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