Skip to content

Commit b44c23d

Browse files
authored
wasmtime: Use ConstExpr for data segment offsets (bytecodealliance#8515)
This shouldn't change any behavior currently, but prepares us for supporting extended constant expressions.
1 parent d48d610 commit b44c23d

File tree

3 files changed

+131
-147
lines changed

3 files changed

+131
-147
lines changed

crates/environ/src/compile/module_environ.rs

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -619,24 +619,11 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
619619
} => {
620620
let range = mk_range(&mut self.result.total_data)?;
621621
let memory_index = MemoryIndex::from_u32(memory_index);
622-
let mut offset_expr_reader = offset_expr.get_binary_reader();
623-
let (base, offset) = match offset_expr_reader.read_operator()? {
624-
Operator::I32Const { value } => (None, value.unsigned().into()),
625-
Operator::I64Const { value } => (None, value.unsigned()),
626-
Operator::GlobalGet { global_index } => {
627-
(Some(GlobalIndex::from_u32(global_index)), 0)
628-
}
629-
s => {
630-
bail!(WasmError::Unsupported(format!(
631-
"unsupported init expr in data section: {:?}",
632-
s
633-
)));
634-
}
635-
};
622+
let (offset, escaped) = ConstExpr::from_wasmparser(offset_expr)?;
623+
debug_assert!(escaped.is_empty());
636624

637625
initializers.push(MemoryInitializer {
638626
memory_index,
639-
base,
640627
offset,
641628
data: range,
642629
});
@@ -1039,30 +1026,54 @@ impl ModuleTranslation<'_> {
10391026
segments: Vec::new(),
10401027
});
10411028
}
1042-
let mut idx = 0;
1043-
let ok = self.module.memory_initialization.init_memory(
1044-
&mut (),
1045-
InitMemory::CompileTime(&self.module),
1046-
|(), memory, init| {
1029+
1030+
struct InitMemoryAtCompileTime<'a> {
1031+
module: &'a Module,
1032+
info: &'a mut PrimaryMap<MemoryIndex, Memory>,
1033+
idx: usize,
1034+
}
1035+
impl InitMemory for InitMemoryAtCompileTime<'_> {
1036+
fn memory_size_in_pages(&mut self, memory_index: MemoryIndex) -> u64 {
1037+
self.module.memory_plans[memory_index].memory.minimum
1038+
}
1039+
1040+
fn eval_offset(&mut self, memory_index: MemoryIndex, expr: &ConstExpr) -> Option<u64> {
1041+
let mem64 = self.module.memory_plans[memory_index].memory.memory64;
1042+
match expr.ops() {
1043+
&[ConstOp::I32Const(offset)] if !mem64 => Some(offset.unsigned().into()),
1044+
&[ConstOp::I64Const(offset)] if mem64 => Some(offset.unsigned()),
1045+
_ => None,
1046+
}
1047+
}
1048+
1049+
fn write(&mut self, memory: MemoryIndex, init: &StaticMemoryInitializer) -> bool {
10471050
// Currently `Static` only applies to locally-defined memories,
10481051
// so if a data segment references an imported memory then
10491052
// transitioning to a `Static` memory initializer is not
10501053
// possible.
10511054
if self.module.defined_memory_index(memory).is_none() {
10521055
return false;
10531056
};
1054-
let info = &mut info[memory];
1057+
let info = &mut self.info[memory];
10551058
let data_len = u64::from(init.data.end - init.data.start);
10561059
if data_len > 0 {
10571060
info.data_size += data_len;
10581061
info.min_addr = info.min_addr.min(init.offset);
10591062
info.max_addr = info.max_addr.max(init.offset + data_len);
1060-
info.segments.push((idx, init.clone()));
1063+
info.segments.push((self.idx, init.clone()));
10611064
}
1062-
idx += 1;
1065+
self.idx += 1;
10631066
true
1064-
},
1065-
);
1067+
}
1068+
}
1069+
let ok = self
1070+
.module
1071+
.memory_initialization
1072+
.init_memory(&mut InitMemoryAtCompileTime {
1073+
idx: 0,
1074+
module: &self.module,
1075+
info: &mut info,
1076+
});
10661077
if !ok {
10671078
return;
10681079
}

crates/environ/src/module.rs

Lines changed: 27 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,8 @@ impl MemoryPlan {
102102
pub struct MemoryInitializer {
103103
/// The index of a linear memory to initialize.
104104
pub memory_index: MemoryIndex,
105-
/// Optionally, a global variable giving a base index.
106-
pub base: Option<GlobalIndex>,
107-
/// The offset to add to the base.
108-
pub offset: u64,
105+
/// The base offset to start this segment at.
106+
pub offset: ConstExpr,
109107
/// The range of the data to write within the linear memory.
110108
///
111109
/// This range indexes into a separately stored data section which will be
@@ -201,37 +199,14 @@ impl MemoryInitialization {
201199
/// only be called as part of initialization, however, as it's structured to
202200
/// allow learning about memory ahead-of-time at compile time possibly.
203201
///
204-
/// The various callbacks provided here are used to drive the smaller bits
205-
/// of initialization, such as:
206-
///
207-
/// * `get_cur_size_in_pages` - gets the current size, in wasm pages, of the
208-
/// memory specified. For compile-time purposes this would be the memory
209-
/// type's minimum size.
210-
///
211-
/// * `get_global` - gets the value of the global specified. This is
212-
/// statically, via validation, a pointer to the global of the correct
213-
/// type (either u32 or u64 depending on the memory), but the value
214-
/// returned here is `u64`. A `None` value can be returned to indicate
215-
/// that the global's value isn't known yet.
216-
///
217-
/// * `write` - a callback used to actually write data. This indicates that
218-
/// the specified memory must receive the specified range of data at the
219-
/// specified offset. This can internally return an false error if it
220-
/// wants to fail.
221-
///
222202
/// This function will return true if all memory initializers are processed
223203
/// successfully. If any initializer hits an error or, for example, a
224204
/// global value is needed but `None` is returned, then false will be
225205
/// returned. At compile-time this typically means that the "error" in
226206
/// question needs to be deferred to runtime, and at runtime this means
227207
/// that an invalid initializer has been found and a trap should be
228208
/// generated.
229-
pub fn init_memory<T>(
230-
&self,
231-
state: &mut T,
232-
init: InitMemory<'_, T>,
233-
mut write: impl FnMut(&mut T, MemoryIndex, &StaticMemoryInitializer) -> bool,
234-
) -> bool {
209+
pub fn init_memory(&self, state: &mut dyn InitMemory) -> bool {
235210
let initializers = match self {
236211
// Fall through below to the segmented memory one-by-one
237212
// initialization.
@@ -245,7 +220,7 @@ impl MemoryInitialization {
245220
MemoryInitialization::Static { map } => {
246221
for (index, init) in map {
247222
if let Some(init) = init {
248-
let result = write(state, index, init);
223+
let result = state.write(index, init);
249224
if !result {
250225
return result;
251226
}
@@ -256,28 +231,18 @@ impl MemoryInitialization {
256231
};
257232

258233
for initializer in initializers {
259-
let MemoryInitializer {
234+
let &MemoryInitializer {
260235
memory_index,
261-
base,
262-
offset,
236+
ref offset,
263237
ref data,
264-
} = *initializer;
238+
} = initializer;
265239

266240
// First up determine the start/end range and verify that they're
267241
// in-bounds for the initial size of the memory at `memory_index`.
268242
// Note that this can bail if we don't have access to globals yet
269243
// (e.g. this is a task happening before instantiation at
270244
// compile-time).
271-
let base = match base {
272-
Some(index) => match &init {
273-
InitMemory::Runtime {
274-
get_global_as_u64, ..
275-
} => get_global_as_u64(state, index),
276-
InitMemory::CompileTime(_) => return false,
277-
},
278-
None => 0,
279-
};
280-
let start = match base.checked_add(offset) {
245+
let start = match state.eval_offset(memory_index, offset) {
281246
Some(start) => start,
282247
None => return false,
283248
};
@@ -287,13 +252,7 @@ impl MemoryInitialization {
287252
None => return false,
288253
};
289254

290-
let cur_size_in_pages = match &init {
291-
InitMemory::CompileTime(module) => module.memory_plans[memory_index].memory.minimum,
292-
InitMemory::Runtime {
293-
memory_size_in_pages,
294-
..
295-
} => memory_size_in_pages(state, memory_index),
296-
};
255+
let cur_size_in_pages = state.memory_size_in_pages(memory_index);
297256

298257
// Note that this `minimum` can overflow if `minimum` is
299258
// `1 << 48`, the maximum number of minimum pages for 64-bit
@@ -318,7 +277,7 @@ impl MemoryInitialization {
318277
offset: start,
319278
data: data.clone(),
320279
};
321-
let result = write(state, memory_index, &init);
280+
let result = state.write(memory_index, &init);
322281
if !result {
323282
return result;
324283
}
@@ -328,25 +287,23 @@ impl MemoryInitialization {
328287
}
329288
}
330289

331-
/// Argument to [`MemoryInitialization::init_memory`] indicating the current
332-
/// status of the instance.
333-
pub enum InitMemory<'a, T> {
334-
/// This evaluation of memory initializers is happening at compile time.
335-
/// This means that the current state of memories is whatever their initial
336-
/// state is, and additionally globals are not available if data segments
337-
/// have global offsets.
338-
CompileTime(&'a Module),
339-
340-
/// Evaluation of memory initializers is happening at runtime when the
341-
/// instance is available, and callbacks are provided to learn about the
342-
/// instance's state.
343-
Runtime {
344-
/// Returns the size, in wasm pages, of the the memory specified.
345-
memory_size_in_pages: &'a dyn Fn(&mut T, MemoryIndex) -> u64,
346-
/// Returns the value of the global, as a `u64`. Note that this may
347-
/// involve zero-extending a 32-bit global to a 64-bit number.
348-
get_global_as_u64: &'a dyn Fn(&mut T, GlobalIndex) -> u64,
349-
},
290+
/// The various callbacks provided here are used to drive the smaller bits of
291+
/// memory initialization.
292+
pub trait InitMemory {
293+
/// Returns the size, in wasm pages, of the the memory specified. For
294+
/// compile-time purposes this would be the memory type's minimum size.
295+
fn memory_size_in_pages(&mut self, memory_index: MemoryIndex) -> u64;
296+
297+
/// Returns the value of the constant expression, as a `u64`. Note that
298+
/// this may involve zero-extending a 32-bit global to a 64-bit number. May
299+
/// return `None` to indicate that the expression involves a value which is
300+
/// not available yet.
301+
fn eval_offset(&mut self, memory_index: MemoryIndex, expr: &ConstExpr) -> Option<u64>;
302+
303+
/// A callback used to actually write data. This indicates that the
304+
/// specified memory must receive the specified range of data at the
305+
/// specified offset. This can return false on failure.
306+
fn write(&mut self, memory_index: MemoryIndex, init: &StaticMemoryInitializer) -> bool;
350307
}
351308

352309
/// Implementation styles for WebAssembly tables.

0 commit comments

Comments
 (0)