wasm/execution/interpreter_loop/table.rs
1use core::{num::NonZeroU64, ops::ControlFlow};
2
3use crate::{
4 assert_validated::UnwrapValidatedExt,
5 core::{
6 indices::{ElemIdx, TableIdx},
7 reader::types::opcode,
8 utils::ToUsizeExt,
9 },
10 execution::interpreter_loop::{
11 define_instruction_fn, elem_drop, table_init, Args, InterpreterLoopOutcome,
12 },
13 value::Ref,
14 TrapError, Value,
15};
16
17define_instruction_fn! {
18 table_get,
19 fuel_check = flat(opcode::TABLE_GET),
20 |Args {
21 store_inner,
22 modules,
23 resumable,
24 wasm,
25 current_module,
26 ..
27 }| {
28 // SAFETY: Validation guarantees there to be a valid table index
29 // next.
30 let table_idx = unsafe { TableIdx::read_unchecked(wasm) };
31 // SAFETY: The current module address must come from the current
32 // store, because it is the only parameter to this function that
33 // can contain module addresses. All stores guarantee all
34 // addresses in them to be valid within themselves.
35 let module = unsafe { modules.get(*current_module) };
36
37 // SAFETY: Validation guarantees the table index to be valid in
38 // the current module.
39 let table_addr = *unsafe { module.table_addrs.get(table_idx) };
40 // SAFETY: This table address was just read from the current
41 // store. Therefore, it is valid in the current store.
42 let tab = unsafe { store_inner.tables.get(table_addr) };
43
44 let i: i32 = resumable.stack.pop_value().try_into().unwrap_validated();
45
46 let val = tab
47 .elem
48 .get(i.cast_unsigned().into_usize())
49 .ok_or(TrapError::TableOrElementAccessOutOfBounds)?;
50
51 resumable.stack.push_value((*val).into())?;
52 trace!(
53 "Instruction: table.get '{}' [{}] -> [{}]",
54 table_idx,
55 i,
56 val
57 );
58 Ok(ControlFlow::Continue(()))
59 }
60}
61
62define_instruction_fn! {
63 table_set,
64 fuel_check = flat(opcode::TABLE_SET),
65 |Args {
66 store_inner,
67 modules,
68 resumable,
69 wasm,
70 current_module,
71 ..
72 }| {
73 // SAFETY: Validation guarantees there to be valid table index
74 // next.
75 let table_idx = unsafe { TableIdx::read_unchecked(wasm) };
76 // SAFETY: The current module address must come from the current
77 // store, because it is the only parameter to this function that
78 // can contain module addresses. All stores guarantee all
79 // addresses in them to be valid within themselves.
80 let module = unsafe { modules.get(*current_module) };
81
82 // SAFETY: Validation guarantees the table index to be valid in
83 // the current module.
84 let table_addr = *unsafe { module.table_addrs.get(table_idx) };
85 // SAFETY: This table address was just read from the current
86 // store. Therefore, it is valid in the current store.
87 let tab = unsafe { store_inner.tables.get_mut(table_addr) };
88
89 let val: Ref = resumable.stack.pop_value().try_into().unwrap_validated();
90 let i: i32 = resumable.stack.pop_value().try_into().unwrap_validated();
91
92 tab.elem
93 .get_mut(i.cast_unsigned().into_usize())
94 .ok_or(TrapError::TableOrElementAccessOutOfBounds)
95 .map(|r| *r = val)?;
96 trace!(
97 "Instruction: table.set '{}' [{} {}] -> []",
98 table_idx,
99 i,
100 val
101 );
102 Ok(ControlFlow::Continue(()))
103 }
104}
105
106define_instruction_fn! {
107 table_size,
108 fuel_check = flat_fc(opcode::fc_extensions::TABLE_SIZE),
109 |Args {
110 wasm,
111 resumable,
112 modules,
113 current_module,
114 store_inner,
115 ..
116 }| {
117 // SAFETY: Validation guarantees there to be valid table
118 // index next.
119 let table_idx = unsafe { TableIdx::read_unchecked(wasm) };
120
121 // SAFETY: The current module address must come from the current
122 // store, because it is the only parameter to this function that
123 // can contain module addresses. All stores guarantee all
124 // addresses in them to be valid within themselves.
125 let module = unsafe { modules.get(*current_module) };
126
127 // SAFETY: Validation guarantees the table index to be
128 // valid in the current module.
129 let table_addr = *unsafe { module.table_addrs.get(table_idx) };
130 // SAFETY: This table address was just read from the
131 // current store. Therefore, it is valid in the current
132 // store.
133 let tab = unsafe { store_inner.tables.get_mut(table_addr) };
134
135 let sz = tab.elem.len() as u32;
136
137 resumable.stack.push_value(Value::I32(sz))?;
138
139 trace!("Instruction: table.size '{}' [] -> [{}]", table_idx, sz);
140 Ok(ControlFlow::Continue(()))
141 }
142}
143
144define_instruction_fn! {
145 table_grow,
146 fuel_check = omit,
147 |Args {
148 resumable,
149 wasm,
150 modules,
151 current_module,
152 store_inner,
153 ..
154 }| {
155 // SAFETY: Validation guarantees there to be a valid
156 // table index next.
157 let table_idx = unsafe { TableIdx::read_unchecked(wasm) };
158
159 // SAFETY: The current module address must come from the current
160 // store, because it is the only parameter to this function that
161 // can contain module addresses. All stores guarantee all
162 // addresses in them to be valid within themselves.
163 let module = unsafe { modules.get(*current_module) };
164
165 // SAFETY: Validation guarantees the table index to be
166 // valid in the current module.
167 let table_addr = *unsafe { module.table_addrs.get(table_idx) };
168 // SAFETY: This table address was just read from the
169 // current store. Therefore, it is valid in the current
170 // store.
171 let tab = unsafe { store_inner.tables.get_mut(table_addr) };
172
173 let sz = tab.elem.len() as u32;
174
175 let n: u32 = resumable.stack.pop_value().try_into().unwrap_validated();
176 let cost = T::get_fc_extension_flat_cost(opcode::fc_extensions::TABLE_GROW)
177 + u64::from(n)
178 * T::get_fc_extension_cost_per_element(opcode::fc_extensions::TABLE_GROW);
179 if let Some(fuel) = &mut resumable.maybe_fuel {
180 if *fuel >= cost {
181 *fuel -= cost;
182 } else {
183 resumable.stack.push_value(Value::I32(n)).unwrap_validated(); // we are pushing back what was just popped, this can't panic.
184 return Ok(ControlFlow::Break(InterpreterLoopOutcome::OutOfFuel {
185 required_fuel: NonZeroU64::new(cost - *fuel).expect(
186 "the last check guarantees that the current fuel is smaller than cost",
187 ),
188 }));
189 }
190 }
191
192 let val: Ref = resumable.stack.pop_value().try_into().unwrap_validated();
193
194 // TODO this instruction is non-deterministic w.r.t. spec, and can fail if the embedder wills it.
195 // for now we execute it always according to the following match expr.
196 // if the grow operation fails, err := Value::I32(2^32-1) is pushed to the resumable.stack per spec
197 match tab.grow(n, val) {
198 Ok(_) => {
199 resumable.stack.push_value(Value::I32(sz))?;
200 }
201 Err(_) => {
202 resumable.stack.push_value(Value::I32(u32::MAX))?;
203 }
204 }
205 Ok(ControlFlow::Continue(()))
206 }
207}
208
209define_instruction_fn! {
210 table_fill,
211 fuel_check = omit,
212 |Args {
213 resumable,
214 wasm,
215 modules,
216 current_module,
217 store_inner,
218 ..
219 }| {
220 // SAFETY: Validation guarantees there to be a valid
221 // table index next.
222 let table_idx = unsafe { TableIdx::read_unchecked(wasm) };
223
224 // SAFETY: The current module address must come from the current
225 // store, because it is the only parameter to this function that
226 // can contain module addresses. All stores guarantee all
227 // addresses in them to be valid within themselves.
228 let module = unsafe { modules.get(*current_module) };
229
230 // SAFETY: Validation guarantees the table index to be
231 // valid in the current module.
232 let table_addr = *unsafe { module.table_addrs.get(table_idx) };
233 // SAFETY: This table address was just read from the
234 // current store. Therefore, it is valid in the current
235 // store.
236 let tab = unsafe { store_inner.tables.get_mut(table_addr) };
237
238 let len: u32 = resumable.stack.pop_value().try_into().unwrap_validated();
239 let cost = T::get_fc_extension_flat_cost(opcode::fc_extensions::TABLE_FILL)
240 + u64::from(len)
241 * T::get_fc_extension_cost_per_element(opcode::fc_extensions::TABLE_FILL);
242 if let Some(fuel) = &mut resumable.maybe_fuel {
243 if *fuel >= cost {
244 *fuel -= cost;
245 } else {
246 resumable
247 .stack
248 .push_value(Value::I32(len))
249 .unwrap_validated(); // we are pushing back what was just popped, this can't panic.
250 return Ok(ControlFlow::Break(InterpreterLoopOutcome::OutOfFuel {
251 required_fuel: NonZeroU64::new(cost - *fuel).expect(
252 "the last check guarantees that the current fuel is smaller than cost",
253 ),
254 }));
255 }
256 }
257
258 let val: Ref = resumable.stack.pop_value().try_into().unwrap_validated();
259 let dst: u32 = resumable.stack.pop_value().try_into().unwrap_validated();
260
261 let end = (dst.into_usize())
262 .checked_add(len.into_usize())
263 .ok_or(TrapError::TableOrElementAccessOutOfBounds)?;
264
265 tab.elem
266 .get_mut(dst.into_usize()..end)
267 .ok_or(TrapError::TableOrElementAccessOutOfBounds)?
268 .fill(val);
269
270 trace!(
271 "Instruction table.fill '{}' [{} {} {}] -> []",
272 table_idx,
273 dst,
274 val,
275 len
276 );
277 Ok(ControlFlow::Continue(()))
278 }
279}
280
281// https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-table-mathsf-table-copy-x-y
282define_instruction_fn! {
283 table_copy,
284 fuel_check = omit,
285 |Args {
286 resumable,
287 wasm,
288 modules,
289 current_module,
290 store_inner,
291 ..
292 }| {
293 // SAFETY: Validation guarantees there to be a valid
294 // table index next.
295 let table_x_idx = unsafe { TableIdx::read_unchecked(wasm) };
296 // SAFETY: Validation guarantees there to be a valid
297 // table index next.
298 let table_y_idx = unsafe { TableIdx::read_unchecked(wasm) };
299
300 // SAFETY: The current module address must come from the current
301 // store, because it is the only parameter to this function that
302 // can contain module addresses. All stores guarantee all
303 // addresses in them to be valid within themselves.
304 let module = unsafe { modules.get(*current_module) };
305
306 // SAFETY: Validation guarantees the table index to be
307 // valid in the current module.
308 let table_addr_x = *unsafe { module.table_addrs.get(table_x_idx) };
309 // SAFETY: Validation guarantees the table index to be
310 // valid in the current module.
311 let table_addr_y = *unsafe { module.table_addrs.get(table_y_idx) };
312
313 // SAFETY: This table address was just read from the
314 // current store. Therefore, it is valid in the current
315 // store.
316 let tab_x_elem_len = unsafe { store_inner.tables.get(table_addr_x) }.elem.len();
317 // SAFETY: This table address was just read from the
318 // current store. Therefore, it is valid in the current
319 // store.
320 let tab_y_elem_len = unsafe { store_inner.tables.get(table_addr_y) }.elem.len();
321
322 let n: u32 = resumable.stack.pop_value().try_into().unwrap_validated(); // size
323 let cost = T::get_fc_extension_flat_cost(opcode::fc_extensions::TABLE_COPY)
324 + u64::from(n)
325 * T::get_fc_extension_cost_per_element(opcode::fc_extensions::TABLE_COPY);
326 if let Some(fuel) = &mut resumable.maybe_fuel {
327 if *fuel >= cost {
328 *fuel -= cost;
329 } else {
330 resumable.stack.push_value(Value::I32(n)).unwrap_validated(); // we are pushing back what was just popped, this can't panic.
331 return Ok(ControlFlow::Break(InterpreterLoopOutcome::OutOfFuel {
332 required_fuel: NonZeroU64::new(cost - *fuel).expect(
333 "the last check guarantees that the current fuel is smaller than cost",
334 ),
335 }));
336 }
337 }
338
339 let s: u32 = resumable.stack.pop_value().try_into().unwrap_validated(); // source
340 let d: u32 = resumable.stack.pop_value().try_into().unwrap_validated(); // destination
341
342 let src_res = match s.checked_add(n) {
343 Some(res) => {
344 if res > tab_y_elem_len as u32 {
345 return Err(TrapError::TableOrElementAccessOutOfBounds.into());
346 } else {
347 res.into_usize()
348 }
349 }
350 _ => return Err(TrapError::TableOrElementAccessOutOfBounds.into()),
351 };
352
353 let dst_res = match d.checked_add(n) {
354 Some(res) => {
355 if res > tab_x_elem_len as u32 {
356 return Err(TrapError::TableOrElementAccessOutOfBounds.into());
357 } else {
358 res.into_usize()
359 }
360 }
361 _ => return Err(TrapError::TableOrElementAccessOutOfBounds.into()),
362 };
363
364 if table_addr_x == table_addr_y {
365 // SAFETY: This table address was just read from the
366 // current store. Therefore, it is valid in the
367 // current store.
368 let table = unsafe { store_inner.tables.get_mut(table_addr_x) };
369
370 table.elem.copy_within(s as usize..src_res, d as usize);
371 } else {
372 let dst_addr = table_addr_x;
373 let src_addr = table_addr_y;
374
375 // SAFETY: These table addresses were just read from
376 // the current store. Therefore, they are valid in
377 // the current store.
378 let (src_table, dst_table) =
379 unsafe { store_inner.tables.get_two_mut(src_addr, dst_addr) }
380 .expect("both addrs to never be equal");
381
382 dst_table.elem[d.into_usize()..dst_res]
383 .copy_from_slice(&src_table.elem[s.into_usize()..src_res]);
384 }
385
386 trace!(
387 "Instruction: table.copy '{}' '{}' [{} {} {}] -> []",
388 table_x_idx,
389 table_y_idx,
390 d,
391 s,
392 n
393 );
394 Ok(ControlFlow::Continue(()))
395 }
396}
397
398// https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-table-mathsf-table-init-x-y
399// https://webassembly.github.io/spec/core/binary/instructions.html#table-instructions
400// in binary format it seems that elemidx is first ???????
401// this is ONLY for passive elements
402define_instruction_fn! {
403 table_init_fn,
404 fuel_check = omit,
405 |Args {
406 resumable,
407 wasm,
408 store_inner,
409 modules,
410 current_module,
411 ..
412 }| {
413 // SAFETY: Validation guarantees there to be a valid
414 // element index next.
415 let elem_idx = unsafe { ElemIdx::read_unchecked(wasm) };
416 // SAFETY: Validation guarantees there to be a valid
417 // table index next.
418 let table_idx = unsafe { TableIdx::read_unchecked(wasm) };
419
420 let n: u32 = resumable.stack.pop_value().try_into().unwrap_validated(); // size
421 let cost = T::get_fc_extension_flat_cost(opcode::fc_extensions::TABLE_INIT)
422 + u64::from(n)
423 * T::get_fc_extension_cost_per_element(opcode::fc_extensions::TABLE_INIT);
424 if let Some(fuel) = &mut resumable.maybe_fuel {
425 if *fuel >= cost {
426 *fuel -= cost;
427 } else {
428 resumable.stack.push_value(Value::I32(n)).unwrap_validated(); // we are pushing back what was just popped, this can't panic.
429 return Ok(ControlFlow::Break(InterpreterLoopOutcome::OutOfFuel {
430 required_fuel: NonZeroU64::new(cost - *fuel).expect(
431 "the last check guarantees that the current fuel is smaller than cost",
432 ),
433 }));
434 }
435 }
436
437 let s: i32 = resumable.stack.pop_value().try_into().unwrap_validated(); // offset
438 let d: i32 = resumable.stack.pop_value().try_into().unwrap_validated(); // dst
439
440 // SAFETY: All requirements are met:
441 // 1. The current module address must come from the
442 // current store, because it is the only parameter to
443 // this function that can contain module addresses. All
444 // stores guarantee all addresses in them to be valid
445 // within themselves.
446 // 2. Validation guarantees the table index to be valid
447 // in the current module instance.
448 // 3./5. The table/element addresses are valid for a
449 // similar reason that the module address is valid:
450 // they are stored in the current module instance,
451 // which is also part of the current store.
452 // 4. Validation guarantees the element index to be
453 // valid in the current module instance.
454 unsafe {
455 table_init(
456 modules,
457 &mut store_inner.tables,
458 &store_inner.elements,
459 *current_module,
460 elem_idx,
461 table_idx,
462 n,
463 s,
464 d,
465 )?
466 };
467 Ok(ControlFlow::Continue(()))
468 }
469}
470
471define_instruction_fn! {
472 elem_drop_fn,
473 fuel_check = flat_fc(opcode::fc_extensions::ELEM_DROP),
474 |Args {
475 wasm,
476 modules,
477 current_module,
478 store_inner,
479 ..
480 }| {
481 // SAFETY: Validation guarantees there a valid element
482 // index next.
483 let elem_idx = unsafe { ElemIdx::read_unchecked(wasm) };
484
485 // SAFETY: All requirements are met:
486 // 1. The current module address must come from the
487 // current store, because it is the only parameter to
488 // this function that can contain module addresses. All
489 // stores guarantee all addresses in them to be valid
490 // within themselves.
491 // 2. Validation guarantees the element index to be
492 // valid in the current module instance.
493 // 3. The element address is valid for a similar reason
494 // that the module address is valid: it is stored in the
495 // current module instance, which is also part of the
496 // current store.
497 unsafe {
498 elem_drop(
499 modules,
500 &mut store_inner.elements,
501 *current_module,
502 elem_idx,
503 );
504 }
505 Ok(ControlFlow::Continue(()))
506 }
507}