Skip to content

Commit 193551a

Browse files
authored
Optimize table.init instruction and instantiation (#2847)
* Optimize `table.init` instruction and instantiation This commit optimizes table initialization as part of instance instantiation and also applies the same optimization to the `table.init` instruction. One part of this commit is to remove some preexisting duplication between instance instantiation and the `table.init` instruction itself, after this the actual implementation of `table.init` is optimized to effectively have fewer bounds checks in fewer places and have a much tighter loop for instantiation. A big fallout from this change is that memory/table initializer offsets are now stored as `u32` instead of `usize` to remove a few casts in a few places. This ended up requiring moving some overflow checks that happened in parsing to later in code itself because otherwise the wrong spec test errors are emitted during testing. I've tried to trace where these can possibly overflow but I think that I managed to get everything. In a local synthetic test where an empty module with a single 80,000 element initializer this improves total instantiation time by 4x (562us => 141us) * Review comments
1 parent 2864bb4 commit 193551a

8 files changed

Lines changed: 137 additions & 126 deletions

File tree

cranelift/wasm/src/environ/dummy.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ impl<'data> ModuleEnvironment<'data> for DummyEnvironment {
747747
&mut self,
748748
_table_index: TableIndex,
749749
_base: Option<GlobalIndex>,
750-
_offset: usize,
750+
_offset: u32,
751751
_elements: Box<[FuncIndex]>,
752752
) -> WasmResult<()> {
753753
// We do nothing
@@ -792,7 +792,7 @@ impl<'data> ModuleEnvironment<'data> for DummyEnvironment {
792792
&mut self,
793793
_memory_index: MemoryIndex,
794794
_base: Option<GlobalIndex>,
795-
_offset: usize,
795+
_offset: u32,
796796
_data: &'data [u8],
797797
) -> WasmResult<()> {
798798
// We do nothing

cranelift/wasm/src/environ/spec.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment {
937937
&mut self,
938938
table_index: TableIndex,
939939
base: Option<GlobalIndex>,
940-
offset: usize,
940+
offset: u32,
941941
elements: Box<[FuncIndex]>,
942942
) -> WasmResult<()>;
943943

@@ -984,7 +984,7 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment {
984984
&mut self,
985985
memory_index: MemoryIndex,
986986
base: Option<GlobalIndex>,
987-
offset: usize,
987+
offset: u32,
988988
data: &'data [u8],
989989
) -> WasmResult<()>;
990990

cranelift/wasm/src/sections_translator.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ pub fn parse_element_section<'data>(
377377
} => {
378378
let mut init_expr_reader = init_expr.get_binary_reader();
379379
let (base, offset) = match init_expr_reader.read_operator()? {
380-
Operator::I32Const { value } => (None, value as u32 as usize),
380+
Operator::I32Const { value } => (None, value as u32),
381381
Operator::GlobalGet { global_index } => {
382382
(Some(GlobalIndex::from_u32(global_index)), 0)
383383
}
@@ -388,12 +388,6 @@ pub fn parse_element_section<'data>(
388388
));
389389
}
390390
};
391-
// Check for offset + len overflow
392-
if offset.checked_add(segments.len()).is_none() {
393-
return Err(wasm_unsupported!(
394-
"element segment offset and length overflows"
395-
));
396-
}
397391
environ.declare_table_elements(
398392
TableIndex::from_u32(table_index),
399393
base,
@@ -429,7 +423,7 @@ pub fn parse_data_section<'data>(
429423
} => {
430424
let mut init_expr_reader = init_expr.get_binary_reader();
431425
let (base, offset) = match init_expr_reader.read_operator()? {
432-
Operator::I32Const { value } => (None, value as u32 as usize),
426+
Operator::I32Const { value } => (None, value as u32),
433427
Operator::GlobalGet { global_index } => {
434428
(Some(GlobalIndex::from_u32(global_index)), 0)
435429
}
@@ -440,12 +434,6 @@ pub fn parse_data_section<'data>(
440434
))
441435
}
442436
};
443-
// Check for offset + len overflow
444-
if offset.checked_add(data.len()).is_none() {
445-
return Err(wasm_unsupported!(
446-
"data segment offset and length overflows"
447-
));
448-
}
449437
environ.declare_data_initialization(
450438
MemoryIndex::from_u32(memory_index),
451439
base,

crates/environ/src/module.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use cranelift_wasm::*;
77
use indexmap::IndexMap;
88
use serde::{Deserialize, Serialize};
99
use std::collections::{HashMap, HashSet};
10+
use std::convert::TryFrom;
1011
use std::sync::Arc;
1112

1213
/// Implemenation styles for WebAssembly linear memory.
@@ -86,7 +87,7 @@ pub struct MemoryInitializer {
8687
/// Optionally, a global variable giving a base index.
8788
pub base: Option<GlobalIndex>,
8889
/// The offset to add to the base.
89-
pub offset: usize,
90+
pub offset: u32,
9091
/// The data to write into the linear memory.
9192
pub data: Box<[u8]>,
9293
}
@@ -168,7 +169,15 @@ impl MemoryInitialization {
168169
// Perform a bounds check on the segment
169170
// As this segment is referencing a defined memory without a global base, the last byte
170171
// written to by the segment cannot exceed the memory's initial minimum size
171-
if (initializer.offset + initializer.data.len())
172+
let offset = usize::try_from(initializer.offset).unwrap();
173+
let end = match offset.checked_add(initializer.data.len()) {
174+
Some(end) => end,
175+
None => {
176+
out_of_bounds = true;
177+
continue;
178+
}
179+
};
180+
if end
172181
> ((module.memory_plans[initializer.memory_index].memory.minimum
173182
as usize)
174183
* WASM_PAGE_SIZE)
@@ -178,8 +187,8 @@ impl MemoryInitialization {
178187
}
179188

180189
let pages = &mut map[index];
181-
let mut page_index = initializer.offset / WASM_PAGE_SIZE;
182-
let mut page_offset = initializer.offset % WASM_PAGE_SIZE;
190+
let mut page_index = offset / WASM_PAGE_SIZE;
191+
let mut page_offset = offset % WASM_PAGE_SIZE;
183192
let mut data_offset = 0;
184193
let mut data_remaining = initializer.data.len();
185194

@@ -268,7 +277,7 @@ pub struct TableInitializer {
268277
/// Optionally, a global variable giving a base index.
269278
pub base: Option<GlobalIndex>,
270279
/// The offset to add to the base.
271-
pub offset: usize,
280+
pub offset: u32,
272281
/// The values to write into the table elements.
273282
pub elements: Box<[FuncIndex]>,
274283
}

crates/environ/src/module_environ.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
705705
&mut self,
706706
table_index: TableIndex,
707707
base: Option<GlobalIndex>,
708-
offset: usize,
708+
offset: u32,
709709
elements: Box<[FuncIndex]>,
710710
) -> WasmResult<()> {
711711
for element in elements.iter() {
@@ -794,7 +794,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
794794
&mut self,
795795
memory_index: MemoryIndex,
796796
base: Option<GlobalIndex>,
797-
offset: usize,
797+
offset: u32,
798798
data: &'data [u8],
799799
) -> WasmResult<()> {
800800
match &mut self.result.module.memory_initialization {

crates/runtime/src/instance.rs

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -573,11 +573,11 @@ impl Instance {
573573
return None;
574574
}
575575

576-
Some(unsafe { &*self.anyfunc_ptr(index) })
576+
unsafe { Some(&*self.vmctx_plus_offset(self.offsets.vmctx_anyfunc(index))) }
577577
}
578578

579-
unsafe fn anyfunc_ptr(&self, index: FuncIndex) -> *mut VMCallerCheckedAnyfunc {
580-
self.vmctx_plus_offset(self.offsets.vmctx_anyfunc(index))
579+
unsafe fn anyfunc_base(&self) -> *mut VMCallerCheckedAnyfunc {
580+
self.vmctx_plus_offset(self.offsets.vmctx_anyfuncs_begin())
581581
}
582582

583583
fn find_passive_segment<'a, I, D, T>(
@@ -611,38 +611,56 @@ impl Instance {
611611
src: u32,
612612
len: u32,
613613
) -> Result<(), Trap> {
614-
// https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-init
615-
616-
let table = self.get_table(table_index);
617-
618614
let elements = Self::find_passive_segment(
619615
elem_index,
620616
&self.module.passive_elements_map,
621617
&self.module.passive_elements,
622618
&self.dropped_elements,
623619
);
620+
self.table_init_segment(table_index, elements, dst, src, len)
621+
}
624622

625-
if src
626-
.checked_add(len)
627-
.map_or(true, |n| n as usize > elements.len())
628-
|| dst.checked_add(len).map_or(true, |m| m > table.size())
629-
{
630-
return Err(Trap::wasm(ir::TrapCode::TableOutOfBounds));
631-
}
623+
pub(crate) fn table_init_segment(
624+
&self,
625+
table_index: TableIndex,
626+
elements: &[FuncIndex],
627+
dst: u32,
628+
src: u32,
629+
len: u32,
630+
) -> Result<(), Trap> {
631+
// https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-init
632632

633-
// TODO(#983): investigate replacing this get/set loop with a `memcpy`.
634-
for (dst, src) in (dst..dst + len).zip(src..src + len) {
635-
let elem = self
636-
.get_caller_checked_anyfunc(elements[src as usize])
637-
.map_or(ptr::null_mut(), |f: &VMCallerCheckedAnyfunc| {
638-
f as *const VMCallerCheckedAnyfunc as *mut _
639-
});
640-
641-
table
642-
.set(dst, TableElement::FuncRef(elem))
643-
.expect("should never panic because we already did the bounds check above");
644-
}
633+
let table = self.get_table(table_index);
645634

635+
let elements = match elements
636+
.get(usize::try_from(src).unwrap()..)
637+
.and_then(|s| s.get(..usize::try_from(len).unwrap()))
638+
{
639+
Some(elements) => elements,
640+
None => return Err(Trap::wasm(ir::TrapCode::TableOutOfBounds)),
641+
};
642+
643+
match table.element_type() {
644+
TableElementType::Func => unsafe {
645+
let base = self.anyfunc_base();
646+
table.init_funcs(
647+
dst,
648+
elements.iter().map(|idx| {
649+
if *idx == FuncIndex::reserved_value() {
650+
ptr::null_mut()
651+
} else {
652+
debug_assert!(idx.as_u32() < self.offsets.num_defined_functions);
653+
base.add(usize::try_from(idx.as_u32()).unwrap())
654+
}
655+
}),
656+
)?;
657+
},
658+
659+
TableElementType::Val(_) => {
660+
debug_assert!(elements.iter().all(|e| *e == FuncIndex::reserved_value()));
661+
table.fill(dst, TableElement::ExternRef(None), len)?;
662+
}
663+
}
646664
Ok(())
647665
}
648666

@@ -773,16 +791,26 @@ impl Instance {
773791
src: u32,
774792
len: u32,
775793
) -> Result<(), Trap> {
776-
// https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-memory-init
777-
778-
let memory = self.get_memory(memory_index);
779-
780794
let data = Self::find_passive_segment(
781795
data_index,
782796
&self.module.passive_data_map,
783797
&self.module.passive_data,
784798
&self.dropped_data,
785799
);
800+
self.memory_init_segment(memory_index, &data, dst, src, len)
801+
}
802+
803+
pub(crate) fn memory_init_segment(
804+
&self,
805+
memory_index: MemoryIndex,
806+
data: &[u8],
807+
dst: u32,
808+
src: u32,
809+
len: u32,
810+
) -> Result<(), Trap> {
811+
// https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-memory-init
812+
813+
let memory = self.get_memory(memory_index);
786814

787815
if src
788816
.checked_add(len)

0 commit comments

Comments
 (0)