1 use cranelift_codegen::ir::BlockArg;
2 use itertools::{Either, Itertools};
3
4 use crate::trap::TranslateTrap;
5 use cranelift_codegen::ir::condcodes::*;
6 use cranelift_codegen::ir::types::*;
7 use cranelift_codegen::ir::{self, MemFlags};
8 use cranelift_codegen::ir::{Block, BlockCall, InstBuilder, JumpTableData};
9 use cranelift_frontend::FunctionBuilder;
10 use wasmtime_environ::{PtrSize, TagIndex, TypeIndex, WasmResult, WasmValType, wasm_unsupported};
11
control_context_size(triple: &target_lexicon::Triple) -> WasmResult<u8>12 fn control_context_size(triple: &target_lexicon::Triple) -> WasmResult<u8> {
13 match (triple.architecture, triple.operating_system) {
14 (target_lexicon::Architecture::X86_64, target_lexicon::OperatingSystem::Linux) => Ok(24),
15 _ => Err(wasm_unsupported!(
16 "stack switching not supported on {triple}"
17 )),
18 }
19 }
20
21 use super::control_effect::ControlEffect;
22 use super::fatpointer;
23
24 /// This module contains compile-time counterparts to types defined elsewhere.
25 pub(crate) mod stack_switching_helpers {
26 use core::marker::PhantomData;
27 use cranelift_codegen::ir;
28 use cranelift_codegen::ir::InstBuilder;
29 use cranelift_codegen::ir::condcodes::IntCC;
30 use cranelift_codegen::ir::types::*;
31 use cranelift_codegen::ir::{StackSlot, StackSlotKind::*};
32 use cranelift_frontend::FunctionBuilder;
33 use wasmtime_environ::PtrSize;
34
35 /// Provides information about the layout of a type when it is used as an
36 /// element in a host array. This is used for `VMHostArrayRef`.
37 pub(crate) trait VMHostArrayEntry {
38 /// Returns `(align, size)` in bytes.
vmhostarray_entry_layout<P: wasmtime_environ::PtrSize>(p: &P) -> (u8, u32)39 fn vmhostarray_entry_layout<P: wasmtime_environ::PtrSize>(p: &P) -> (u8, u32);
40 }
41
42 impl VMHostArrayEntry for u128 {
vmhostarray_entry_layout<P: wasmtime_environ::PtrSize>(_p: &P) -> (u8, u32)43 fn vmhostarray_entry_layout<P: wasmtime_environ::PtrSize>(_p: &P) -> (u8, u32) {
44 (16, 16)
45 }
46 }
47
48 impl<T> VMHostArrayEntry for *mut T {
vmhostarray_entry_layout<P: wasmtime_environ::PtrSize>(p: &P) -> (u8, u32)49 fn vmhostarray_entry_layout<P: wasmtime_environ::PtrSize>(p: &P) -> (u8, u32) {
50 (p.size(), p.size().into())
51 }
52 }
53
54 #[derive(Copy, Clone)]
55 pub struct VMContRef {
56 pub address: ir::Value,
57 }
58
59 #[derive(Copy, Clone)]
60 pub struct VMHostArrayRef<T> {
61 /// Address of the VMHostArray we are referencing
62 address: ir::Value,
63
64 /// The type parameter T is never used in the fields above. We still
65 /// want to have it for consistency with
66 /// `wasmtime_environ::Vector` and to use it in the associated
67 /// functions.
68 phantom: PhantomData<T>,
69 }
70
71 pub type VMPayloads = VMHostArrayRef<u128>;
72
73 // Actually a vector of *mut VMTagDefinition
74 pub type VMHandlerList = VMHostArrayRef<*mut u8>;
75
76 /// Compile-time representation of wasmtime_environ::VMStackChain,
77 /// consisting of two `ir::Value`s.
78 pub struct VMStackChain {
79 discriminant: ir::Value,
80 payload: ir::Value,
81 }
82
83 pub struct VMCommonStackInformation {
84 pub address: ir::Value,
85 }
86
87 /// Compile-time representation of `crate::runtime::vm::stack::VMContinuationStack`.
88 pub struct VMContinuationStack {
89 /// This is NOT the "top of stack" address of the stack itself. In line
90 /// with how the (runtime) `FiberStack` type works, this is a pointer to
91 /// the TOS address.
92 tos_ptr: ir::Value,
93 }
94
95 impl VMContRef {
new(address: ir::Value) -> VMContRef96 pub fn new(address: ir::Value) -> VMContRef {
97 VMContRef { address }
98 }
99
args<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> VMPayloads100 pub fn args<'a>(
101 &self,
102 env: &mut crate::func_environ::FuncEnvironment<'a>,
103 builder: &mut FunctionBuilder,
104 ) -> VMPayloads {
105 let offset: i64 = env.offsets.ptr.vmcontref_args().into();
106 let address = builder.ins().iadd_imm(self.address, offset);
107 VMPayloads::new(address)
108 }
109
values<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> VMPayloads110 pub fn values<'a>(
111 &self,
112 env: &mut crate::func_environ::FuncEnvironment<'a>,
113 builder: &mut FunctionBuilder,
114 ) -> VMPayloads {
115 let offset: i64 = env.offsets.ptr.vmcontref_values().into();
116 let address = builder.ins().iadd_imm(self.address, offset);
117 VMPayloads::new(address)
118 }
119
common_stack_information<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> VMCommonStackInformation120 pub fn common_stack_information<'a>(
121 &self,
122 env: &mut crate::func_environ::FuncEnvironment<'a>,
123 builder: &mut FunctionBuilder,
124 ) -> VMCommonStackInformation {
125 let offset: i64 = env.offsets.ptr.vmcontref_common_stack_information().into();
126 let address = builder.ins().iadd_imm(self.address, offset);
127 VMCommonStackInformation { address }
128 }
129
130 /// Stores the parent of this continuation, which may either be another
131 /// continuation or the initial stack. It is therefore represented as a
132 /// `VMStackChain` element.
set_parent_stack_chain<'a>( &mut self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, new_stack_chain: &VMStackChain, )133 pub fn set_parent_stack_chain<'a>(
134 &mut self,
135 env: &mut crate::func_environ::FuncEnvironment<'a>,
136 builder: &mut FunctionBuilder,
137 new_stack_chain: &VMStackChain,
138 ) {
139 let offset = env.offsets.ptr.vmcontref_parent_chain().into();
140 new_stack_chain.store(env, builder, self.address, offset)
141 }
142
143 /// Loads the parent of this continuation, which may either be another
144 /// continuation or the initial stack. It is therefore represented as a
145 /// `VMStackChain` element.
get_parent_stack_chain<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> VMStackChain146 pub fn get_parent_stack_chain<'a>(
147 &self,
148 env: &mut crate::func_environ::FuncEnvironment<'a>,
149 builder: &mut FunctionBuilder,
150 ) -> VMStackChain {
151 let offset = env.offsets.ptr.vmcontref_parent_chain().into();
152 VMStackChain::load(env, builder, self.address, offset, env.pointer_type())
153 }
154
set_last_ancestor<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, last_ancestor: ir::Value, )155 pub fn set_last_ancestor<'a>(
156 &self,
157 env: &mut crate::func_environ::FuncEnvironment<'a>,
158 builder: &mut FunctionBuilder,
159 last_ancestor: ir::Value,
160 ) {
161 let offset: i32 = env.offsets.ptr.vmcontref_last_ancestor().into();
162 let mem_flags = ir::MemFlags::trusted();
163 builder
164 .ins()
165 .store(mem_flags, last_ancestor, self.address, offset);
166 }
167
get_last_ancestor<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value168 pub fn get_last_ancestor<'a>(
169 &self,
170 env: &mut crate::func_environ::FuncEnvironment<'a>,
171 builder: &mut FunctionBuilder,
172 ) -> ir::Value {
173 let offset: i32 = env.offsets.ptr.vmcontref_last_ancestor().into();
174 let mem_flags = ir::MemFlags::trusted();
175 builder
176 .ins()
177 .load(env.pointer_type(), mem_flags, self.address, offset)
178 }
179
180 /// Gets the revision counter the a given continuation
181 /// reference.
get_revision<'a>( &mut self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value182 pub fn get_revision<'a>(
183 &mut self,
184 env: &mut crate::func_environ::FuncEnvironment<'a>,
185 builder: &mut FunctionBuilder,
186 ) -> ir::Value {
187 let mem_flags = ir::MemFlags::trusted();
188 let offset: i32 = env.offsets.ptr.vmcontref_revision().into();
189 let revision = builder.ins().load(I64, mem_flags, self.address, offset);
190 revision
191 }
192
193 /// Sets the revision counter on the given continuation
194 /// reference to `revision + 1`.
195
incr_revision<'a>( &mut self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, revision: ir::Value, ) -> ir::Value196 pub fn incr_revision<'a>(
197 &mut self,
198 env: &mut crate::func_environ::FuncEnvironment<'a>,
199 builder: &mut FunctionBuilder,
200 revision: ir::Value,
201 ) -> ir::Value {
202 let mem_flags = ir::MemFlags::trusted();
203 let offset: i32 = env.offsets.ptr.vmcontref_revision().into();
204 let revision_plus1 = builder.ins().iadd_imm(revision, 1);
205 builder
206 .ins()
207 .store(mem_flags, revision_plus1, self.address, offset);
208 revision_plus1
209 }
210
get_fiber_stack<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> VMContinuationStack211 pub fn get_fiber_stack<'a>(
212 &self,
213 env: &mut crate::func_environ::FuncEnvironment<'a>,
214 builder: &mut FunctionBuilder,
215 ) -> VMContinuationStack {
216 // The top of stack field is stored at offset 0 of the `FiberStack`.
217 let offset: i64 = env.offsets.ptr.vmcontref_stack().into();
218 let fiber_stack_top_of_stack_ptr = builder.ins().iadd_imm(self.address, offset);
219 VMContinuationStack::new(fiber_stack_top_of_stack_ptr)
220 }
221 }
222
223 impl<T: VMHostArrayEntry> VMHostArrayRef<T> {
new(address: ir::Value) -> Self224 pub(crate) fn new(address: ir::Value) -> Self {
225 Self {
226 address,
227 phantom: PhantomData::default(),
228 }
229 }
230
get(&self, builder: &mut FunctionBuilder, ty: ir::Type, offset: i32) -> ir::Value231 fn get(&self, builder: &mut FunctionBuilder, ty: ir::Type, offset: i32) -> ir::Value {
232 let mem_flags = ir::MemFlags::trusted();
233 builder.ins().load(ty, mem_flags, self.address, offset)
234 }
235
set<U>(&self, builder: &mut FunctionBuilder, offset: i32, value: ir::Value)236 fn set<U>(&self, builder: &mut FunctionBuilder, offset: i32, value: ir::Value) {
237 debug_assert_eq!(
238 builder.func.dfg.value_type(value),
239 Type::int_with_byte_size(u16::try_from(core::mem::size_of::<U>()).unwrap())
240 .unwrap()
241 );
242 let mem_flags = ir::MemFlags::trusted();
243 builder.ins().store(mem_flags, value, self.address, offset);
244 }
245
get_data<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value246 pub fn get_data<'a>(
247 &self,
248 env: &mut crate::func_environ::FuncEnvironment<'a>,
249 builder: &mut FunctionBuilder,
250 ) -> ir::Value {
251 let offset = env.offsets.ptr.vmhostarray_data().into();
252 self.get(builder, env.pointer_type(), offset)
253 }
254
get_length<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value255 pub fn get_length<'a>(
256 &self,
257 env: &mut crate::func_environ::FuncEnvironment<'a>,
258 builder: &mut FunctionBuilder,
259 ) -> ir::Value {
260 // Array length is stored as u32.
261 let offset = env.offsets.ptr.vmhostarray_length().into();
262 self.get(builder, I32, offset)
263 }
264
set_length<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, length: ir::Value, )265 fn set_length<'a>(
266 &self,
267 env: &mut crate::func_environ::FuncEnvironment<'a>,
268 builder: &mut FunctionBuilder,
269 length: ir::Value,
270 ) {
271 // Array length is stored as u32.
272 let offset = env.offsets.ptr.vmhostarray_length().into();
273 self.set::<u32>(builder, offset, length);
274 }
275
set_capacity<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, capacity: ir::Value, )276 fn set_capacity<'a>(
277 &self,
278 env: &mut crate::func_environ::FuncEnvironment<'a>,
279 builder: &mut FunctionBuilder,
280 capacity: ir::Value,
281 ) {
282 // Array capacity is stored as u32.
283 let offset = env.offsets.ptr.vmhostarray_capacity().into();
284 self.set::<u32>(builder, offset, capacity);
285 }
286
set_data<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, data: ir::Value, )287 fn set_data<'a>(
288 &self,
289 env: &mut crate::func_environ::FuncEnvironment<'a>,
290 builder: &mut FunctionBuilder,
291 data: ir::Value,
292 ) {
293 debug_assert_eq!(builder.func.dfg.value_type(data), env.pointer_type());
294 let offset: i32 = env.offsets.ptr.vmhostarray_data().into();
295 let mem_flags = ir::MemFlags::trusted();
296 builder.ins().store(mem_flags, data, self.address, offset);
297 }
298
299 /// Returns pointer to next empty slot in data buffer and marks the
300 /// subsequent `arg_count` slots as occupied.
occupy_next_slots<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, arg_count: i32, ) -> ir::Value301 pub fn occupy_next_slots<'a>(
302 &self,
303 env: &mut crate::func_environ::FuncEnvironment<'a>,
304 builder: &mut FunctionBuilder,
305 arg_count: i32,
306 ) -> ir::Value {
307 let data = self.get_data(env, builder);
308 let original_length = self.get_length(env, builder);
309 let new_length = builder
310 .ins()
311 .iadd_imm(original_length, i64::from(arg_count));
312 self.set_length(env, builder, new_length);
313
314 let (_align, entry_size) = T::vmhostarray_entry_layout(&env.offsets.ptr);
315 let original_length = builder.ins().uextend(I64, original_length);
316 let byte_offset = builder
317 .ins()
318 .imul_imm(original_length, i64::from(entry_size));
319 builder.ins().iadd(data, byte_offset)
320 }
321
allocate_or_reuse_stack_slot<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, required_capacity: u32, existing_slot: Option<StackSlot>, ) -> StackSlot322 pub fn allocate_or_reuse_stack_slot<'a>(
323 &self,
324 env: &mut crate::func_environ::FuncEnvironment<'a>,
325 builder: &mut FunctionBuilder,
326 required_capacity: u32,
327 existing_slot: Option<StackSlot>,
328 ) -> StackSlot {
329 let (align, entry_size) = T::vmhostarray_entry_layout(&env.offsets.ptr);
330 let required_size = required_capacity * entry_size;
331
332 match existing_slot {
333 Some(slot) if builder.func.sized_stack_slots[slot].size >= required_size => {
334 let slot_data = &builder.func.sized_stack_slots[slot];
335 debug_assert!(align <= slot_data.align_shift);
336 debug_assert_eq!(slot_data.kind, ExplicitSlot);
337 let existing_capacity = slot_data.size / entry_size;
338
339 let capacity_value = builder.ins().iconst(I32, i64::from(existing_capacity));
340 let existing_data = builder.ins().stack_addr(env.pointer_type(), slot, 0);
341
342 self.set_capacity(env, builder, capacity_value);
343 self.set_data(env, builder, existing_data);
344
345 slot
346 }
347 _ => {
348 let capacity_value = builder.ins().iconst(I32, i64::from(required_capacity));
349 let slot_size = ir::StackSlotData::new(
350 ir::StackSlotKind::ExplicitSlot,
351 required_size,
352 align,
353 );
354 let slot = builder.create_sized_stack_slot(slot_size);
355 let new_data = builder.ins().stack_addr(env.pointer_type(), slot, 0);
356
357 self.set_capacity(env, builder, capacity_value);
358 self.set_data(env, builder, new_data);
359
360 slot
361 }
362 }
363 }
364
365 /// Loads n entries from this Vector object, where n is the length of
366 /// `load_types`, which also gives the types of the values to load.
367 /// Loading starts at index 0 of the Vector object.
load_data_entries<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, load_types: &[ir::Type], ) -> Vec<ir::Value>368 pub fn load_data_entries<'a>(
369 &self,
370 env: &mut crate::func_environ::FuncEnvironment<'a>,
371 builder: &mut FunctionBuilder,
372 load_types: &[ir::Type],
373 ) -> Vec<ir::Value> {
374 let memflags = ir::MemFlags::trusted();
375
376 let data_start_pointer = self.get_data(env, builder);
377 let mut values = vec![];
378 let mut offset = 0;
379 let (_align, entry_size) = T::vmhostarray_entry_layout(&env.offsets.ptr);
380 for valtype in load_types {
381 let val = builder
382 .ins()
383 .load(*valtype, memflags, data_start_pointer, offset);
384 values.push(val);
385 offset += i32::try_from(entry_size).unwrap();
386 }
387 values
388 }
389
390 /// Stores the given `values` in this Vector object, beginning at
391 /// index 0. This expects the Vector object to be empty (i.e., current
392 /// length is 0), and to be of sufficient capacity to store |`values`|
393 /// entries.
store_data_entries<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, values: &[ir::Value], )394 pub fn store_data_entries<'a>(
395 &self,
396 env: &mut crate::func_environ::FuncEnvironment<'a>,
397 builder: &mut FunctionBuilder,
398 values: &[ir::Value],
399 ) {
400 let store_count = builder
401 .ins()
402 .iconst(I32, i64::try_from(values.len()).unwrap());
403
404 let (_align, entry_size) = T::vmhostarray_entry_layout(&env.offsets.ptr);
405
406 debug_assert!(values.iter().all(|val| {
407 let ty = builder.func.dfg.value_type(*val);
408 let size = ty.bytes();
409 size <= entry_size
410 }));
411
412 let memflags = ir::MemFlags::trusted();
413
414 let data_start_pointer = self.get_data(env, builder);
415
416 let mut offset = 0;
417 for value in values {
418 builder
419 .ins()
420 .store(memflags, *value, data_start_pointer, offset);
421 offset += i32::try_from(entry_size).unwrap();
422 }
423
424 self.set_length(env, builder, store_count);
425 }
426
clear<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, discard_buffer: bool, )427 pub fn clear<'a>(
428 &self,
429 env: &mut crate::func_environ::FuncEnvironment<'a>,
430 builder: &mut FunctionBuilder,
431 discard_buffer: bool,
432 ) {
433 let zero32 = builder.ins().iconst(I32, 0);
434 self.set_length(env, builder, zero32);
435
436 if discard_buffer {
437 let zero32 = builder.ins().iconst(I32, 0);
438 self.set_capacity(env, builder, zero32);
439
440 let zero_ptr = builder.ins().iconst(env.pointer_type(), 0);
441 self.set_data(env, builder, zero_ptr);
442 }
443 }
444 }
445
446 impl VMStackChain {
447 /// Creates a `Self` corresponding to `VMStackChain::Continuation(contref)`.
from_continuation<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, contref: ir::Value, ) -> VMStackChain448 pub fn from_continuation<'a>(
449 env: &mut crate::func_environ::FuncEnvironment<'a>,
450 builder: &mut FunctionBuilder,
451 contref: ir::Value,
452 ) -> VMStackChain {
453 debug_assert_eq!(
454 env.offsets.ptr.size_of_vmstack_chain(),
455 2 * env.offsets.ptr.size()
456 );
457 let discriminant = wasmtime_environ::STACK_CHAIN_CONTINUATION_DISCRIMINANT;
458 let discriminant = builder
459 .ins()
460 .iconst(env.pointer_type(), i64::try_from(discriminant).unwrap());
461 VMStackChain {
462 discriminant,
463 payload: contref,
464 }
465 }
466
467 /// Creates a `Self` corresponding to `VMStackChain::Absent`.
absent<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> VMStackChain468 pub fn absent<'a>(
469 env: &mut crate::func_environ::FuncEnvironment<'a>,
470 builder: &mut FunctionBuilder,
471 ) -> VMStackChain {
472 debug_assert_eq!(
473 env.offsets.ptr.size_of_vmstack_chain(),
474 2 * env.offsets.ptr.size()
475 );
476 let discriminant = wasmtime_environ::STACK_CHAIN_ABSENT_DISCRIMINANT;
477 let discriminant = builder
478 .ins()
479 .iconst(env.pointer_type(), i64::try_from(discriminant).unwrap());
480 let zero_filler = builder.ins().iconst(env.pointer_type(), 0i64);
481 VMStackChain {
482 discriminant,
483 payload: zero_filler,
484 }
485 }
486
is_initial_stack<'a>( &self, _env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value487 pub fn is_initial_stack<'a>(
488 &self,
489 _env: &mut crate::func_environ::FuncEnvironment<'a>,
490 builder: &mut FunctionBuilder,
491 ) -> ir::Value {
492 builder.ins().icmp_imm(
493 IntCC::Equal,
494 self.discriminant,
495 i64::try_from(wasmtime_environ::STACK_CHAIN_INITIAL_STACK_DISCRIMINANT).unwrap(),
496 )
497 }
498
499 /// Return the two raw `ir::Value`s that represent this VMStackChain.
to_raw_parts(&self) -> [ir::Value; 2]500 pub fn to_raw_parts(&self) -> [ir::Value; 2] {
501 [self.discriminant, self.payload]
502 }
503
504 /// Construct a `Self` from two raw `ir::Value`s.
from_raw_parts(raw_data: [ir::Value; 2]) -> VMStackChain505 pub fn from_raw_parts(raw_data: [ir::Value; 2]) -> VMStackChain {
506 VMStackChain {
507 discriminant: raw_data[0],
508 payload: raw_data[1],
509 }
510 }
511
512 /// Load a `VMStackChain` object from the given address.
load<'a>( _env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, pointer: ir::Value, initial_offset: i32, pointer_type: ir::Type, ) -> VMStackChain513 pub fn load<'a>(
514 _env: &mut crate::func_environ::FuncEnvironment<'a>,
515 builder: &mut FunctionBuilder,
516 pointer: ir::Value,
517 initial_offset: i32,
518 pointer_type: ir::Type,
519 ) -> VMStackChain {
520 let memflags = ir::MemFlags::trusted();
521 let mut offset = initial_offset;
522 let mut data = vec![];
523 for _ in 0..2 {
524 data.push(builder.ins().load(pointer_type, memflags, pointer, offset));
525 offset += i32::try_from(pointer_type.bytes()).unwrap();
526 }
527 let data = <[ir::Value; 2]>::try_from(data).unwrap();
528 Self::from_raw_parts(data)
529 }
530
531 /// Store this `VMStackChain` object at the given address.
store<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, target_pointer: ir::Value, initial_offset: i32, )532 pub fn store<'a>(
533 &self,
534 env: &mut crate::func_environ::FuncEnvironment<'a>,
535 builder: &mut FunctionBuilder,
536 target_pointer: ir::Value,
537 initial_offset: i32,
538 ) {
539 let memflags = ir::MemFlags::trusted();
540 let mut offset = initial_offset;
541 let data = self.to_raw_parts();
542
543 for value in data {
544 debug_assert_eq!(builder.func.dfg.value_type(value), env.pointer_type());
545 builder.ins().store(memflags, value, target_pointer, offset);
546 offset += i32::try_from(env.pointer_type().bytes()).unwrap();
547 }
548 }
549
550 /// Use this only if you've already checked that `self` corresponds to a `VMStackChain::Continuation`.
unchecked_get_continuation(&self) -> ir::Value551 pub fn unchecked_get_continuation(&self) -> ir::Value {
552 self.payload
553 }
554
555 /// Must only be called if `self` represents a `InitialStack` or
556 /// `Continuation` variant. Returns a pointer to the associated
557 /// `CommonStackInformation` object.
get_common_stack_information<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, _builder: &mut FunctionBuilder, ) -> VMCommonStackInformation558 pub fn get_common_stack_information<'a>(
559 &self,
560 env: &mut crate::func_environ::FuncEnvironment<'a>,
561 _builder: &mut FunctionBuilder,
562 ) -> VMCommonStackInformation {
563 // `self` corresponds to a VMStackChain::InitialStack or
564 // VMStackChain::Continuation.
565 // In both cases, the payload is a pointer.
566 let address = self.payload;
567
568 // `obj` is now a pointer to the beginning of either
569 // 1. A `VMContRef` struct (in the case of a
570 // VMStackChain::Continuation)
571 // 2. A CommonStackInformation struct (in the case of
572 // VMStackChain::InitialStack)
573 //
574 // Since a `VMContRef` starts with an (inlined) CommonStackInformation
575 // object at offset 0, we actually have in both cases that `ptr` is
576 // now the address of the beginning of a VMStackLimits object.
577 debug_assert_eq!(env.offsets.ptr.vmcontref_common_stack_information(), 0);
578 VMCommonStackInformation { address }
579 }
580 }
581
582 impl VMCommonStackInformation {
get_state_ptr<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value583 fn get_state_ptr<'a>(
584 &self,
585 env: &mut crate::func_environ::FuncEnvironment<'a>,
586 builder: &mut FunctionBuilder,
587 ) -> ir::Value {
588 let offset: i64 = env.offsets.ptr.vmcommon_stack_information_state().into();
589
590 builder.ins().iadd_imm(self.address, offset)
591 }
592
get_stack_limits_ptr<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value593 fn get_stack_limits_ptr<'a>(
594 &self,
595 env: &mut crate::func_environ::FuncEnvironment<'a>,
596 builder: &mut FunctionBuilder,
597 ) -> ir::Value {
598 let offset: i64 = env.offsets.ptr.vmcommon_stack_information_limits().into();
599
600 builder.ins().iadd_imm(self.address, offset)
601 }
602
load_state<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value603 fn load_state<'a>(
604 &self,
605 env: &mut crate::func_environ::FuncEnvironment<'a>,
606 builder: &mut FunctionBuilder,
607 ) -> ir::Value {
608 let mem_flags = ir::MemFlags::trusted();
609 let state_ptr = self.get_state_ptr(env, builder);
610
611 builder.ins().load(I32, mem_flags, state_ptr, 0)
612 }
613
set_state_no_payload<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, discriminant: u32, )614 fn set_state_no_payload<'a>(
615 &self,
616 env: &mut crate::func_environ::FuncEnvironment<'a>,
617 builder: &mut FunctionBuilder,
618 discriminant: u32,
619 ) {
620 let discriminant = builder.ins().iconst(I32, i64::from(discriminant));
621 let mem_flags = ir::MemFlags::trusted();
622 let state_ptr = self.get_state_ptr(env, builder);
623
624 builder.ins().store(mem_flags, discriminant, state_ptr, 0);
625 }
626
set_state_running<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, )627 pub fn set_state_running<'a>(
628 &self,
629 env: &mut crate::func_environ::FuncEnvironment<'a>,
630 builder: &mut FunctionBuilder,
631 ) {
632 let discriminant = wasmtime_environ::STACK_STATE_RUNNING_DISCRIMINANT;
633 self.set_state_no_payload(env, builder, discriminant);
634 }
635
set_state_parent<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, )636 pub fn set_state_parent<'a>(
637 &self,
638 env: &mut crate::func_environ::FuncEnvironment<'a>,
639 builder: &mut FunctionBuilder,
640 ) {
641 let discriminant = wasmtime_environ::STACK_STATE_PARENT_DISCRIMINANT;
642 self.set_state_no_payload(env, builder, discriminant);
643 }
644
set_state_returned<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, )645 pub fn set_state_returned<'a>(
646 &self,
647 env: &mut crate::func_environ::FuncEnvironment<'a>,
648 builder: &mut FunctionBuilder,
649 ) {
650 let discriminant = wasmtime_environ::STACK_STATE_RETURNED_DISCRIMINANT;
651 self.set_state_no_payload(env, builder, discriminant);
652 }
653
set_state_suspended<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, )654 pub fn set_state_suspended<'a>(
655 &self,
656 env: &mut crate::func_environ::FuncEnvironment<'a>,
657 builder: &mut FunctionBuilder,
658 ) {
659 let discriminant = wasmtime_environ::STACK_STATE_SUSPENDED_DISCRIMINANT;
660 self.set_state_no_payload(env, builder, discriminant);
661 }
662
663 /// Checks whether the `VMStackState` reflects that the stack has ever been
664 /// active (instead of just having been allocated, but never resumed).
was_invoked<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value665 pub fn was_invoked<'a>(
666 &self,
667 env: &mut crate::func_environ::FuncEnvironment<'a>,
668 builder: &mut FunctionBuilder,
669 ) -> ir::Value {
670 let actual_state = self.load_state(env, builder);
671 let allocated = wasmtime_environ::STACK_STATE_FRESH_DISCRIMINANT;
672 builder
673 .ins()
674 .icmp_imm(IntCC::NotEqual, actual_state, i64::from(allocated))
675 }
676
get_handler_list<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> VMHandlerList677 pub fn get_handler_list<'a>(
678 &self,
679 env: &mut crate::func_environ::FuncEnvironment<'a>,
680 builder: &mut FunctionBuilder,
681 ) -> VMHandlerList {
682 let offset: i64 = env.offsets.ptr.vmcommon_stack_information_handlers().into();
683 let address = builder.ins().iadd_imm(self.address, offset);
684 VMHandlerList::new(address)
685 }
686
get_first_switch_handler_index<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value687 pub fn get_first_switch_handler_index<'a>(
688 &self,
689 env: &mut crate::func_environ::FuncEnvironment<'a>,
690 builder: &mut FunctionBuilder,
691 ) -> ir::Value {
692 // Field first_switch_handler_index has type u32
693 let memflags = ir::MemFlags::trusted();
694 let offset: i32 = env
695 .offsets
696 .ptr
697 .vmcommon_stack_information_first_switch_handler_index()
698 .into();
699 builder.ins().load(I32, memflags, self.address, offset)
700 }
701
set_first_switch_handler_index<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, value: ir::Value, )702 pub fn set_first_switch_handler_index<'a>(
703 &self,
704 env: &mut crate::func_environ::FuncEnvironment<'a>,
705 builder: &mut FunctionBuilder,
706 value: ir::Value,
707 ) {
708 // Field first_switch_handler_index has type u32
709 let memflags = ir::MemFlags::trusted();
710 let offset: i32 = env
711 .offsets
712 .ptr
713 .vmcommon_stack_information_first_switch_handler_index()
714 .into();
715 builder.ins().store(memflags, value, self.address, offset);
716 }
717
718 /// Sets `last_wasm_entry_sp` and `stack_limit` fields in
719 /// `VMRuntimelimits` using the values from the `VMStackLimits` of this
720 /// object.
write_limits_to_vmcontext<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, vmruntime_limits_ptr: ir::Value, )721 pub fn write_limits_to_vmcontext<'a>(
722 &self,
723 env: &mut crate::func_environ::FuncEnvironment<'a>,
724 builder: &mut FunctionBuilder,
725 vmruntime_limits_ptr: ir::Value,
726 ) {
727 let stack_limits_ptr = self.get_stack_limits_ptr(env, builder);
728
729 let memflags = ir::MemFlags::trusted();
730
731 let mut copy_to_vm_runtime_limits = |our_offset, their_offset| {
732 let our_value = builder.ins().load(
733 env.pointer_type(),
734 memflags,
735 stack_limits_ptr,
736 i32::from(our_offset),
737 );
738 builder.ins().store(
739 memflags,
740 our_value,
741 vmruntime_limits_ptr,
742 i32::from(their_offset),
743 );
744 };
745
746 let pointer_size = u8::try_from(env.pointer_type().bytes()).unwrap();
747 let stack_limit_offset = env.offsets.ptr.vmstack_limits_stack_limit();
748 let last_wasm_entry_fp_offset = env.offsets.ptr.vmstack_limits_last_wasm_entry_fp();
749 copy_to_vm_runtime_limits(
750 stack_limit_offset,
751 pointer_size.vmstore_context_stack_limit(),
752 );
753 copy_to_vm_runtime_limits(
754 last_wasm_entry_fp_offset,
755 pointer_size.vmstore_context_last_wasm_entry_fp(),
756 );
757 }
758
759 /// Overwrites the `last_wasm_entry_fp` field of the `VMStackLimits`
760 /// object in the `VMStackLimits` of this object by loading the corresponding
761 /// field from the `VMRuntimeLimits`.
762 /// If `load_stack_limit` is true, we do the same for the `stack_limit`
763 /// field.
load_limits_from_vmcontext<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, vmruntime_limits_ptr: ir::Value, load_stack_limit: bool, )764 pub fn load_limits_from_vmcontext<'a>(
765 &self,
766 env: &mut crate::func_environ::FuncEnvironment<'a>,
767 builder: &mut FunctionBuilder,
768 vmruntime_limits_ptr: ir::Value,
769 load_stack_limit: bool,
770 ) {
771 let stack_limits_ptr = self.get_stack_limits_ptr(env, builder);
772
773 let memflags = ir::MemFlags::trusted();
774 let pointer_size = u8::try_from(env.pointer_type().bytes()).unwrap();
775
776 let mut copy = |runtime_limits_offset, stack_limits_offset| {
777 let from_vm_runtime_limits = builder.ins().load(
778 env.pointer_type(),
779 memflags,
780 vmruntime_limits_ptr,
781 runtime_limits_offset,
782 );
783 builder.ins().store(
784 memflags,
785 from_vm_runtime_limits,
786 stack_limits_ptr,
787 stack_limits_offset,
788 );
789 };
790
791 let last_wasm_entry_fp_offset = env.offsets.ptr.vmstack_limits_last_wasm_entry_fp();
792 copy(
793 pointer_size.vmstore_context_last_wasm_entry_fp(),
794 last_wasm_entry_fp_offset,
795 );
796
797 if load_stack_limit {
798 let stack_limit_offset = env.offsets.ptr.vmstack_limits_stack_limit();
799 copy(
800 pointer_size.vmstore_context_stack_limit(),
801 stack_limit_offset,
802 );
803 }
804 }
805 }
806
807 impl VMContinuationStack {
808 /// The parameter is NOT the "top of stack" address of the stack itself. In line
809 /// with how the (runtime) `FiberStack` type works, this is a pointer to
810 /// the TOS address.
new(tos_ptr: ir::Value) -> Self811 pub fn new(tos_ptr: ir::Value) -> Self {
812 Self { tos_ptr }
813 }
814
load_top_of_stack<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value815 fn load_top_of_stack<'a>(
816 &self,
817 env: &mut crate::func_environ::FuncEnvironment<'a>,
818 builder: &mut FunctionBuilder,
819 ) -> ir::Value {
820 let mem_flags = ir::MemFlags::trusted();
821 builder
822 .ins()
823 .load(env.pointer_type(), mem_flags, self.tos_ptr, 0)
824 }
825
826 /// Returns address of the control context stored in the stack memory,
827 /// as used by stack_switch instructions.
load_control_context<'a>( &self, env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, ) -> ir::Value828 pub fn load_control_context<'a>(
829 &self,
830 env: &mut crate::func_environ::FuncEnvironment<'a>,
831 builder: &mut FunctionBuilder,
832 ) -> ir::Value {
833 let tos = self.load_top_of_stack(env, builder);
834 // Control context begins 24 bytes below top of stack (see unix.rs)
835 builder.ins().iadd_imm(tos, -0x18)
836 }
837 }
838 }
839
840 use helpers::VMStackChain;
841 use stack_switching_helpers as helpers;
842
843 /// Stores the given arguments in the appropriate `VMPayloads` object in the
844 /// continuation. If the continuation was never invoked, use the `args` object.
845 /// Otherwise, use the `values` object.
vmcontref_store_payloads<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, values: &[ir::Value], contref: ir::Value, )846 pub(crate) fn vmcontref_store_payloads<'a>(
847 env: &mut crate::func_environ::FuncEnvironment<'a>,
848 builder: &mut FunctionBuilder,
849 values: &[ir::Value],
850 contref: ir::Value,
851 ) {
852 let count =
853 i32::try_from(values.len()).expect("Number of stack switching payloads should fit in i32");
854 if values.len() > 0 {
855 let use_args_block = builder.create_block();
856 let use_payloads_block = builder.create_block();
857 let store_data_block = builder.create_block();
858 builder.append_block_param(store_data_block, env.pointer_type());
859
860 let co = helpers::VMContRef::new(contref);
861 let csi = co.common_stack_information(env, builder);
862 let was_invoked = csi.was_invoked(env, builder);
863 builder
864 .ins()
865 .brif(was_invoked, use_payloads_block, &[], use_args_block, &[]);
866
867 {
868 builder.switch_to_block(use_args_block);
869 builder.seal_block(use_args_block);
870
871 let args = co.args(env, builder);
872 let ptr = args.occupy_next_slots(env, builder, count);
873
874 builder
875 .ins()
876 .jump(store_data_block, &[BlockArg::Value(ptr)]);
877 }
878
879 {
880 builder.switch_to_block(use_payloads_block);
881 builder.seal_block(use_payloads_block);
882
883 let payloads = co.values(env, builder);
884
885 // This also checks that the buffer is large enough to hold
886 // `values.len()` more elements.
887 let ptr = payloads.occupy_next_slots(env, builder, count);
888 builder
889 .ins()
890 .jump(store_data_block, &[BlockArg::Value(ptr)]);
891 }
892
893 {
894 builder.switch_to_block(store_data_block);
895 builder.seal_block(store_data_block);
896
897 let ptr = builder.block_params(store_data_block)[0];
898
899 // Store the values.
900 let memflags = ir::MemFlags::trusted();
901 let mut offset = 0;
902 for value in values {
903 builder.ins().store(memflags, *value, ptr, offset);
904 offset += i32::from(env.offsets.ptr.maximum_value_size());
905 }
906 }
907 }
908 }
909
tag_address<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, index: u32, ) -> ir::Value910 pub(crate) fn tag_address<'a>(
911 env: &mut crate::func_environ::FuncEnvironment<'a>,
912 builder: &mut FunctionBuilder,
913 index: u32,
914 ) -> ir::Value {
915 let vmctx = env.vmctx_val(&mut builder.cursor());
916 let tag_index = wasmtime_environ::TagIndex::from_u32(index);
917 let pointer_type = env.pointer_type();
918 if let Some(def_index) = env.module.defined_tag_index(tag_index) {
919 let offset = i32::try_from(env.offsets.vmctx_vmtag_definition(def_index)).unwrap();
920 builder.ins().iadd_imm(vmctx, i64::from(offset))
921 } else {
922 let offset = i32::try_from(env.offsets.vmctx_vmtag_import_from(tag_index)).unwrap();
923 builder.ins().load(
924 pointer_type,
925 ir::MemFlags::trusted().with_readonly(),
926 vmctx,
927 ir::immediates::Offset32::new(offset),
928 )
929 }
930 }
931
932 /// Returns the stack chain saved in the given `VMContext`. Note that the
933 /// head of the list is the actively running stack (initial stack or
934 /// continuation).
vmctx_load_stack_chain<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, vmctx: ir::Value, ) -> VMStackChain935 pub fn vmctx_load_stack_chain<'a>(
936 env: &mut crate::func_environ::FuncEnvironment<'a>,
937 builder: &mut FunctionBuilder,
938 vmctx: ir::Value,
939 ) -> VMStackChain {
940 let stack_chain_offset = env.offsets.ptr.vmstore_context_stack_chain().into();
941
942 // First we need to get the `VMStoreContext`.
943 let vm_store_context_offset = env.offsets.ptr.vmctx_store_context();
944 let vm_store_context = builder.ins().load(
945 env.pointer_type(),
946 MemFlags::trusted(),
947 vmctx,
948 vm_store_context_offset,
949 );
950
951 VMStackChain::load(
952 env,
953 builder,
954 vm_store_context,
955 stack_chain_offset,
956 env.pointer_type(),
957 )
958 }
959
960 /// Stores the given stack chain saved in the `VMContext`, overwriting the
961 /// existing one.
vmctx_store_stack_chain<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, vmctx: ir::Value, stack_chain: &VMStackChain, )962 pub fn vmctx_store_stack_chain<'a>(
963 env: &mut crate::func_environ::FuncEnvironment<'a>,
964 builder: &mut FunctionBuilder,
965 vmctx: ir::Value,
966 stack_chain: &VMStackChain,
967 ) {
968 let stack_chain_offset = env.offsets.ptr.vmstore_context_stack_chain().into();
969
970 // First we need to get the `VMStoreContext`.
971 let vm_store_context_offset = env.offsets.ptr.vmctx_store_context();
972 let vm_store_context = builder.ins().load(
973 env.pointer_type(),
974 MemFlags::trusted(),
975 vmctx,
976 vm_store_context_offset,
977 );
978
979 stack_chain.store(env, builder, vm_store_context, stack_chain_offset)
980 }
981
982 /// Similar to `vmctx_store_stack_chain`, but instead of storing an arbitrary
983 /// `VMStackChain`, stores VMStackChain::Continuation(contref)`.
vmctx_set_active_continuation<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, vmctx: ir::Value, contref: ir::Value, )984 pub fn vmctx_set_active_continuation<'a>(
985 env: &mut crate::func_environ::FuncEnvironment<'a>,
986 builder: &mut FunctionBuilder,
987 vmctx: ir::Value,
988 contref: ir::Value,
989 ) {
990 let chain = VMStackChain::from_continuation(env, builder, contref);
991 vmctx_store_stack_chain(env, builder, vmctx, &chain)
992 }
993
vmctx_load_vm_runtime_limits_ptr<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, vmctx: ir::Value, ) -> ir::Value994 pub fn vmctx_load_vm_runtime_limits_ptr<'a>(
995 env: &mut crate::func_environ::FuncEnvironment<'a>,
996 builder: &mut FunctionBuilder,
997 vmctx: ir::Value,
998 ) -> ir::Value {
999 let pointer_type = env.pointer_type();
1000 let offset = i32::from(env.offsets.ptr.vmctx_store_context());
1001
1002 // The *pointer* to the VMRuntimeLimits does not change within the
1003 // same function, allowing us to set the `read_only` flag.
1004 let flags = ir::MemFlags::trusted().with_readonly();
1005
1006 builder.ins().load(pointer_type, flags, vmctx, offset)
1007 }
1008
1009 /// This function generates code that searches for a handler for `tag_address`,
1010 /// which must be a `*mut VMTagDefinition`. The search walks up the chain of
1011 /// continuations beginning at `start`.
1012 ///
1013 /// The flag `search_suspend_handlers` determines whether we search for a
1014 /// suspend or switch handler. Concretely, this influences which part of each
1015 /// handler list we will search.
1016 ///
1017 /// We trap if no handler was found.
1018 ///
1019 /// The returned values are:
1020 /// 1. The stack (continuation or initial stack, represented as a VMStackChain) in
1021 /// whose handler list we found the tag (i.e., the stack that performed the
1022 /// resume instruction that installed handler for the tag).
1023 /// 2. The continuation whose parent is the stack mentioned in 1.
1024 /// 3. The index of the handler in the handler list.
1025 ///
1026 /// In pseudo-code, the generated code's behavior can be expressed as
1027 /// follows:
1028 ///
1029 /// chain_link = start
1030 /// while !chain_link.is_initial_stack() {
1031 /// contref = chain_link.get_contref()
1032 /// parent_link = contref.parent
1033 /// parent_csi = parent_link.get_common_stack_information();
1034 /// handlers = parent_csi.handlers;
1035 /// (begin_range, end_range) = if search_suspend_handlers {
1036 /// (0, parent_csi.first_switch_handler_index)
1037 /// } else {
1038 /// (parent_csi.first_switch_handler_index, handlers.length)
1039 /// };
1040 /// for index in begin_range..end_range {
1041 /// if handlers[index] == tag_address {
1042 /// goto on_match(contref, index)
1043 /// }
1044 /// }
1045 /// chain_link = parent_link
1046 /// }
1047 /// trap(unhandled_tag)
1048 ///
1049 /// on_match(conref : VMContRef, handler_index : u32)
1050 /// ... execution continues here here ...
1051 ///
search_handler<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, start: &helpers::VMStackChain, tag_address: ir::Value, search_suspend_handlers: bool, ) -> (VMStackChain, ir::Value, ir::Value)1052 fn search_handler<'a>(
1053 env: &mut crate::func_environ::FuncEnvironment<'a>,
1054 builder: &mut FunctionBuilder,
1055 start: &helpers::VMStackChain,
1056 tag_address: ir::Value,
1057 search_suspend_handlers: bool,
1058 ) -> (VMStackChain, ir::Value, ir::Value) {
1059 let handle_link = builder.create_block();
1060 let begin_search_handler_list = builder.create_block();
1061 let try_index = builder.create_block();
1062 let compare_tags = builder.create_block();
1063 let on_match = builder.create_block();
1064 let on_no_match = builder.create_block();
1065 let block_args = start.to_raw_parts().map(|v| BlockArg::Value(v));
1066
1067 // Terminate previous block:
1068 builder.ins().jump(handle_link, &block_args);
1069
1070 // Block handle_link
1071 let chain_link = {
1072 builder.append_block_param(handle_link, env.pointer_type());
1073 builder.append_block_param(handle_link, env.pointer_type());
1074 builder.switch_to_block(handle_link);
1075
1076 let raw_parts = builder.block_params(handle_link);
1077 let chain_link = helpers::VMStackChain::from_raw_parts([raw_parts[0], raw_parts[1]]);
1078 let is_initial_stack = chain_link.is_initial_stack(env, builder);
1079 builder.ins().brif(
1080 is_initial_stack,
1081 on_no_match,
1082 &[],
1083 begin_search_handler_list,
1084 &[],
1085 );
1086 chain_link
1087 };
1088
1089 // Block begin_search_handler_list
1090 let (contref, parent_link, handler_list_data_ptr, end_range) = {
1091 builder.switch_to_block(begin_search_handler_list);
1092 let contref = chain_link.unchecked_get_continuation();
1093 let contref = helpers::VMContRef::new(contref);
1094
1095 let parent_link = contref.get_parent_stack_chain(env, builder);
1096 let parent_csi = parent_link.get_common_stack_information(env, builder);
1097
1098 let handlers = parent_csi.get_handler_list(env, builder);
1099 let handler_list_data_ptr = handlers.get_data(env, builder);
1100
1101 let first_switch_handler_index = parent_csi.get_first_switch_handler_index(env, builder);
1102
1103 // Note that these indices are inclusive-exclusive, i.e. [begin_range, end_range).
1104 let (begin_range, end_range) = if search_suspend_handlers {
1105 let zero = builder.ins().iconst(I32, 0);
1106 (zero, first_switch_handler_index)
1107 } else {
1108 let length = handlers.get_length(env, builder);
1109 (first_switch_handler_index, length)
1110 };
1111
1112 builder
1113 .ins()
1114 .jump(try_index, &[BlockArg::Value(begin_range)]);
1115
1116 (contref, parent_link, handler_list_data_ptr, end_range)
1117 };
1118
1119 // Block try_index
1120 let index = {
1121 builder.append_block_param(try_index, I32);
1122 builder.switch_to_block(try_index);
1123 let index = builder.block_params(try_index)[0];
1124
1125 let in_bounds = builder
1126 .ins()
1127 .icmp(IntCC::UnsignedLessThan, index, end_range);
1128 let block_args = parent_link.to_raw_parts().map(|v| BlockArg::Value(v));
1129 builder
1130 .ins()
1131 .brif(in_bounds, compare_tags, &[], handle_link, &block_args);
1132 index
1133 };
1134
1135 // Block compare_tags
1136 {
1137 builder.switch_to_block(compare_tags);
1138
1139 let base = handler_list_data_ptr;
1140 let entry_size = env.pointer_type().bytes();
1141 let offset = builder.ins().imul_imm(index, i64::from(entry_size));
1142 let offset = builder.ins().uextend(I64, offset);
1143 let entry_address = builder.ins().iadd(base, offset);
1144
1145 let memflags = ir::MemFlags::trusted();
1146
1147 let handled_tag = builder
1148 .ins()
1149 .load(env.pointer_type(), memflags, entry_address, 0);
1150
1151 let tags_match = builder.ins().icmp(IntCC::Equal, handled_tag, tag_address);
1152 let incremented_index = builder.ins().iadd_imm(index, 1);
1153 builder.ins().brif(
1154 tags_match,
1155 on_match,
1156 &[],
1157 try_index,
1158 &[BlockArg::Value(incremented_index)],
1159 );
1160 }
1161
1162 // Block on_no_match
1163 {
1164 builder.switch_to_block(on_no_match);
1165 builder.set_cold_block(on_no_match);
1166 builder.ins().trap(crate::TRAP_UNHANDLED_TAG);
1167 }
1168
1169 builder.seal_block(handle_link);
1170 builder.seal_block(begin_search_handler_list);
1171 builder.seal_block(try_index);
1172 builder.seal_block(compare_tags);
1173 builder.seal_block(on_match);
1174 builder.seal_block(on_no_match);
1175
1176 // final block: on_match
1177 builder.switch_to_block(on_match);
1178
1179 (parent_link, contref.address, index)
1180 }
1181
translate_cont_bind<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, contobj: ir::Value, args: &[ir::Value], ) -> ir::Value1182 pub(crate) fn translate_cont_bind<'a>(
1183 env: &mut crate::func_environ::FuncEnvironment<'a>,
1184 builder: &mut FunctionBuilder,
1185 contobj: ir::Value,
1186 args: &[ir::Value],
1187 ) -> ir::Value {
1188 let (witness, contref) = fatpointer::deconstruct(env, &mut builder.cursor(), contobj);
1189
1190 // The typing rules for cont.bind allow a null reference to be passed to it.
1191 builder.ins().trapz(contref, crate::TRAP_NULL_REFERENCE);
1192
1193 let mut vmcontref = helpers::VMContRef::new(contref);
1194 let revision = vmcontref.get_revision(env, builder);
1195 let evidence = builder.ins().icmp(IntCC::Equal, witness, revision);
1196 builder
1197 .ins()
1198 .trapz(evidence, crate::TRAP_CONTINUATION_ALREADY_CONSUMED);
1199
1200 vmcontref_store_payloads(env, builder, args, contref);
1201
1202 let revision = vmcontref.incr_revision(env, builder, revision);
1203 let contobj = fatpointer::construct(env, &mut builder.cursor(), revision, contref);
1204 contobj
1205 }
1206
translate_cont_new<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, func: ir::Value, arg_types: &[WasmValType], return_types: &[WasmValType], ) -> WasmResult<ir::Value>1207 pub(crate) fn translate_cont_new<'a>(
1208 env: &mut crate::func_environ::FuncEnvironment<'a>,
1209 builder: &mut FunctionBuilder,
1210 func: ir::Value,
1211 arg_types: &[WasmValType],
1212 return_types: &[WasmValType],
1213 ) -> WasmResult<ir::Value> {
1214 // The typing rules for cont.new allow a null reference to be passed to it.
1215 builder.ins().trapz(func, crate::TRAP_NULL_REFERENCE);
1216
1217 let nargs = builder
1218 .ins()
1219 .iconst(I32, i64::try_from(arg_types.len()).unwrap());
1220 let nreturns = builder
1221 .ins()
1222 .iconst(I32, i64::try_from(return_types.len()).unwrap());
1223
1224 let cont_new_func = super::builtins::cont_new(env, &mut builder.func)?;
1225 let vmctx = env.vmctx_val(&mut builder.cursor());
1226 let call_inst = builder
1227 .ins()
1228 .call(cont_new_func, &[vmctx, func, nargs, nreturns]);
1229 let contref = *builder.func.dfg.inst_results(call_inst).first().unwrap();
1230
1231 let tag = helpers::VMContRef::new(contref).get_revision(env, builder);
1232 let contobj = fatpointer::construct(env, &mut builder.cursor(), tag, contref);
1233 Ok(contobj)
1234 }
1235
translate_resume<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, type_index: u32, resume_contobj: ir::Value, resume_args: &[ir::Value], resumetable: &[(u32, Option<ir::Block>)], ) -> WasmResult<Vec<ir::Value>>1236 pub(crate) fn translate_resume<'a>(
1237 env: &mut crate::func_environ::FuncEnvironment<'a>,
1238 builder: &mut FunctionBuilder,
1239 type_index: u32,
1240 resume_contobj: ir::Value,
1241 resume_args: &[ir::Value],
1242 resumetable: &[(u32, Option<ir::Block>)],
1243 ) -> WasmResult<Vec<ir::Value>> {
1244 // The resume instruction is the most involved instruction to
1245 // compile as it is responsible for both continuation application
1246 // and control tag dispatch.
1247 //
1248 // Here we translate a resume instruction into several basic
1249 // blocks as follows:
1250 //
1251 // previous block
1252 // |
1253 // |
1254 // resume_block
1255 // / \
1256 // / \
1257 // | |
1258 // return_block |
1259 // suspend block
1260 // |
1261 // dispatch block
1262 //
1263 // * resume_block handles continuation arguments and performs
1264 // actual stack switch. On ordinary return from resume, it jumps
1265 // to the `return_block`, whereas on suspension it jumps to the
1266 // `suspend_block`.
1267 // * suspend_block is used on suspension, jumps onward to
1268 // `dispatch_block`.
1269 // * dispatch_block uses a jump table to dispatch to actual
1270 // user-defined handler blocks, based on the handler index
1271 // provided on suspension. Note that we do not jump to the
1272 // handler blocks directly. Instead, each handler block has a
1273 // corresponding preamble block, which we jump to in order to
1274 // reach a particular handler block. The preamble block prepares
1275 // the arguments and continuation object to be passed to the
1276 // actual handler block.
1277 //
1278 let resume_block = builder.create_block();
1279 let return_block = builder.create_block();
1280 let suspend_block = builder.create_block();
1281 let dispatch_block = builder.create_block();
1282
1283 let vmctx = env.vmctx_val(&mut builder.cursor());
1284
1285 // Split the resumetable into suspend handlers (each represented by the tag
1286 // index and handler block) and the switch handlers (represented just by the
1287 // tag index). Note that we currently don't remove duplicate tags.
1288 let (suspend_handlers, switch_tags): (Vec<(u32, Block)>, Vec<u32>) = resumetable
1289 .iter()
1290 .partition_map(|(tag_index, block_opt)| match block_opt {
1291 Some(block) => Either::Left((*tag_index, *block)),
1292 None => Either::Right(*tag_index),
1293 });
1294
1295 // Technically, there is no need to have a dedicated resume block, we could
1296 // just put all of its contents into the current block.
1297 builder.ins().jump(resume_block, &[]);
1298
1299 // Resume block: actually resume the continuation chain ending at `resume_contref`.
1300 let (resume_result, vm_runtime_limits_ptr, original_stack_chain, new_stack_chain) = {
1301 builder.switch_to_block(resume_block);
1302 builder.seal_block(resume_block);
1303
1304 let (witness, resume_contref) =
1305 fatpointer::deconstruct(env, &mut builder.cursor(), resume_contobj);
1306
1307 // The typing rules for resume allow a null reference to be passed to it.
1308 builder
1309 .ins()
1310 .trapz(resume_contref, crate::TRAP_NULL_REFERENCE);
1311
1312 let mut vmcontref = helpers::VMContRef::new(resume_contref);
1313
1314 let revision = vmcontref.get_revision(env, builder);
1315 let evidence = builder.ins().icmp(IntCC::Equal, revision, witness);
1316 builder
1317 .ins()
1318 .trapz(evidence, crate::TRAP_CONTINUATION_ALREADY_CONSUMED);
1319 let _next_revision = vmcontref.incr_revision(env, builder, revision);
1320
1321 if resume_args.len() > 0 {
1322 // We store the arguments in the `VMContRef` to be resumed.
1323 vmcontref_store_payloads(env, builder, resume_args, resume_contref);
1324 }
1325
1326 // Splice together stack chains:
1327 // Connect the end of the chain starting at `resume_contref` to the currently active chain.
1328 let mut last_ancestor = helpers::VMContRef::new(vmcontref.get_last_ancestor(env, builder));
1329
1330 // Make the currently running continuation (if any) the parent of the one we are about to resume.
1331 let original_stack_chain = vmctx_load_stack_chain(env, builder, vmctx);
1332 last_ancestor.set_parent_stack_chain(env, builder, &original_stack_chain);
1333
1334 // Just for consistency: `vmcontref` is about to get state Running, so let's zero out its last_ancestor field.
1335 let zero = builder.ins().iconst(env.pointer_type(), 0);
1336 vmcontref.set_last_ancestor(env, builder, zero);
1337
1338 // We mark `resume_contref` as the currently running one
1339 vmctx_set_active_continuation(env, builder, vmctx, resume_contref);
1340
1341 // Note that the resume_contref libcall a few lines further below
1342 // manipulates the stack limits as follows:
1343 // 1. Copy stack_limit, last_wasm_entry_sp and last_wasm_exit* values from
1344 // VMRuntimeLimits into the currently active continuation (i.e., the
1345 // one that will become the parent of the to-be-resumed one)
1346 //
1347 // 2. Copy `stack_limit` and `last_wasm_entry_sp` in the
1348 // `VMStackLimits` of `resume_contref` into the `VMRuntimeLimits`.
1349 //
1350 // See the comment on `wasmtime_environ::VMStackChain` for a
1351 // description of the invariants that we maintain for the various stack
1352 // limits.
1353
1354 // `resume_contref` is now active, and its parent is suspended.
1355 let resume_contref = helpers::VMContRef::new(resume_contref);
1356 let resume_csi = resume_contref.common_stack_information(env, builder);
1357 let parent_csi = original_stack_chain.get_common_stack_information(env, builder);
1358 resume_csi.set_state_running(env, builder);
1359 parent_csi.set_state_parent(env, builder);
1360
1361 // We update the `VMStackLimits` of the parent of the continuation to be resumed
1362 // as well as the `VMRuntimeLimits`.
1363 // See the comment on `wasmtime_environ::VMStackChain` for a description
1364 // of the invariants that we maintain for the various stack limits.
1365 let vm_runtime_limits_ptr = vmctx_load_vm_runtime_limits_ptr(env, builder, vmctx);
1366 parent_csi.load_limits_from_vmcontext(env, builder, vm_runtime_limits_ptr, true);
1367 resume_csi.write_limits_to_vmcontext(env, builder, vm_runtime_limits_ptr);
1368
1369 // Install handlers in (soon to be) parent's VMHandlerList:
1370 // Let the i-th handler clause be (on $tag $block).
1371 // Then the i-th entry of the VMHandlerList will be the address of $tag.
1372 let handler_list = parent_csi.get_handler_list(env, builder);
1373
1374 if resumetable.len() > 0 {
1375 // Total number of handlers (suspend and switch).
1376 let handler_count = u32::try_from(resumetable.len()).unwrap();
1377 // Populate the Array's data ptr with a pointer to a sufficiently
1378 // large area on this stack.
1379 env.stack_switching_handler_list_buffer =
1380 Some(handler_list.allocate_or_reuse_stack_slot(
1381 env,
1382 builder,
1383 handler_count,
1384 env.stack_switching_handler_list_buffer,
1385 ));
1386
1387 let suspend_handler_count = suspend_handlers.len();
1388
1389 // All handlers, represented by the indices of the tags they handle.
1390 // All the suspend handlers come first, followed by all the switch handlers.
1391 let all_handlers = suspend_handlers
1392 .iter()
1393 .map(|(tag_index, _block)| *tag_index)
1394 .chain(switch_tags);
1395
1396 // Translate all tag indices to tag addresses (i.e., the corresponding *mut VMTagDefinition).
1397 let all_tag_addresses: Vec<ir::Value> = all_handlers
1398 .map(|tag_index| tag_address(env, builder, tag_index))
1399 .collect();
1400
1401 // Store all tag addresses in the handler list.
1402 handler_list.store_data_entries(env, builder, &all_tag_addresses);
1403
1404 // To enable distinguishing switch and suspend handlers when searching the handler list:
1405 // Store at which index the switch handlers start.
1406 let first_switch_handler_index = builder
1407 .ins()
1408 .iconst(I32, i64::try_from(suspend_handler_count).unwrap());
1409 parent_csi.set_first_switch_handler_index(env, builder, first_switch_handler_index);
1410 }
1411
1412 let resume_payload = ControlEffect::encode_resume(builder).to_u64();
1413
1414 // Note that the control context we use for switching is not the one in
1415 // (the stack of) resume_contref, but in (the stack of) last_ancestor!
1416 let fiber_stack = last_ancestor.get_fiber_stack(env, builder);
1417 let control_context_ptr = fiber_stack.load_control_context(env, builder);
1418
1419 let result =
1420 builder
1421 .ins()
1422 .stack_switch(control_context_ptr, control_context_ptr, resume_payload);
1423
1424 // At this point we know nothing about the continuation that just
1425 // suspended or returned. In particular, it does not have to be what we
1426 // called `resume_contref` earlier on. We must reload the information
1427 // about the now active continuation from the VMContext.
1428 let new_stack_chain = vmctx_load_stack_chain(env, builder, vmctx);
1429
1430 // Now the parent contref (or initial stack) is active again
1431 vmctx_store_stack_chain(env, builder, vmctx, &original_stack_chain);
1432 parent_csi.set_state_running(env, builder);
1433
1434 // Just for consistency: Clear the handler list.
1435 handler_list.clear(env, builder, true);
1436 parent_csi.set_first_switch_handler_index(env, builder, zero);
1437
1438 // Extract the result and signal bit.
1439 let result = ControlEffect::from_u64(result);
1440 let signal = result.signal(builder);
1441
1442 // Jump to the return block if the result signal is 0, otherwise jump to
1443 // the suspend block.
1444 builder
1445 .ins()
1446 .brif(signal, suspend_block, &[], return_block, &[]);
1447
1448 (
1449 result,
1450 vm_runtime_limits_ptr,
1451 original_stack_chain,
1452 new_stack_chain,
1453 )
1454 };
1455
1456 // The suspend block: Only used when we suspended, not for returns.
1457 // Here we extract the index of the handler to use.
1458 let (handler_index, suspended_contref, suspended_contobj) = {
1459 builder.switch_to_block(suspend_block);
1460 builder.seal_block(suspend_block);
1461
1462 let suspended_continuation = new_stack_chain.unchecked_get_continuation();
1463 let mut suspended_continuation = helpers::VMContRef::new(suspended_continuation);
1464 let suspended_csi = suspended_continuation.common_stack_information(env, builder);
1465
1466 // Note that at the suspend site, we already
1467 // 1. Set the state of suspended_continuation to Suspended
1468 // 2. Set suspended_continuation.last_ancestor
1469 // 3. Broke the continuation chain at suspended_continuation.last_ancestor
1470
1471 // We store parts of the VMRuntimeLimits into the continuation that just suspended.
1472 suspended_csi.load_limits_from_vmcontext(env, builder, vm_runtime_limits_ptr, false);
1473
1474 // Afterwards (!), restore parts of the VMRuntimeLimits from the
1475 // parent of the suspended continuation (which is now active).
1476 let parent_csi = original_stack_chain.get_common_stack_information(env, builder);
1477 parent_csi.write_limits_to_vmcontext(env, builder, vm_runtime_limits_ptr);
1478
1479 // Extract the handler index
1480 let handler_index = resume_result.handler_index(builder);
1481
1482 let revision = suspended_continuation.get_revision(env, builder);
1483 let suspended_contobj = fatpointer::construct(
1484 env,
1485 &mut builder.cursor(),
1486 revision,
1487 suspended_continuation.address,
1488 );
1489
1490 // We need to terminate this block before being allowed to switch to
1491 // another one.
1492 builder.ins().jump(dispatch_block, &[]);
1493
1494 (handler_index, suspended_continuation, suspended_contobj)
1495 };
1496
1497 // For technical reasons, the jump table needs to have a default
1498 // block. In our case, it should be unreachable, since the handler
1499 // index we dispatch on should correspond to a an actual handler
1500 // block in the jump table.
1501 let jt_default_block = builder.create_block();
1502 {
1503 builder.switch_to_block(jt_default_block);
1504 builder.set_cold_block(jt_default_block);
1505
1506 builder.ins().trap(crate::TRAP_UNREACHABLE);
1507 }
1508
1509 // We create a preamble block for each of the actual handler blocks: It
1510 // reads the necessary arguments and passes them to the actual handler
1511 // block, together with the continuation object.
1512 let target_preamble_blocks = {
1513 let mut preamble_blocks = vec![];
1514
1515 for &(handle_tag, target_block) in &suspend_handlers {
1516 let preamble_block = builder.create_block();
1517 preamble_blocks.push(preamble_block);
1518 builder.switch_to_block(preamble_block);
1519
1520 let param_types = env.tag_params(TagIndex::from_u32(handle_tag));
1521 let param_types: Vec<ir::Type> = param_types
1522 .iter()
1523 .map(|wty| crate::value_type(env.isa(), *wty))
1524 .collect();
1525
1526 let values = suspended_contref.values(env, builder);
1527 let mut suspend_args: Vec<BlockArg> = values
1528 .load_data_entries(env, builder, ¶m_types)
1529 .into_iter()
1530 .map(|v| BlockArg::Value(v))
1531 .collect();
1532
1533 // At the suspend site, we store the suspend args in the the
1534 // `values` buffer of the VMContRef that was active at the time that
1535 // the suspend instruction was performed.
1536 suspend_args.push(BlockArg::Value(suspended_contobj));
1537
1538 // We clear the suspend args. This is mostly for consistency. Note
1539 // that we don't zero out the data buffer, we still need it for the
1540
1541 values.clear(env, builder, false);
1542
1543 builder.ins().jump(target_block, &suspend_args);
1544 }
1545
1546 preamble_blocks
1547 };
1548
1549 // Dispatch block. All it does is jump to the right preamble block based on
1550 // the handler index.
1551 {
1552 builder.switch_to_block(dispatch_block);
1553 builder.seal_block(dispatch_block);
1554
1555 let default_bc = builder.func.dfg.block_call(jt_default_block, &[]);
1556
1557 let adapter_bcs: Vec<BlockCall> = target_preamble_blocks
1558 .iter()
1559 .map(|b| builder.func.dfg.block_call(*b, &[]))
1560 .collect();
1561
1562 let jt_data = JumpTableData::new(default_bc, &adapter_bcs);
1563 let jt = builder.create_jump_table(jt_data);
1564
1565 builder.ins().br_table(handler_index, jt);
1566
1567 for preamble_block in target_preamble_blocks {
1568 builder.seal_block(preamble_block);
1569 }
1570 builder.seal_block(jt_default_block);
1571 }
1572
1573 // Return block: Jumped to by resume block if continuation
1574 // returned normally.
1575 {
1576 builder.switch_to_block(return_block);
1577 builder.seal_block(return_block);
1578
1579 // If we got a return signal, a continuation must have been running.
1580 let returned_contref = new_stack_chain.unchecked_get_continuation();
1581 let returned_contref = helpers::VMContRef::new(returned_contref);
1582
1583 // Restore parts of the VMRuntimeLimits from the parent of the
1584 // returned continuation (which is now active).
1585 let parent_csi = original_stack_chain.get_common_stack_information(env, builder);
1586 parent_csi.write_limits_to_vmcontext(env, builder, vm_runtime_limits_ptr);
1587
1588 let returned_csi = returned_contref.common_stack_information(env, builder);
1589 returned_csi.set_state_returned(env, builder);
1590
1591 // Load the values returned by the continuation.
1592 let return_types: Vec<_> = env
1593 .continuation_returns(TypeIndex::from_u32(type_index))
1594 .iter()
1595 .map(|ty| crate::value_type(env.isa(), *ty))
1596 .collect();
1597 let payloads = returned_contref.args(env, builder);
1598 let return_values = payloads.load_data_entries(env, builder, &return_types);
1599 payloads.clear(env, builder, true);
1600
1601 Ok(return_values)
1602 }
1603 }
1604
translate_suspend<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, tag_index: u32, suspend_args: &[ir::Value], tag_return_types: &[ir::Type], ) -> Vec<ir::Value>1605 pub(crate) fn translate_suspend<'a>(
1606 env: &mut crate::func_environ::FuncEnvironment<'a>,
1607 builder: &mut FunctionBuilder,
1608 tag_index: u32,
1609 suspend_args: &[ir::Value],
1610 tag_return_types: &[ir::Type],
1611 ) -> Vec<ir::Value> {
1612 let tag_addr = tag_address(env, builder, tag_index);
1613
1614 let vmctx = env.vmctx_val(&mut builder.cursor());
1615 let active_stack_chain = vmctx_load_stack_chain(env, builder, vmctx);
1616
1617 let (_, end_of_chain_contref, handler_index) =
1618 search_handler(env, builder, &active_stack_chain, tag_addr, true);
1619
1620 // If we get here, the search_handler logic succeeded (i.e., did not trap).
1621 // Thus, there is at least one parent, so we are not on the initial stack.
1622 // Can therefore extract continuation directly.
1623 let active_contref = active_stack_chain.unchecked_get_continuation();
1624 let active_contref = helpers::VMContRef::new(active_contref);
1625 let mut end_of_chain_contref = helpers::VMContRef::new(end_of_chain_contref);
1626
1627 active_contref.set_last_ancestor(env, builder, end_of_chain_contref.address);
1628
1629 // In the active_contref's `values` buffer, stack-allocate enough room so that we can
1630 // later store the following:
1631 // 1. The suspend arguments
1632 // 2. Afterwards, the tag return values
1633 let values = active_contref.values(env, builder);
1634 let required_capacity =
1635 u32::try_from(std::cmp::max(suspend_args.len(), tag_return_types.len()))
1636 .expect("Number of stack switching payloads should fit in u32");
1637
1638 if required_capacity > 0 {
1639 env.stack_switching_values_buffer = Some(values.allocate_or_reuse_stack_slot(
1640 env,
1641 builder,
1642 required_capacity,
1643 env.stack_switching_values_buffer,
1644 ));
1645 }
1646
1647 if suspend_args.len() > 0 {
1648 values.store_data_entries(env, builder, suspend_args);
1649 }
1650
1651 // Set current continuation to suspended and break up handler chain.
1652 let active_contref_csi = active_contref.common_stack_information(env, builder);
1653 active_contref_csi.set_state_suspended(env, builder);
1654 let absent_chain_link = VMStackChain::absent(env, builder);
1655 end_of_chain_contref.set_parent_stack_chain(env, builder, &absent_chain_link);
1656
1657 let suspend_payload = ControlEffect::encode_suspend(builder, handler_index).to_u64();
1658
1659 // Note that the control context we use for switching is the one
1660 // at the end of the chain, not the one in active_contref!
1661 // This also means that stack_switch saves the information about
1662 // the current stack in the control context located in the stack
1663 // of end_of_chain_contref.
1664 let fiber_stack = end_of_chain_contref.get_fiber_stack(env, builder);
1665 let control_context_ptr = fiber_stack.load_control_context(env, builder);
1666
1667 builder
1668 .ins()
1669 .stack_switch(control_context_ptr, control_context_ptr, suspend_payload);
1670
1671 // The return values of the suspend instruction are the tag return values, saved in the `args` buffer.
1672 let values = active_contref.values(env, builder);
1673 let return_values = values.load_data_entries(env, builder, tag_return_types);
1674 // We effectively consume the values and discard the stack allocated buffer.
1675 values.clear(env, builder, true);
1676
1677 return_values
1678 }
1679
translate_switch<'a>( env: &mut crate::func_environ::FuncEnvironment<'a>, builder: &mut FunctionBuilder, tag_index: u32, switchee_contobj: ir::Value, switch_args: &[ir::Value], return_types: &[ir::Type], ) -> WasmResult<Vec<ir::Value>>1680 pub(crate) fn translate_switch<'a>(
1681 env: &mut crate::func_environ::FuncEnvironment<'a>,
1682 builder: &mut FunctionBuilder,
1683 tag_index: u32,
1684 switchee_contobj: ir::Value,
1685 switch_args: &[ir::Value],
1686 return_types: &[ir::Type],
1687 ) -> WasmResult<Vec<ir::Value>> {
1688 let vmctx = env.vmctx_val(&mut builder.cursor());
1689
1690 // Check and increment revision on switchee continuation object (i.e., the
1691 // one being switched to). Logically, the switchee continuation extends from
1692 // `switchee_contref` to `switchee_contref.last_ancestor` (i.e., the end of
1693 // the parent chain starting at `switchee_contref`).
1694 let switchee_contref = {
1695 let (witness, target_contref) =
1696 fatpointer::deconstruct(env, &mut builder.cursor(), switchee_contobj);
1697
1698 // The typing rules for switch allow a null reference to be passed to it.
1699 builder
1700 .ins()
1701 .trapz(target_contref, crate::TRAP_NULL_REFERENCE);
1702
1703 let mut target_contref = helpers::VMContRef::new(target_contref);
1704
1705 let revision = target_contref.get_revision(env, builder);
1706 let evidence = builder.ins().icmp(IntCC::Equal, revision, witness);
1707 builder
1708 .ins()
1709 .trapz(evidence, crate::TRAP_CONTINUATION_ALREADY_CONSUMED);
1710 let _next_revision = target_contref.incr_revision(env, builder, revision);
1711 target_contref
1712 };
1713
1714 // We create the "switcher continuation" (i.e., the one executing switch)
1715 // from the current execution context: Logically, it extends from the
1716 // continuation reference executing `switch` (subsequently called
1717 // `switcher_contref`) to the immediate child (called
1718 // `switcher_contref_last_ancestor`) of the stack with the corresponding
1719 // handler (saved in `handler_stack_chain`).
1720 let (
1721 switcher_contref,
1722 switcher_contobj,
1723 switcher_contref_last_ancestor,
1724 handler_stack_chain,
1725 vm_runtime_limits_ptr,
1726 ) = {
1727 let tag_addr = tag_address(env, builder, tag_index);
1728 let active_stack_chain = vmctx_load_stack_chain(env, builder, vmctx);
1729 let (handler_stack_chain, last_ancestor, _handler_index) =
1730 search_handler(env, builder, &active_stack_chain, tag_addr, false);
1731 let mut last_ancestor = helpers::VMContRef::new(last_ancestor);
1732
1733 // If we get here, the search_handler logic succeeded (i.e., did not trap).
1734 // Thus, there is at least one parent, so we are not on the initial stack.
1735 // Can therefore extract continuation directly.
1736 let switcher_contref = active_stack_chain.unchecked_get_continuation();
1737 let mut switcher_contref = helpers::VMContRef::new(switcher_contref);
1738
1739 switcher_contref.set_last_ancestor(env, builder, last_ancestor.address);
1740
1741 // In the switcher_contref's `values` buffer, stack-allocate enough room so that we can
1742 // later store `tag_return_types.len()` when resuming the continuation.
1743 let values = switcher_contref.values(env, builder);
1744 let required_capacity = u32::try_from(return_types.len()).unwrap();
1745 if required_capacity > 0 {
1746 env.stack_switching_values_buffer = Some(values.allocate_or_reuse_stack_slot(
1747 env,
1748 builder,
1749 required_capacity,
1750 env.stack_switching_values_buffer,
1751 ));
1752 }
1753
1754 let switcher_contref_csi = switcher_contref.common_stack_information(env, builder);
1755 switcher_contref_csi.set_state_suspended(env, builder);
1756 // We break off `switcher_contref` from the chain of active
1757 // continuations, by separating the link between `last_ancestor` and its
1758 // parent stack.
1759 let absent = VMStackChain::absent(env, builder);
1760 last_ancestor.set_parent_stack_chain(env, builder, &absent);
1761
1762 // Load current runtime limits from `VMContext` and store in the
1763 // switcher continuation.
1764 let vm_runtime_limits_ptr = vmctx_load_vm_runtime_limits_ptr(env, builder, vmctx);
1765 switcher_contref_csi.load_limits_from_vmcontext(env, builder, vm_runtime_limits_ptr, false);
1766
1767 let revision = switcher_contref.get_revision(env, builder);
1768 let new_contobj = fatpointer::construct(
1769 env,
1770 &mut builder.cursor(),
1771 revision,
1772 switcher_contref.address,
1773 );
1774
1775 (
1776 switcher_contref,
1777 new_contobj,
1778 last_ancestor,
1779 handler_stack_chain,
1780 vm_runtime_limits_ptr,
1781 )
1782 };
1783
1784 // Prepare switchee continuation:
1785 // - Store "ordinary" switch arguments as well as the contobj just
1786 // synthesized from the current context (i.e., `switcher_contobj`) in the
1787 // switchee continuation's payload buffer.
1788 // - Splice switchee's continuation chain with handler stack to form new
1789 // overall chain of active continuations.
1790 let (switchee_contref_csi, switchee_contref_last_ancestor) = {
1791 let mut combined_payloads = switch_args.to_vec();
1792 combined_payloads.push(switcher_contobj);
1793 vmcontref_store_payloads(env, builder, &combined_payloads, switchee_contref.address);
1794
1795 let switchee_contref_csi = switchee_contref.common_stack_information(env, builder);
1796 switchee_contref_csi.set_state_running(env, builder);
1797
1798 let switchee_contref_last_ancestor = switchee_contref.get_last_ancestor(env, builder);
1799 let mut switchee_contref_last_ancestor =
1800 helpers::VMContRef::new(switchee_contref_last_ancestor);
1801
1802 switchee_contref_last_ancestor.set_parent_stack_chain(env, builder, &handler_stack_chain);
1803
1804 (switchee_contref_csi, switchee_contref_last_ancestor)
1805 };
1806
1807 // Update VMContext/Store: Update active continuation and `VMRuntimeLimits`.
1808 {
1809 vmctx_set_active_continuation(env, builder, vmctx, switchee_contref.address);
1810
1811 switchee_contref_csi.write_limits_to_vmcontext(env, builder, vm_runtime_limits_ptr);
1812 }
1813
1814 // Perform actual stack switch
1815 {
1816 let switcher_last_ancestor_fs =
1817 switcher_contref_last_ancestor.get_fiber_stack(env, builder);
1818 let switcher_last_ancestor_cc =
1819 switcher_last_ancestor_fs.load_control_context(env, builder);
1820
1821 let switchee_last_ancestor_fs =
1822 switchee_contref_last_ancestor.get_fiber_stack(env, builder);
1823 let switchee_last_ancestor_cc =
1824 switchee_last_ancestor_fs.load_control_context(env, builder);
1825
1826 // The stack switch involves the following control contexts (e.g., IP,
1827 // SP, FP, ...):
1828 // - `switchee_last_ancestor_cc` contains the information to continue
1829 // execution in the switchee/target continuation.
1830 // - `switcher_last_ancestor_cc` contains the information about how to
1831 // continue execution once we suspend/return to the stack with the
1832 // switch handler.
1833 //
1834 // In total, the following needs to happen:
1835 // 1. Load control context at `switchee_last_ancestor_cc` to perform
1836 // stack switch.
1837 // 2. Move control context at `switcher_last_ancestor_cc` over to
1838 // `switchee_last_ancestor_cc`.
1839 // 3. Upon actual switch, save current control context at
1840 // `switcher_last_ancestor_cc`.
1841 //
1842 // We implement this as follows:
1843 // 1. We copy `switchee_last_ancestor_cc` to a temporary area on the
1844 // stack (`tmp_control_context`).
1845 // 2. We copy `switcher_last_ancestor_cc` over to
1846 // `switchee_last_ancestor_cc`.
1847 // 3. We invoke the stack switch instruction such that it reads from the
1848 // temporary area, and writes to `switcher_last_ancestor_cc`.
1849 //
1850 // Note that the temporary area is only accessed once by the
1851 // `stack_switch` instruction emitted later in this block, meaning that we
1852 // don't have to worry about its lifetime.
1853 //
1854 // NOTE(frank-emrich) The implementation below results in one stack slot
1855 // being created per switch instruction, even though multiple switch
1856 // instructions in the same function could safely re-use the same stack
1857 // slot. Thus, we could implement logic for sharing the stack slot by
1858 // adding an appropriate field to `FuncEnvironment`.
1859 //
1860 // NOTE(frank-emrich) We could avoid the copying to a temporary area by
1861 // making `stack_switch` do all of the necessary moving itself. However,
1862 // that would be a rather ad-hoc change to how the instruction uses the
1863 // two pointers given to it.
1864
1865 let cctx_size = control_context_size(env.isa().triple())?;
1866 let slot_size = ir::StackSlotData::new(
1867 ir::StackSlotKind::ExplicitSlot,
1868 u32::from(cctx_size),
1869 u8::try_from(env.pointer_type().bytes()).unwrap(),
1870 );
1871 let slot = builder.create_sized_stack_slot(slot_size);
1872 let tmp_control_context = builder.ins().stack_addr(env.pointer_type(), slot, 0);
1873
1874 let flags = MemFlags::trusted();
1875 let mut offset: i32 = 0;
1876 while offset < i32::from(cctx_size) {
1877 // switchee_last_ancestor_cc -> tmp control context
1878 let tmp1 =
1879 builder
1880 .ins()
1881 .load(env.pointer_type(), flags, switchee_last_ancestor_cc, offset);
1882 builder
1883 .ins()
1884 .store(flags, tmp1, tmp_control_context, offset);
1885
1886 // switcher_last_ancestor_cc -> switchee_last_ancestor_cc
1887 let tmp2 =
1888 builder
1889 .ins()
1890 .load(env.pointer_type(), flags, switcher_last_ancestor_cc, offset);
1891 builder
1892 .ins()
1893 .store(flags, tmp2, switchee_last_ancestor_cc, offset);
1894
1895 offset += i32::try_from(env.pointer_type().bytes()).unwrap();
1896 }
1897
1898 let switch_payload = ControlEffect::encode_switch(builder).to_u64();
1899
1900 let _result = builder.ins().stack_switch(
1901 switcher_last_ancestor_cc,
1902 tmp_control_context,
1903 switch_payload,
1904 );
1905 }
1906
1907 // After switching back to the original stack: Load return values, they are
1908 // stored on the switcher continuation.
1909 let return_values = {
1910 let payloads = switcher_contref.values(env, builder);
1911 let return_values = payloads.load_data_entries(env, builder, return_types);
1912 // We consume the values and discard the buffer (allocated on this stack)
1913 payloads.clear(env, builder, true);
1914 return_values
1915 };
1916
1917 Ok(return_values)
1918 }
1919