Skip to content

Commit 5a342c8

Browse files
authored
cranelift-wasm: Emit constant bounds for fixed-size tables (#8125)
WebAssembly tables have a minimum size, but may optionally also have a maximum size. If the maximum is set to the same number of elements as the minimum, then we can emit an `iconst` instruction for bounds-checks on tables, rather than loading the bounds from memory. That's a win in its own right, but when optimization is enabled, this also allows bounds-checks to constant-fold if the table index is provided as an integer constant.
1 parent 13bbd6a commit 5a342c8

File tree

6 files changed

+224
-30
lines changed

6 files changed

+224
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
;;! target = "x86_64"
2+
;;! optimize = true
3+
4+
;; Test basic code generation for table WebAssembly instructions on
5+
;; non-resizeable tables. Use optimization but with opt-level "none" to
6+
;; legalize away macro instructions.
7+
8+
(module
9+
(table (export "table") 7 7 externref)
10+
(func (export "table.get.const") (result externref)
11+
i32.const 0
12+
table.get 0)
13+
(func (export "table.get.var") (param i32) (result externref)
14+
local.get 0
15+
table.get 0))
16+
17+
;; function u0:0(i64 vmctx) -> r64 fast {
18+
;; gv0 = vmctx
19+
;; gv1 = load.i64 notrap aligned readonly gv0
20+
;;
21+
;; block0(v0: i64):
22+
;; v12 -> v0
23+
;; @0052 v2 = iconst.i32 0
24+
;; @0054 v3 = iconst.i32 7
25+
;; @0054 v4 = icmp uge v2, v3 ; v2 = 0, v3 = 7
26+
;; @0054 brif v4, block2, block3
27+
;;
28+
;; block2 cold:
29+
;; @0054 trap table_oob
30+
;;
31+
;; block3:
32+
;; @0054 v5 = uextend.i64 v2 ; v2 = 0
33+
;; @0054 v6 = load.i64 notrap aligned readonly v12
34+
;; v13 = iconst.i64 4
35+
;; @0054 v7 = ishl v5, v13 ; v13 = 4
36+
;; @0054 v8 = iadd v6, v7
37+
;; @0054 v9 = icmp.i32 uge v2, v3 ; v2 = 0, v3 = 7
38+
;; @0054 v10 = select_spectre_guard v9, v6, v8
39+
;; @0054 v11 = load.r64 notrap aligned table v10
40+
;; v1 -> v11
41+
;; @0056 jump block1
42+
;;
43+
;; block1:
44+
;; @0056 return v1
45+
;; }
46+
;;
47+
;; function u0:1(i32, i64 vmctx) -> r64 fast {
48+
;; gv0 = vmctx
49+
;; gv1 = load.i64 notrap aligned readonly gv0
50+
;;
51+
;; block0(v0: i32, v1: i64):
52+
;; v12 -> v1
53+
;; @005b v3 = iconst.i32 7
54+
;; @005b v4 = icmp uge v0, v3 ; v3 = 7
55+
;; @005b brif v4, block2, block3
56+
;;
57+
;; block2 cold:
58+
;; @005b trap table_oob
59+
;;
60+
;; block3:
61+
;; @005b v5 = uextend.i64 v0
62+
;; @005b v6 = load.i64 notrap aligned readonly v12
63+
;; v13 = iconst.i64 4
64+
;; @005b v7 = ishl v5, v13 ; v13 = 4
65+
;; @005b v8 = iadd v6, v7
66+
;; @005b v9 = icmp.i32 uge v0, v3 ; v3 = 7
67+
;; @005b v10 = select_spectre_guard v9, v6, v8
68+
;; @005b v11 = load.r64 notrap aligned table v10
69+
;; v2 -> v11
70+
;; @005d jump block1
71+
;;
72+
;; block1:
73+
;; @005d return v2
74+
;; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
;;! target = "x86_64"
2+
;;! optimize = true
3+
4+
;; Test basic code generation for table WebAssembly instructions on
5+
;; non-resizeable tables. Use optimization but with opt-level "none" to
6+
;; legalize away macro instructions.
7+
8+
(module
9+
(table (export "table") 7 7 externref)
10+
(func (export "table.set.const") (param externref)
11+
i32.const 0
12+
local.get 0
13+
table.set 0)
14+
(func (export "table.set.var") (param i32 externref)
15+
local.get 0
16+
local.get 1
17+
table.set 0))
18+
19+
;; function u0:0(r64, i64 vmctx) fast {
20+
;; gv0 = vmctx
21+
;; gv1 = load.i64 notrap aligned readonly gv0
22+
;;
23+
;; block0(v0: r64, v1: i64):
24+
;; v11 -> v1
25+
;; @0052 v2 = iconst.i32 0
26+
;; @0056 v3 = iconst.i32 7
27+
;; @0056 v4 = icmp uge v2, v3 ; v2 = 0, v3 = 7
28+
;; @0056 brif v4, block2, block3
29+
;;
30+
;; block2 cold:
31+
;; @0056 trap table_oob
32+
;;
33+
;; block3:
34+
;; @0056 v5 = uextend.i64 v2 ; v2 = 0
35+
;; @0056 v6 = load.i64 notrap aligned readonly v11
36+
;; v12 = iconst.i64 4
37+
;; @0056 v7 = ishl v5, v12 ; v12 = 4
38+
;; @0056 v8 = iadd v6, v7
39+
;; @0056 v9 = icmp.i32 uge v2, v3 ; v2 = 0, v3 = 7
40+
;; @0056 v10 = select_spectre_guard v9, v6, v8
41+
;; @0056 store.r64 notrap aligned table v0, v10
42+
;; @0058 jump block1
43+
;;
44+
;; block1:
45+
;; @0058 return
46+
;; }
47+
;;
48+
;; function u0:1(i32, r64, i64 vmctx) fast {
49+
;; gv0 = vmctx
50+
;; gv1 = load.i64 notrap aligned readonly gv0
51+
;;
52+
;; block0(v0: i32, v1: r64, v2: i64):
53+
;; v11 -> v2
54+
;; @005f v3 = iconst.i32 7
55+
;; @005f v4 = icmp uge v0, v3 ; v3 = 7
56+
;; @005f brif v4, block2, block3
57+
;;
58+
;; block2 cold:
59+
;; @005f trap table_oob
60+
;;
61+
;; block3:
62+
;; @005f v5 = uextend.i64 v0
63+
;; @005f v6 = load.i64 notrap aligned readonly v11
64+
;; v12 = iconst.i64 4
65+
;; @005f v7 = ishl v5, v12 ; v12 = 4
66+
;; @005f v8 = iadd v6, v7
67+
;; @005f v9 = icmp.i32 uge v0, v3 ; v3 = 7
68+
;; @005f v10 = select_spectre_guard v9, v6, v8
69+
;; @005f store.r64 notrap aligned table v1, v10
70+
;; @0061 jump block1
71+
;;
72+
;; block1:
73+
;; @0061 return
74+
;; }

cranelift/wasm/src/environ/dummy.rs

+19-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::func_translator::FuncTranslator;
1010
use crate::state::FuncTranslationState;
1111
use crate::{
1212
DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Heap,
13-
HeapData, HeapStyle, Memory, MemoryIndex, Table, TableIndex, TypeConvert, TypeIndex,
13+
HeapData, HeapStyle, Memory, MemoryIndex, Table, TableIndex, TableSize, TypeConvert, TypeIndex,
1414
WasmFuncType, WasmHeapType, WasmResult,
1515
};
1616
use crate::{TableData, WasmValType};
@@ -263,16 +263,27 @@ impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> {
263263
// When tables in wasm become "growable", revisit whether this can be readonly or not.
264264
flags: ir::MemFlags::trusted().with_readonly(),
265265
});
266-
let bound_gv = func.create_global_value(ir::GlobalValueData::Load {
267-
base: vmctx,
268-
offset: Offset32::new(0),
269-
global_type: I32,
270-
flags: ir::MemFlags::trusted().with_readonly(),
271-
});
266+
267+
let table = &self.mod_info.tables[index].entity;
268+
269+
let bound = if Some(table.minimum) == table.maximum {
270+
TableSize::Static {
271+
bound: table.minimum,
272+
}
273+
} else {
274+
TableSize::Dynamic {
275+
bound_gv: func.create_global_value(ir::GlobalValueData::Load {
276+
base: vmctx,
277+
offset: Offset32::new(0),
278+
global_type: I32,
279+
flags: ir::MemFlags::trusted().with_readonly(),
280+
}),
281+
}
282+
};
272283

273284
self.tables[index] = Some(TableData {
274285
base_gv,
275-
bound_gv,
286+
bound,
276287
element_size: u32::from(self.pointer_bytes()) * 2,
277288
});
278289
}

cranelift/wasm/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub use crate::func_translator::FuncTranslator;
5050
pub use crate::heap::{Heap, HeapData, HeapStyle};
5151
pub use crate::module_translator::translate_module;
5252
pub use crate::state::FuncTranslationState;
53-
pub use crate::table::TableData;
53+
pub use crate::table::{TableData, TableSize};
5454
pub use crate::translation_utils::*;
5555
pub use cranelift_frontend::FunctionBuilder;
5656
pub use wasmtime_types::*;

cranelift/wasm/src/table.rs

+31-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,41 @@
1-
use cranelift_codegen::ir::{self, condcodes::IntCC, InstBuilder};
1+
use cranelift_codegen::cursor::FuncCursor;
2+
use cranelift_codegen::ir::{self, condcodes::IntCC, immediates::Imm64, InstBuilder};
23
use cranelift_frontend::FunctionBuilder;
34

5+
/// Size of a WebAssembly table, in elements.
6+
#[derive(Clone)]
7+
pub 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+
21+
impl 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+
431
/// An implementation of a WebAssembly table.
532
#[derive(Clone)]
633
pub struct TableData {
734
/// Global value giving the address of the start of the table.
835
pub base_gv: ir::GlobalValue,
936

10-
/// Global value giving the current bound of the table, in elements.
11-
pub bound_gv: ir::GlobalValue,
37+
/// The size of the table, in elements.
38+
pub bound: TableSize,
1239

1340
/// The size of a table element, in bytes.
1441
pub element_size: u32,
@@ -27,7 +54,7 @@ impl TableData {
2754
let index_ty = pos.func.dfg.value_type(index);
2855

2956
// Start with the bounds check. Trap if `index + 1 > bound`.
30-
let bound = pos.ins().global_value(index_ty, self.bound_gv);
57+
let bound = self.bound.bound(pos.cursor(), index_ty);
3158

3259
// `index > bound - 1` is the same as `index >= bound`.
3360
let oob = pos

crates/cranelift/src/func_environ.rs

+25-17
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use cranelift_frontend::FunctionBuilder;
1414
use cranelift_frontend::Variable;
1515
use cranelift_wasm::{
1616
FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, Heap, HeapData, HeapStyle,
17-
MemoryIndex, TableData, TableIndex, TargetEnvironment, TypeIndex, WasmHeapType, WasmRefType,
18-
WasmResult, WasmValType,
17+
MemoryIndex, TableData, TableIndex, TableSize, TargetEnvironment, TypeIndex, WasmHeapType,
18+
WasmRefType, WasmResult, WasmValType,
1919
};
2020
use std::mem;
2121
use wasmparser::Operator;
@@ -912,23 +912,31 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
912912
global_type: pointer_type,
913913
flags: MemFlags::trusted(),
914914
});
915-
let bound_gv = func.create_global_value(ir::GlobalValueData::Load {
916-
base: ptr,
917-
offset: Offset32::new(current_elements_offset),
918-
global_type: ir::Type::int(
919-
u16::from(self.offsets.size_of_vmtable_definition_current_elements()) * 8,
920-
)
921-
.unwrap(),
922-
flags: MemFlags::trusted(),
923-
});
924915

925-
let element_size = self
926-
.reference_type(self.module.table_plans[index].table.wasm_ty.heap_type)
927-
.bytes();
916+
let table = &self.module.table_plans[index].table;
917+
let element_size = self.reference_type(table.wasm_ty.heap_type).bytes();
918+
919+
let bound = if Some(table.minimum) == table.maximum {
920+
TableSize::Static {
921+
bound: table.minimum,
922+
}
923+
} else {
924+
TableSize::Dynamic {
925+
bound_gv: func.create_global_value(ir::GlobalValueData::Load {
926+
base: ptr,
927+
offset: Offset32::new(current_elements_offset),
928+
global_type: ir::Type::int(
929+
u16::from(self.offsets.size_of_vmtable_definition_current_elements()) * 8,
930+
)
931+
.unwrap(),
932+
flags: MemFlags::trusted(),
933+
}),
934+
}
935+
};
928936

929937
self.tables[index] = Some(TableData {
930938
base_gv,
931-
bound_gv,
939+
bound,
932940
element_size,
933941
});
934942
}
@@ -2494,12 +2502,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
24942502

24952503
fn translate_table_size(
24962504
&mut self,
2497-
mut pos: FuncCursor,
2505+
pos: FuncCursor,
24982506
table_index: TableIndex,
24992507
) -> WasmResult<ir::Value> {
25002508
self.ensure_table_exists(pos.func, table_index);
25012509
let table_data = self.tables[table_index].as_ref().unwrap();
2502-
Ok(pos.ins().global_value(ir::types::I32, table_data.bound_gv))
2510+
Ok(table_data.bound.bound(pos, ir::types::I32))
25032511
}
25042512

25052513
fn translate_table_copy(

0 commit comments

Comments
 (0)