Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 26 additions & 30 deletions cranelift/filetests/filetests/wasm/table-get.wat
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,28 @@
;; gv0 = vmctx
;; gv1 = load.i64 notrap aligned readonly gv0
;; gv2 = load.i32 notrap aligned readonly gv0
;; table0 = dynamic gv1, min 0, bound gv2, element_size 16, index_type i32
;;
;; block0(v0: i64):
;; v12 -> v0
;; v13 -> v0
;; v14 -> v0
;; @0051 v2 = iconst.i32 0
;; @0053 v5 = load.i32 notrap aligned readonly v13
;; @0053 v6 = icmp uge v2, v5 ; v2 = 0
;; @0053 brif v6, block2, block3
;; @0053 v3 = load.i32 notrap aligned readonly v12
;; @0053 v4 = icmp uge v2, v3 ; v2 = 0
;; @0053 brif v4, block2, block3
;;
;; block2 cold:
;; @0053 trap table_oob
;;
;; block3:
;; @0053 v7 = uextend.i64 v2 ; v2 = 0
;; @0053 v8 = load.i64 notrap aligned readonly v14
;; v15 = iconst.i64 4
;; @0053 v9 = ishl v7, v15 ; v15 = 4
;; @0053 v10 = iadd v8, v9
;; @0053 v11 = icmp.i32 uge v2, v5 ; v2 = 0
;; @0053 v12 = select_spectre_guard v11, v8, v10
;; v3 -> v12
;; @0053 v4 = load.r64 notrap aligned table v3
;; v1 -> v4
;; @0053 v5 = uextend.i64 v2 ; v2 = 0
;; @0053 v6 = load.i64 notrap aligned readonly v13
;; v14 = iconst.i64 4
;; @0053 v7 = ishl v5, v14 ; v14 = 4
;; @0053 v8 = iadd v6, v7
;; @0053 v9 = icmp.i32 uge v2, v3 ; v2 = 0
;; @0053 v10 = select_spectre_guard v9, v6, v8
;; @0053 v11 = load.r64 notrap aligned table v10
;; v1 -> v11
;; @0055 jump block1
;;
;; block1:
Expand All @@ -51,29 +49,27 @@
;; gv0 = vmctx
;; gv1 = load.i64 notrap aligned readonly gv0
;; gv2 = load.i32 notrap aligned readonly gv0
;; table0 = dynamic gv1, min 0, bound gv2, element_size 16, index_type i32
;;
;; block0(v0: i32, v1: i64):
;; v12 -> v1
;; v13 -> v1
;; v14 -> v1
;; @005a v5 = load.i32 notrap aligned readonly v13
;; @005a v6 = icmp uge v0, v5
;; @005a brif v6, block2, block3
;; @005a v3 = load.i32 notrap aligned readonly v12
;; @005a v4 = icmp uge v0, v3
;; @005a brif v4, block2, block3
;;
;; block2 cold:
;; @005a trap table_oob
;;
;; block3:
;; @005a v7 = uextend.i64 v0
;; @005a v8 = load.i64 notrap aligned readonly v14
;; v15 = iconst.i64 4
;; @005a v9 = ishl v7, v15 ; v15 = 4
;; @005a v10 = iadd v8, v9
;; @005a v11 = icmp.i32 uge v0, v5
;; @005a v12 = select_spectre_guard v11, v8, v10
;; v3 -> v12
;; @005a v4 = load.r64 notrap aligned table v3
;; v2 -> v4
;; @005a v5 = uextend.i64 v0
;; @005a v6 = load.i64 notrap aligned readonly v13
;; v14 = iconst.i64 4
;; @005a v7 = ishl v5, v14 ; v14 = 4
;; @005a v8 = iadd v6, v7
;; @005a v9 = icmp.i32 uge v0, v3
;; @005a v10 = select_spectre_guard v9, v6, v8
;; @005a v11 = load.r64 notrap aligned table v10
;; v2 -> v11
;; @005c jump block1
;;
;; block1:
Expand Down
52 changes: 24 additions & 28 deletions cranelift/filetests/filetests/wasm/table-set.wat
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,27 @@
;; gv0 = vmctx
;; gv1 = load.i64 notrap aligned readonly gv0
;; gv2 = load.i32 notrap aligned readonly gv0
;; table0 = dynamic gv1, min 0, bound gv2, element_size 16, index_type i32
;;
;; block0(v0: r64, v1: i64):
;; v11 -> v1
;; v12 -> v1
;; v13 -> v1
;; @0051 v2 = iconst.i32 0
;; @0055 v4 = load.i32 notrap aligned readonly v12
;; @0055 v5 = icmp uge v2, v4 ; v2 = 0
;; @0055 brif v5, block2, block3
;; @0055 v3 = load.i32 notrap aligned readonly v11
;; @0055 v4 = icmp uge v2, v3 ; v2 = 0
;; @0055 brif v4, block2, block3
;;
;; block2 cold:
;; @0055 trap table_oob
;;
;; block3:
;; @0055 v6 = uextend.i64 v2 ; v2 = 0
;; @0055 v7 = load.i64 notrap aligned readonly v13
;; v14 = iconst.i64 4
;; @0055 v8 = ishl v6, v14 ; v14 = 4
;; @0055 v9 = iadd v7, v8
;; @0055 v10 = icmp.i32 uge v2, v4 ; v2 = 0
;; @0055 v11 = select_spectre_guard v10, v7, v9
;; v3 -> v11
;; @0055 store.r64 notrap aligned table v0, v3
;; @0055 v5 = uextend.i64 v2 ; v2 = 0
;; @0055 v6 = load.i64 notrap aligned readonly v12
;; v13 = iconst.i64 4
;; @0055 v7 = ishl v5, v13 ; v13 = 4
;; @0055 v8 = iadd v6, v7
;; @0055 v9 = icmp.i32 uge v2, v3 ; v2 = 0
;; @0055 v10 = select_spectre_guard v9, v6, v8
;; @0055 store.r64 notrap aligned table v0, v10
;; @0057 jump block1
;;
;; block1:
Expand All @@ -52,28 +50,26 @@
;; gv0 = vmctx
;; gv1 = load.i64 notrap aligned readonly gv0
;; gv2 = load.i32 notrap aligned readonly gv0
;; table0 = dynamic gv1, min 0, bound gv2, element_size 16, index_type i32
;;
;; block0(v0: i32, v1: r64, v2: i64):
;; v11 -> v2
;; v12 -> v2
;; v13 -> v2
;; @005e v4 = load.i32 notrap aligned readonly v12
;; @005e v5 = icmp uge v0, v4
;; @005e brif v5, block2, block3
;; @005e v3 = load.i32 notrap aligned readonly v11
;; @005e v4 = icmp uge v0, v3
;; @005e brif v4, block2, block3
;;
;; block2 cold:
;; @005e trap table_oob
;;
;; block3:
;; @005e v6 = uextend.i64 v0
;; @005e v7 = load.i64 notrap aligned readonly v13
;; v14 = iconst.i64 4
;; @005e v8 = ishl v6, v14 ; v14 = 4
;; @005e v9 = iadd v7, v8
;; @005e v10 = icmp.i32 uge v0, v4
;; @005e v11 = select_spectre_guard v10, v7, v9
;; v3 -> v11
;; @005e store.r64 notrap aligned table v1, v3
;; @005e v5 = uextend.i64 v0
;; @005e v6 = load.i64 notrap aligned readonly v12
;; v13 = iconst.i64 4
;; @005e v7 = ishl v5, v13 ; v13 = 4
;; @005e v8 = iadd v6, v7
;; @005e v9 = icmp.i32 uge v0, v3
;; @005e v10 = select_spectre_guard v9, v6, v8
;; @005e store.r64 notrap aligned table v1, v10
;; @0060 jump block1
;;
;; block1:
Expand Down
25 changes: 11 additions & 14 deletions cranelift/wasm/src/environ/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@
use crate::environ::{FuncEnvironment, GlobalVariable, ModuleEnvironment, TargetEnvironment};
use crate::func_translator::FuncTranslator;
use crate::state::FuncTranslationState;
use crate::WasmValType;
use crate::{
DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Heap,
HeapData, HeapStyle, Memory, MemoryIndex, Table, TableIndex, TypeConvert, TypeIndex,
WasmFuncType, WasmHeapType, WasmResult,
};
use crate::{TableData, WasmValType};
use cranelift_codegen::cursor::FuncCursor;
use cranelift_codegen::ir::immediates::{Offset32, Uimm64};
use cranelift_codegen::ir::immediates::Offset32;
use cranelift_codegen::ir::{self, InstBuilder};
use cranelift_codegen::ir::{types::*, UserFuncName};
use cranelift_codegen::isa::{CallConv, TargetFrontendConfig};
use cranelift_entity::packed_option::ReservedValue;
use cranelift_entity::{EntityRef, PrimaryMap, SecondaryMap};
use cranelift_frontend::FunctionBuilder;
use std::boxed::Box;
Expand Down Expand Up @@ -214,7 +213,7 @@ pub struct DummyFuncEnvironment<'dummy_environment> {
pub heaps: PrimaryMap<Heap, HeapData>,

/// Cranelift tables we have created to implement Wasm tables.
tables: SecondaryMap<TableIndex, ir::Table>,
tables: SecondaryMap<TableIndex, Option<TableData>>,
}

impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> {
Expand All @@ -227,7 +226,7 @@ impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> {
mod_info,
expected_reachability,
heaps: Default::default(),
tables: SecondaryMap::with_default(ir::Table::reserved_value()),
tables: Default::default(),
}
}

Expand All @@ -251,7 +250,7 @@ impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> {
}

fn ensure_table_exists(&mut self, func: &mut ir::Function, index: TableIndex) {
if !self.tables[index].is_reserved_value() {
if self.tables[index].is_some() {
return;
}

Expand All @@ -271,12 +270,10 @@ impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> {
flags: ir::MemFlags::trusted().with_readonly(),
});

self.tables[index] = func.create_table(ir::TableData {
self.tables[index] = Some(TableData {
base_gv,
min_size: Uimm64::new(0),
bound_gv,
element_size: Uimm64::from(u64::from(self.pointer_bytes()) * 2),
index_type: I32,
element_size: u32::from(self.pointer_bytes()) * 2,
});
}
}
Expand Down Expand Up @@ -603,8 +600,8 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
) -> WasmResult<ir::Value> {
let pointer_type = self.pointer_type();
self.ensure_table_exists(builder.func, table_index);
let table = self.tables[table_index];
let table_entry_addr = builder.ins().table_addr(pointer_type, table, index, 0);
let table = self.tables[table_index].as_ref().unwrap();
let table_entry_addr = table.prepare_table_addr(builder, index, pointer_type, true);
let flags = ir::MemFlags::trusted().with_table();
let value = builder
.ins()
Expand All @@ -621,8 +618,8 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
) -> WasmResult<()> {
let pointer_type = self.pointer_type();
self.ensure_table_exists(builder.func, table_index);
let table = self.tables[table_index];
let table_entry_addr = builder.ins().table_addr(pointer_type, table, index, 0);
let table = self.tables[table_index].as_ref().unwrap();
let table_entry_addr = table.prepare_table_addr(builder, index, pointer_type, true);
let flags = ir::MemFlags::trusted().with_table();
builder.ins().store(flags, value, table_entry_addr, 0);
Ok(())
Expand Down
2 changes: 2 additions & 0 deletions cranelift/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod heap;
mod module_translator;
mod sections_translator;
mod state;
mod table;
mod translation_utils;

pub use crate::environ::{
Expand All @@ -49,6 +50,7 @@ pub use crate::func_translator::FuncTranslator;
pub use crate::heap::{Heap, HeapData, HeapStyle};
pub use crate::module_translator::translate_module;
pub use crate::state::FuncTranslationState;
pub use crate::table::TableData;
pub use crate::translation_utils::*;
pub use cranelift_frontend::FunctionBuilder;
pub use wasmtime_types::*;
Expand Down
77 changes: 77 additions & 0 deletions cranelift/wasm/src/table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use cranelift_codegen::ir::{self, condcodes::IntCC, InstBuilder};
use cranelift_frontend::FunctionBuilder;

/// An implementation of a WebAssembly table.
#[derive(Clone)]
pub struct TableData {
/// Global value giving the address of the start of the table.
pub base_gv: ir::GlobalValue,

/// Global value giving the current bound of the table, in elements.
pub bound_gv: ir::GlobalValue,

/// The size of a table element, in bytes.
pub element_size: u32,
}

impl TableData {
/// Return a CLIF value containing a native pointer to the beginning of the
/// given index within this table.
pub fn prepare_table_addr(
&self,
pos: &mut FunctionBuilder,
mut index: ir::Value,
addr_ty: ir::Type,
enable_table_access_spectre_mitigation: bool,
) -> ir::Value {
let index_ty = pos.func.dfg.value_type(index);

// Start with the bounds check. Trap if `index + 1 > bound`.
let bound = pos.ins().global_value(index_ty, self.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);

// If Spectre mitigations are enabled, we will use a comparison to
// short-circuit the computed table element address to the start
// of the table on the misspeculation path when out-of-bounds.
let spectre_oob_cmp = if enable_table_access_spectre_mitigation {
Some((index, bound))
} else {
None
};

// 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 = pos.ins().global_value(addr_ty, self.base_gv);

let element_size = self.element_size;
let offset = if element_size == 1 {
index
} else if element_size.is_power_of_two() {
pos.ins()
.ishl_imm(index, i64::from(element_size.trailing_zeros()))
} else {
pos.ins().imul_imm(index, element_size as i64)
};
Comment on lines +58 to +63
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% convinced it is worth adding the power-of-2 optimization here, since our mid-end rules should definitely already handle this. I think this file should focus on just (a) the correctness of the bounds checks and (b) optimizations to the bounds checks that rely on knowledge of the Wasm table and its limits that the mid-end can't possibly know.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And apologies if tihs already existed. I think we can clean up a tiny bit of complexity here, if that was the case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I considered removing that in favor of relying on the corresponding egraph optimizations, but decided to keep it since it was in the original. I'd prefer to land it in this form to avoid bigger changes to the filetests, where I specifically have not turned on the egraph pass so it's more clear what's being generated, but I wouldn't be upset about removing this later.


let element_addr = pos.ins().iadd(base, offset);

if let Some((index, bound)) = spectre_oob_cmp {
let cond = pos
.ins()
.icmp(IntCC::UnsignedGreaterThanOrEqual, index, bound);
// If out-of-bounds, choose the table base on the misspeculation path.
pos.ins().select_spectre_guard(cond, base, element_addr)
} else {
element_addr
}
}
}
Loading