1 use super::ControlStackFrame; 2 use crate::{ 3 Result, 4 abi::{ABIOperand, ABIResults, RetArea, vmctx}, 5 bail, 6 codegen::{BranchState, CodeGenError, CodeGenPhase, Emission, Prologue}, 7 ensure, 8 frame::Frame, 9 isa::reg::RegClass, 10 masm::{ 11 ExtractLaneKind, Imm, IntScratch, MacroAssembler, MemMoveDirection, OperandSize, RegImm, 12 ReplaceLaneKind, SPOffset, ShiftKind, StackSlot, 13 }, 14 reg::{Reg, WritableReg, writable}, 15 regalloc::RegAlloc, 16 stack::{Stack, TypedReg, Val}, 17 }; 18 use wasmparser::{Ieee32, Ieee64}; 19 use wasmtime_environ::{VMOffsets, WasmHeapType, WasmValType}; 20 21 /// The code generation context. 22 /// The code generation context is made up of three 23 /// essential data structures: 24 /// 25 /// * The register allocator, in charge of keeping the inventory of register 26 /// availability. 27 /// * The value stack, which keeps track of the state of the values 28 /// after each operation. 29 /// * The current function's frame. 30 /// 31 /// These data structures normally require cooperating with each other 32 /// to perform most of the operations needed during the code 33 /// generation process. The code generation context should 34 /// be generally used as the single entry point to access 35 /// the compound functionality provided by its elements. 36 pub(crate) struct CodeGenContext<'a, P: CodeGenPhase> { 37 /// The register allocator. 38 pub regalloc: RegAlloc, 39 /// The value stack. 40 pub stack: Stack, 41 /// The current function's frame. 42 pub frame: Frame<P>, 43 /// Reachability state. 44 pub reachable: bool, 45 /// A reference to the VMOffsets. 46 pub vmoffsets: &'a VMOffsets<u8>, 47 } 48 49 impl<'a> CodeGenContext<'a, Emission> { 50 /// Prepares arguments for emitting an i32 shift operation. i32_shift<M>(&mut self, masm: &mut M, kind: ShiftKind) -> Result<()> where M: MacroAssembler,51 pub fn i32_shift<M>(&mut self, masm: &mut M, kind: ShiftKind) -> Result<()> 52 where 53 M: MacroAssembler, 54 { 55 let top = self 56 .stack 57 .peek() 58 .ok_or_else(|| CodeGenError::missing_values_in_stack())?; 59 60 if top.is_i32_const() { 61 let val = self 62 .stack 63 .pop_i32_const() 64 .ok_or_else(|| CodeGenError::missing_values_in_stack())?; 65 let typed_reg = self.pop_to_reg(masm, None)?; 66 masm.shift_ir( 67 writable!(typed_reg.reg), 68 Imm::i32(val), 69 typed_reg.reg, 70 kind, 71 OperandSize::S32, 72 )?; 73 self.stack.push(typed_reg.into()); 74 } else { 75 masm.shift(self, kind, OperandSize::S32)?; 76 } 77 Ok(()) 78 } 79 80 /// Prepares arguments for emitting an i64 binary operation. i64_shift<M>(&mut self, masm: &mut M, kind: ShiftKind) -> Result<()> where M: MacroAssembler,81 pub fn i64_shift<M>(&mut self, masm: &mut M, kind: ShiftKind) -> Result<()> 82 where 83 M: MacroAssembler, 84 { 85 let top = self 86 .stack 87 .peek() 88 .ok_or_else(|| CodeGenError::missing_values_in_stack())?; 89 if top.is_i64_const() { 90 let val = self 91 .stack 92 .pop_i64_const() 93 .ok_or_else(|| CodeGenError::missing_values_in_stack())?; 94 let typed_reg = self.pop_to_reg(masm, None)?; 95 masm.shift_ir( 96 writable!(typed_reg.reg), 97 Imm::i64(val), 98 typed_reg.reg, 99 kind, 100 OperandSize::S64, 101 )?; 102 self.stack.push(typed_reg.into()); 103 } else { 104 masm.shift(self, kind, OperandSize::S64)?; 105 }; 106 107 Ok(()) 108 } 109 } 110 111 impl<'a> CodeGenContext<'a, Prologue> { 112 /// Create a new code generation context. new( regalloc: RegAlloc, stack: Stack, frame: Frame<Prologue>, vmoffsets: &'a VMOffsets<u8>, ) -> Self113 pub fn new( 114 regalloc: RegAlloc, 115 stack: Stack, 116 frame: Frame<Prologue>, 117 vmoffsets: &'a VMOffsets<u8>, 118 ) -> Self { 119 Self { 120 regalloc, 121 stack, 122 frame, 123 reachable: true, 124 vmoffsets, 125 } 126 } 127 128 /// Prepares the frame for the [`Emission`] code generation phase. for_emission(self) -> CodeGenContext<'a, Emission>129 pub fn for_emission(self) -> CodeGenContext<'a, Emission> { 130 CodeGenContext { 131 regalloc: self.regalloc, 132 stack: self.stack, 133 reachable: self.reachable, 134 vmoffsets: self.vmoffsets, 135 frame: self.frame.for_emission(), 136 } 137 } 138 } 139 140 impl<'a> CodeGenContext<'a, Emission> { 141 /// Request a specific register to the register allocator, 142 /// spilling if not available. reg<M: MacroAssembler>(&mut self, named: Reg, masm: &mut M) -> Result<Reg>143 pub fn reg<M: MacroAssembler>(&mut self, named: Reg, masm: &mut M) -> Result<Reg> { 144 self.regalloc.reg(named, |regalloc| { 145 Self::spill_impl(&mut self.stack, regalloc, &self.frame, masm) 146 }) 147 } 148 149 /// Allocate a register for the given WebAssembly type. reg_for_type<M: MacroAssembler>( &mut self, ty: WasmValType, masm: &mut M, ) -> Result<Reg>150 pub fn reg_for_type<M: MacroAssembler>( 151 &mut self, 152 ty: WasmValType, 153 masm: &mut M, 154 ) -> Result<Reg> { 155 use WasmValType::*; 156 match ty { 157 I32 | I64 => self.reg_for_class(RegClass::Int, masm), 158 F32 | F64 => self.reg_for_class(RegClass::Float, masm), 159 // All of our supported architectures use the float registers for vector operations. 160 V128 => self.reg_for_class(RegClass::Float, masm), 161 Ref(rt) => match rt.heap_type { 162 WasmHeapType::Func | WasmHeapType::Extern => { 163 self.reg_for_class(RegClass::Int, masm) 164 } 165 _ => bail!(CodeGenError::unsupported_wasm_type()), 166 }, 167 } 168 } 169 170 /// Request the register allocator to provide the next available 171 /// register of the specified class. reg_for_class<M: MacroAssembler>( &mut self, class: RegClass, masm: &mut M, ) -> Result<Reg>172 pub fn reg_for_class<M: MacroAssembler>( 173 &mut self, 174 class: RegClass, 175 masm: &mut M, 176 ) -> Result<Reg> { 177 self.regalloc.reg_for_class(class, &mut |regalloc| { 178 Self::spill_impl(&mut self.stack, regalloc, &self.frame, masm) 179 }) 180 } 181 182 /// Convenience wrapper around `CodeGenContext::reg_for_class`, to 183 /// request the next available general purpose register. any_gpr<M: MacroAssembler>(&mut self, masm: &mut M) -> Result<Reg>184 pub fn any_gpr<M: MacroAssembler>(&mut self, masm: &mut M) -> Result<Reg> { 185 self.reg_for_class(RegClass::Int, masm) 186 } 187 188 /// Convenience wrapper around `CodeGenContext::reg_for_class`, to 189 /// request the next available floating point register. any_fpr<M: MacroAssembler>(&mut self, masm: &mut M) -> Result<Reg>190 pub fn any_fpr<M: MacroAssembler>(&mut self, masm: &mut M) -> Result<Reg> { 191 self.reg_for_class(RegClass::Float, masm) 192 } 193 194 /// Executes the provided function, guaranteeing that the specified set of 195 /// registers, if any, remain unallocatable throughout the function's 196 /// execution. without<'r, T, M, F>( &mut self, regs: impl IntoIterator<Item = &'r Reg> + Copy, masm: &mut M, mut f: F, ) -> Result<T> where M: MacroAssembler, F: FnMut(&mut Self, &mut M) -> T,197 pub fn without<'r, T, M, F>( 198 &mut self, 199 regs: impl IntoIterator<Item = &'r Reg> + Copy, 200 masm: &mut M, 201 mut f: F, 202 ) -> Result<T> 203 where 204 M: MacroAssembler, 205 F: FnMut(&mut Self, &mut M) -> T, 206 { 207 for r in regs { 208 self.reg(*r, masm)?; 209 } 210 211 let result = f(self, masm); 212 213 for r in regs { 214 self.free_reg(*r); 215 } 216 217 Ok(result) 218 } 219 220 /// Free the given register. free_reg(&mut self, reg: impl Into<Reg>)221 pub fn free_reg(&mut self, reg: impl Into<Reg>) { 222 let reg: Reg = reg.into(); 223 self.regalloc.free(reg); 224 } 225 226 /// Loads the stack top value into the next available register, if 227 /// it isn't already one; spilling if there are no registers 228 /// available. Optionally the caller may specify a specific 229 /// destination register. 230 /// When a named register is requested and it's not at the top of the 231 /// stack a move from register to register might happen, in which case 232 /// the source register will be freed. pop_to_reg<M: MacroAssembler>( &mut self, masm: &mut M, named: Option<Reg>, ) -> Result<TypedReg>233 pub fn pop_to_reg<M: MacroAssembler>( 234 &mut self, 235 masm: &mut M, 236 named: Option<Reg>, 237 ) -> Result<TypedReg> { 238 let typed_reg = if let Some(dst) = named { 239 self.stack.pop_named_reg(dst) 240 } else { 241 self.stack.pop_reg() 242 }; 243 244 if let Some(dst) = typed_reg { 245 return Ok(dst); 246 } 247 248 let val = self.stack.pop().expect("a value at stack top"); 249 let reg = if let Some(r) = named { 250 self.reg(r, masm)? 251 } else { 252 self.reg_for_type(val.ty(), masm)? 253 }; 254 255 if val.is_mem() { 256 let mem = val.unwrap_mem(); 257 let curr_offset = masm.sp_offset()?.as_u32(); 258 let slot_offset = mem.slot.offset.as_u32(); 259 ensure!( 260 curr_offset == slot_offset, 261 CodeGenError::invalid_sp_offset(), 262 ); 263 masm.pop(writable!(reg), val.ty().try_into()?)?; 264 } else { 265 self.move_val_to_reg(&val, reg, masm)?; 266 // Free the source value if it is a register. 267 if val.is_reg() { 268 self.free_reg(val.unwrap_reg()); 269 } 270 } 271 272 Ok(TypedReg::new(val.ty(), reg)) 273 } 274 275 /// Pops the value stack top and stores it at the specified address. pop_to_addr<M: MacroAssembler>(&mut self, masm: &mut M, addr: M::Address) -> Result<()>276 pub fn pop_to_addr<M: MacroAssembler>(&mut self, masm: &mut M, addr: M::Address) -> Result<()> { 277 let val = self.stack.pop().expect("a value at stack top"); 278 let ty = val.ty(); 279 let size: OperandSize = ty.try_into()?; 280 match val { 281 Val::Reg(tr) => { 282 masm.store(tr.reg.into(), addr, size)?; 283 self.free_reg(tr.reg); 284 } 285 Val::I32(v) => masm.store(RegImm::i32(v), addr, size)?, 286 Val::I64(v) => masm.store(RegImm::i64(v), addr, size)?, 287 Val::F32(v) => masm.store(RegImm::f32(v.bits()), addr, size)?, 288 Val::F64(v) => masm.store(RegImm::f64(v.bits()), addr, size)?, 289 Val::V128(v) => masm.store(RegImm::v128(v), addr, size)?, 290 Val::Local(local) => { 291 let slot = self.frame.get_wasm_local(local.index); 292 let local_addr = masm.local_address(&slot)?; 293 masm.with_scratch::<IntScratch, _>(|masm, scratch| { 294 masm.load(local_addr, scratch.writable(), size)?; 295 masm.store(scratch.inner().into(), addr, size) 296 })?; 297 } 298 Val::Memory(_) => { 299 masm.with_scratch_for(ty, |masm, scratch| { 300 masm.pop(scratch.writable(), size)?; 301 masm.store(scratch.inner().into(), addr, size) 302 })?; 303 } 304 } 305 306 Ok(()) 307 } 308 309 /// Move a stack value to the given register. move_val_to_reg<M: MacroAssembler>( &self, src: &Val, dst: Reg, masm: &mut M, ) -> Result<()>310 pub fn move_val_to_reg<M: MacroAssembler>( 311 &self, 312 src: &Val, 313 dst: Reg, 314 masm: &mut M, 315 ) -> Result<()> { 316 let size: OperandSize = src.ty().try_into()?; 317 match src { 318 Val::Reg(tr) => masm.mov(writable!(dst), RegImm::reg(tr.reg), size), 319 Val::I32(imm) => masm.mov(writable!(dst), RegImm::i32(*imm), size), 320 Val::I64(imm) => masm.mov(writable!(dst), RegImm::i64(*imm), size), 321 Val::F32(imm) => masm.mov(writable!(dst), RegImm::f32(imm.bits()), size), 322 Val::F64(imm) => masm.mov(writable!(dst), RegImm::f64(imm.bits()), size), 323 Val::V128(imm) => masm.mov(writable!(dst), RegImm::v128(*imm), size), 324 Val::Local(local) => { 325 let slot = self.frame.get_wasm_local(local.index); 326 let addr = masm.local_address(&slot)?; 327 masm.load(addr, writable!(dst), size) 328 } 329 Val::Memory(mem) => { 330 let addr = masm.address_from_sp(mem.slot.offset)?; 331 masm.load(addr, writable!(dst), size) 332 } 333 } 334 } 335 336 /// Prepares arguments for emitting a unary operation. 337 /// 338 /// The `emit` function returns the `TypedReg` to put on the value stack. unop<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> where F: FnOnce(&mut M, Reg) -> Result<TypedReg>, M: MacroAssembler,339 pub fn unop<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> 340 where 341 F: FnOnce(&mut M, Reg) -> Result<TypedReg>, 342 M: MacroAssembler, 343 { 344 let typed_reg = self.pop_to_reg(masm, None)?; 345 let dst = emit(masm, typed_reg.reg)?; 346 self.stack.push(dst.into()); 347 348 Ok(()) 349 } 350 351 /// Prepares arguments for emitting a binary operation. 352 /// 353 /// The `emit` function returns the `TypedReg` to put on the value stack. binop<F, M>(&mut self, masm: &mut M, size: OperandSize, emit: F) -> Result<()> where F: FnOnce(&mut M, Reg, Reg, OperandSize) -> Result<TypedReg>, M: MacroAssembler,354 pub fn binop<F, M>(&mut self, masm: &mut M, size: OperandSize, emit: F) -> Result<()> 355 where 356 F: FnOnce(&mut M, Reg, Reg, OperandSize) -> Result<TypedReg>, 357 M: MacroAssembler, 358 { 359 let src = self.pop_to_reg(masm, None)?; 360 let dst = self.pop_to_reg(masm, None)?; 361 let dst = emit(masm, dst.reg, src.reg, size)?; 362 self.free_reg(src); 363 self.stack.push(dst.into()); 364 365 Ok(()) 366 } 367 368 /// Prepares arguments for emitting an f32 or f64 comparison operation. float_cmp_op<F, M>(&mut self, masm: &mut M, size: OperandSize, emit: F) -> Result<()> where F: FnOnce(&mut M, Reg, Reg, Reg, OperandSize) -> Result<()>, M: MacroAssembler,369 pub fn float_cmp_op<F, M>(&mut self, masm: &mut M, size: OperandSize, emit: F) -> Result<()> 370 where 371 F: FnOnce(&mut M, Reg, Reg, Reg, OperandSize) -> Result<()>, 372 M: MacroAssembler, 373 { 374 let src2 = self.pop_to_reg(masm, None)?; 375 let src1 = self.pop_to_reg(masm, None)?; 376 let dst = self.any_gpr(masm)?; 377 emit(masm, dst, src1.reg, src2.reg, size)?; 378 self.free_reg(src1); 379 self.free_reg(src2); 380 381 let dst = match size { 382 // Float comparison operators are defined as 383 // [f64 f64] -> i32 384 // https://webassembly.github.io/spec/core/appendix/index-instructions.html 385 OperandSize::S32 | OperandSize::S64 => TypedReg::i32(dst), 386 OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => { 387 bail!(CodeGenError::unexpected_operand_size()) 388 } 389 }; 390 self.stack.push(dst.into()); 391 392 Ok(()) 393 } 394 395 /// Prepares arguments for emitting an i32 binary operation. 396 /// 397 /// The `emit` function returns the `TypedReg` to put on the value stack. i32_binop<F, M>(&mut self, masm: &mut M, mut emit: F) -> Result<()> where F: FnMut(&mut M, Reg, RegImm, OperandSize) -> Result<TypedReg>, M: MacroAssembler,398 pub fn i32_binop<F, M>(&mut self, masm: &mut M, mut emit: F) -> Result<()> 399 where 400 F: FnMut(&mut M, Reg, RegImm, OperandSize) -> Result<TypedReg>, 401 M: MacroAssembler, 402 { 403 match self.pop_i32_const() { 404 Some(val) => { 405 let typed_reg = self.pop_to_reg(masm, None)?; 406 let dst = emit(masm, typed_reg.reg, RegImm::i32(val), OperandSize::S32)?; 407 self.stack.push(dst.into()); 408 } 409 None => self.binop(masm, OperandSize::S32, |masm, dst, src, size| { 410 emit(masm, dst, src.into(), size) 411 })?, 412 } 413 Ok(()) 414 } 415 416 /// Prepares arguments for emitting an i64 binary operation. 417 /// 418 /// The `emit` function returns the `TypedReg` to put on the value stack. i64_binop<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> where F: FnOnce(&mut M, Reg, RegImm, OperandSize) -> Result<TypedReg>, M: MacroAssembler,419 pub fn i64_binop<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> 420 where 421 F: FnOnce(&mut M, Reg, RegImm, OperandSize) -> Result<TypedReg>, 422 M: MacroAssembler, 423 { 424 match self.pop_i64_const() { 425 Some(val) => { 426 let typed_reg = self.pop_to_reg(masm, None)?; 427 let dst = emit(masm, typed_reg.reg, RegImm::i64(val), OperandSize::S64)?; 428 self.stack.push(dst.into()); 429 } 430 None => self.binop(masm, OperandSize::S64, |masm, dst, src, size| { 431 emit(masm, dst, src.into(), size) 432 })?, 433 } 434 Ok(()) 435 } 436 437 /// Returns the i32 const on top of the stack or None if there isn't one. pop_i32_const(&mut self) -> Option<i32>438 pub fn pop_i32_const(&mut self) -> Option<i32> { 439 let top = self.stack.peek().expect("value at stack top"); 440 441 if top.is_i32_const() { 442 let val = self 443 .stack 444 .pop_i32_const() 445 .expect("i32 const value at stack top"); 446 Some(val) 447 } else { 448 None 449 } 450 } 451 452 /// Returns the i64 const on top of the stack or None if there isn't one. pop_i64_const(&mut self) -> Option<i64>453 pub fn pop_i64_const(&mut self) -> Option<i64> { 454 let top = self.stack.peek().expect("value at stack top"); 455 456 if top.is_i64_const() { 457 let val = self 458 .stack 459 .pop_i64_const() 460 .expect("i64 const value at stack top"); 461 Some(val) 462 } else { 463 None 464 } 465 } 466 467 /// Returns the f32 const on top of the stack or None if there isn't one. pop_f32_const(&mut self) -> Option<Ieee32>468 pub fn pop_f32_const(&mut self) -> Option<Ieee32> { 469 let top = self.stack.peek().expect("value at stack top"); 470 471 if top.is_f32_const() { 472 let val = self 473 .stack 474 .pop_f32_const() 475 .expect("f32 const value at stack top"); 476 Some(val) 477 } else { 478 None 479 } 480 } 481 482 /// Returns the f64 const on top of the stack or None if there isn't one. pop_f64_const(&mut self) -> Option<Ieee64>483 pub fn pop_f64_const(&mut self) -> Option<Ieee64> { 484 let top = self.stack.peek().expect("value at stack top"); 485 486 if top.is_f64_const() { 487 let val = self 488 .stack 489 .pop_f64_const() 490 .expect("f64 const value at stack top"); 491 Some(val) 492 } else { 493 None 494 } 495 } 496 497 /// Prepares arguments for emitting a convert operation. convert_op<F, M>(&mut self, masm: &mut M, dst_ty: WasmValType, emit: F) -> Result<()> where F: FnOnce(&mut M, Reg, Reg, OperandSize) -> Result<()>, M: MacroAssembler,498 pub fn convert_op<F, M>(&mut self, masm: &mut M, dst_ty: WasmValType, emit: F) -> Result<()> 499 where 500 F: FnOnce(&mut M, Reg, Reg, OperandSize) -> Result<()>, 501 M: MacroAssembler, 502 { 503 let src = self.pop_to_reg(masm, None)?; 504 let dst = self.reg_for_type(dst_ty, masm)?; 505 let dst_size = match dst_ty { 506 WasmValType::I32 => OperandSize::S32, 507 WasmValType::I64 => OperandSize::S64, 508 WasmValType::F32 => OperandSize::S32, 509 WasmValType::F64 => OperandSize::S64, 510 WasmValType::V128 => bail!(CodeGenError::unsupported_wasm_type()), 511 WasmValType::Ref(_) => bail!(CodeGenError::unsupported_wasm_type()), 512 }; 513 514 emit(masm, dst, src.into(), dst_size)?; 515 516 self.free_reg(src); 517 self.stack.push(TypedReg::new(dst_ty, dst).into()); 518 Ok(()) 519 } 520 521 /// Prepares arguments for emitting a convert operation with a temporary 522 /// register. convert_op_with_tmp_reg<F, M>( &mut self, masm: &mut M, dst_ty: WasmValType, tmp_reg_class: RegClass, emit: F, ) -> Result<()> where F: FnOnce(&mut M, Reg, Reg, Reg, OperandSize) -> Result<()>, M: MacroAssembler,523 pub fn convert_op_with_tmp_reg<F, M>( 524 &mut self, 525 masm: &mut M, 526 dst_ty: WasmValType, 527 tmp_reg_class: RegClass, 528 emit: F, 529 ) -> Result<()> 530 where 531 F: FnOnce(&mut M, Reg, Reg, Reg, OperandSize) -> Result<()>, 532 M: MacroAssembler, 533 { 534 let tmp_gpr = self.reg_for_class(tmp_reg_class, masm)?; 535 self.convert_op(masm, dst_ty, |masm, dst, src, dst_size| { 536 emit(masm, dst, src, tmp_gpr, dst_size) 537 })?; 538 self.free_reg(tmp_gpr); 539 Ok(()) 540 } 541 542 /// Prepares arguments for emitting an extract lane operation. extract_lane_op<F, M>( &mut self, masm: &mut M, kind: ExtractLaneKind, emit: F, ) -> Result<()> where F: FnOnce(&mut M, Reg, WritableReg, ExtractLaneKind) -> Result<()>, M: MacroAssembler,543 pub fn extract_lane_op<F, M>( 544 &mut self, 545 masm: &mut M, 546 kind: ExtractLaneKind, 547 emit: F, 548 ) -> Result<()> 549 where 550 F: FnOnce(&mut M, Reg, WritableReg, ExtractLaneKind) -> Result<()>, 551 M: MacroAssembler, 552 { 553 let src = self.pop_to_reg(masm, None)?; 554 let dst = writable!(match kind { 555 ExtractLaneKind::I8x16S 556 | ExtractLaneKind::I8x16U 557 | ExtractLaneKind::I16x8S 558 | ExtractLaneKind::I16x8U 559 | ExtractLaneKind::I32x4 560 | ExtractLaneKind::I64x2 => self.any_gpr(masm)?, 561 ExtractLaneKind::F32x4 | ExtractLaneKind::F64x2 => src.reg, 562 }); 563 564 emit(masm, src.reg, dst, kind)?; 565 566 match kind { 567 ExtractLaneKind::I8x16S 568 | ExtractLaneKind::I8x16U 569 | ExtractLaneKind::I16x8S 570 | ExtractLaneKind::I16x8U 571 | ExtractLaneKind::I32x4 572 | ExtractLaneKind::I64x2 => self.free_reg(src), 573 _ => (), 574 } 575 576 let dst = dst.to_reg(); 577 let dst = match kind { 578 ExtractLaneKind::I8x16S 579 | ExtractLaneKind::I8x16U 580 | ExtractLaneKind::I16x8S 581 | ExtractLaneKind::I16x8U 582 | ExtractLaneKind::I32x4 => TypedReg::i32(dst), 583 ExtractLaneKind::I64x2 => TypedReg::i64(dst), 584 ExtractLaneKind::F32x4 => TypedReg::f32(dst), 585 ExtractLaneKind::F64x2 => TypedReg::f64(dst), 586 }; 587 588 self.stack.push(Val::Reg(dst)); 589 Ok(()) 590 } 591 592 /// Prepares arguments for emitting a replace lane operation. replace_lane_op<F, M>( &mut self, masm: &mut M, kind: ReplaceLaneKind, emit: F, ) -> Result<()> where F: FnOnce(&mut M, RegImm, WritableReg, ReplaceLaneKind) -> Result<()>, M: MacroAssembler,593 pub fn replace_lane_op<F, M>( 594 &mut self, 595 masm: &mut M, 596 kind: ReplaceLaneKind, 597 emit: F, 598 ) -> Result<()> 599 where 600 F: FnOnce(&mut M, RegImm, WritableReg, ReplaceLaneKind) -> Result<()>, 601 M: MacroAssembler, 602 { 603 let src = match kind { 604 ReplaceLaneKind::I8x16 | ReplaceLaneKind::I16x8 | ReplaceLaneKind::I32x4 => { 605 self.pop_i32_const().map(RegImm::i32) 606 } 607 ReplaceLaneKind::I64x2 => self.pop_i64_const().map(RegImm::i64), 608 ReplaceLaneKind::F32x4 => self.pop_f32_const().map(|v| RegImm::f32(v.bits())), 609 ReplaceLaneKind::F64x2 => self.pop_f64_const().map(|v| RegImm::f64(v.bits())), 610 } 611 .map_or_else( 612 || Ok(RegImm::reg(self.pop_to_reg(masm, None)?.into())), 613 Ok::<_, crate::Error>, 614 )?; 615 616 let dst = self.pop_to_reg(masm, None)?; 617 618 emit(masm, src, writable!(dst.into()), kind)?; 619 620 if let RegImm::Reg(reg) = src { 621 self.free_reg(reg); 622 } 623 self.stack.push(dst.into()); 624 625 Ok(()) 626 } 627 628 /// Drops the last `n` elements of the stack, calling the provided 629 /// function for each `n` stack value. 630 /// The values are dropped in top-to-bottom order. drop_last<F>(&mut self, last: usize, mut f: F) -> Result<()> where F: FnMut(&mut RegAlloc, &Val) -> Result<()>,631 pub fn drop_last<F>(&mut self, last: usize, mut f: F) -> Result<()> 632 where 633 F: FnMut(&mut RegAlloc, &Val) -> Result<()>, 634 { 635 if last > 0 { 636 let len = self.stack.len(); 637 ensure!(last <= len, CodeGenError::unexpected_value_stack_index(),); 638 let truncate = self.stack.len() - last; 639 let stack_mut = self.stack.inner_mut(); 640 641 // Invoke the callback in top-to-bottom order. 642 for v in stack_mut[truncate..].into_iter().rev() { 643 f(&mut self.regalloc, v)? 644 } 645 stack_mut.truncate(truncate); 646 } 647 648 Ok(()) 649 } 650 651 /// Convenience wrapper around [`Self::spill_callback`]. 652 /// 653 /// This function exists for cases in which triggering an unconditional 654 /// spill is needed, like before entering control flow. spill<M: MacroAssembler>(&mut self, masm: &mut M) -> Result<()>655 pub fn spill<M: MacroAssembler>(&mut self, masm: &mut M) -> Result<()> { 656 Self::spill_impl(&mut self.stack, &mut self.regalloc, &self.frame, masm) 657 } 658 659 /// Prepares the compiler to branch to the given destination 660 /// frame. 661 /// This process involves: 662 /// * Balancing the machine stack pointer and value stack by 663 /// popping it to match the destination branch. 664 /// * Updating the reachability state. 665 /// * Marking the destination frame as a destination target. br<M, F, B>( &mut self, dest: &mut ControlStackFrame, masm: &mut M, mut maybe_pop_results: F, ) -> Result<()> where M: MacroAssembler, F: FnMut(&mut M, &mut Self, &mut ControlStackFrame) -> Result<()>, B: BranchState,666 pub fn br<M, F, B>( 667 &mut self, 668 dest: &mut ControlStackFrame, 669 masm: &mut M, 670 mut maybe_pop_results: F, 671 ) -> Result<()> 672 where 673 M: MacroAssembler, 674 F: FnMut(&mut M, &mut Self, &mut ControlStackFrame) -> Result<()>, 675 B: BranchState, 676 { 677 let state = dest.stack_state(); 678 let target_offset = state.target_offset; 679 let base_offset = state.base_offset; 680 let results_size = dest.results::<M>()?.size(); 681 682 maybe_pop_results(masm, self, dest)?; 683 // After calling `maybe_pop_results`, the stack pointer plus 684 // any result space needed, must be greater or equal to the 685 // destination frame base stack pointer offset. 686 // 687 // We check 688 // current_sp + results >= base_offset 689 // as opposed to 690 // current_sp >= base_offset 691 // 692 // To: 693 // - Verify that `maybe_pop_results` popped exactly the right 694 // amount relative to the base offset. 695 // - Accommodate for multi-branch cases (i.e., `br_table`) in which 696 // result handling happens only once and _could_ happen outside of 697 // `maybe_pop_results` callback. 698 // 699 // 700 // Ensuring that the current stack pointer offset plus any 701 // result space is equal to or greater than the target branch 702 // base offset is the the most deterministic check at branch 703 // emission time since we can be certain that the base offset 704 // is the value recorded when a new control frame was pushed, 705 // upon which the expected target offset is calculated. 706 ensure!( 707 (masm.sp_offset()?.as_u32() + results_size) >= base_offset.as_u32(), 708 CodeGenError::invalid_sp_offset() 709 ); 710 711 // At jump sites, the machine stack might be left unbalanced, 712 // due to register spills. 713 // The following snippet, pops the stack pointer to ensure 714 // that it is correctly placed according to the expectations 715 // of the destination branch. 716 // 717 // Note that in most branch cases (`return`, ` br`) the stack 718 // pointer will be already balanced, by virtue of calling 719 // [`ControlStackFrame::pop_abi_results`] through the 720 // callback. 721 // 722 // More generally speaking the current stack pointer will be 723 // less than the destination frame stack pointer offset in 724 // cases in which the top value in the value stack is a memory 725 // entry which needs to be popped into the return location 726 // according to the ABI (a register for single value returns 727 // and a memory slot for 1+ returns). 728 // 729 // Stack balancing is mostly required for WebAssembly 730 // instructions that deal with multiple destination branches 731 // (e.g., `br_table`) or fall-through scenarios (e.g., 732 // `br_if`). In order to ensure that multi-value returns are 733 // handled correctly we ensure that correct placing of stack 734 // results by emitting a [`MacroAssembler::memmove`] 735 // instruction, prior to claiming any excess stack space. 736 // 737 // Depending on the branch state, the compiler might enter in an 738 // unreachable state; instead of immediately truncating the value stack 739 // to the expected length of the destination branch, we let the 740 // reachability analysis code decide what should happen with the length 741 // of the value stack once reachability is actually restored. At that 742 // point, the right stack pointer offset will also be restored, which 743 // should match the contents of the value stack. 744 if dest.unbalanced::<M>(masm)? { 745 masm.memmove( 746 masm.sp_offset()?, 747 target_offset, 748 results_size, 749 MemMoveDirection::LowToHigh, 750 )?; 751 } 752 masm.ensure_sp_for_jump(target_offset)?; 753 dest.set_as_target(); 754 masm.jmp(*dest.label())?; 755 if B::unreachable_state_after_emission() { 756 self.reachable = false; 757 } 758 Ok(()) 759 } 760 761 /// Push the ABI representation of the results stack. push_abi_results<M, F>( &mut self, results: &ABIResults, masm: &mut M, mut calculate_ret_area: F, ) -> Result<()> where M: MacroAssembler, F: FnMut(&ABIResults, &mut CodeGenContext<Emission>, &mut M) -> Option<RetArea>,762 pub fn push_abi_results<M, F>( 763 &mut self, 764 results: &ABIResults, 765 masm: &mut M, 766 mut calculate_ret_area: F, 767 ) -> Result<()> 768 where 769 M: MacroAssembler, 770 F: FnMut(&ABIResults, &mut CodeGenContext<Emission>, &mut M) -> Option<RetArea>, 771 { 772 let area = results 773 .on_stack() 774 .then(|| calculate_ret_area(&results, self, masm).unwrap()); 775 776 for operand in results.operands().iter() { 777 match operand { 778 ABIOperand::Reg { reg, ty, .. } => { 779 ensure!( 780 self.regalloc.reg_available(*reg), 781 CodeGenError::expected_register_to_be_available(), 782 ); 783 784 let typed_reg = TypedReg::new(*ty, self.reg(*reg, masm)?); 785 self.stack.push(typed_reg.into()); 786 } 787 ABIOperand::Stack { ty, offset, size } => match area.unwrap() { 788 RetArea::SP(sp_offset) => { 789 let slot = 790 StackSlot::new(SPOffset::from_u32(sp_offset.as_u32() - offset), *size); 791 self.stack.push(Val::mem(*ty, slot)); 792 } 793 // This function is only expected to be called when dealing 794 // with control flow and when calling functions; as a 795 // callee, only [Self::pop_abi_results] is needed when 796 // finalizing the function compilation. 797 _ => bail!(CodeGenError::unexpected_function_call()), 798 }, 799 } 800 } 801 802 Ok(()) 803 } 804 805 /// Truncates the value stack to the specified target. 806 /// This function is intended to only be used when restoring the code 807 /// generation's reachability state, when handling an unreachable end or 808 /// else. truncate_stack_to(&mut self, target: usize) -> Result<()>809 pub fn truncate_stack_to(&mut self, target: usize) -> Result<()> { 810 if self.stack.len() > target { 811 self.drop_last(self.stack.len() - target, |regalloc, val| match val { 812 Val::Reg(tr) => Ok(regalloc.free(tr.reg)), 813 _ => Ok(()), 814 }) 815 } else { 816 Ok(()) 817 } 818 } 819 820 /// Load the [VMContext] pointer into the designated pinned register. load_vmctx<M>(&mut self, masm: &mut M) -> Result<()> where M: MacroAssembler,821 pub fn load_vmctx<M>(&mut self, masm: &mut M) -> Result<()> 822 where 823 M: MacroAssembler, 824 { 825 let addr = masm.local_address(&self.frame.vmctx_slot())?; 826 masm.load_ptr(addr, writable!(vmctx!(M))) 827 } 828 829 /// Spill locals and registers to memory. 830 // TODO: optimize the spill range; 831 // At any point in the program, the stack might already contain memory 832 // entries; we could effectively ignore that range; only focusing on the 833 // range that contains spillable values. spill_impl<M: MacroAssembler>( stack: &mut Stack, regalloc: &mut RegAlloc, frame: &Frame<Emission>, masm: &mut M, ) -> Result<()>834 fn spill_impl<M: MacroAssembler>( 835 stack: &mut Stack, 836 regalloc: &mut RegAlloc, 837 frame: &Frame<Emission>, 838 masm: &mut M, 839 ) -> Result<()> { 840 for v in stack.inner_mut() { 841 match v { 842 Val::Reg(r) => { 843 let slot = masm.push(r.reg, r.ty.try_into()?)?; 844 regalloc.free(r.reg); 845 *v = Val::mem(r.ty, slot); 846 } 847 Val::Local(local) => { 848 let slot = frame.get_wasm_local(local.index); 849 let addr = masm.local_address(&slot)?; 850 masm.with_scratch_for(slot.ty, |masm, scratch| { 851 masm.load(addr, scratch.writable(), slot.ty.try_into()?)?; 852 let stack_slot = masm.push(scratch.inner(), slot.ty.try_into()?)?; 853 *v = Val::mem(slot.ty, stack_slot); 854 wasmtime_environ::error::Ok(()) 855 })?; 856 } 857 _ => {} 858 } 859 } 860 861 Ok(()) 862 } 863 864 /// Prepares for emitting a binary operation where four 64-bit operands are 865 /// used to produce two 64-bit operands, e.g. a 128-bit binop. binop128<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> where F: FnOnce(&mut M, Reg, Reg, Reg, Reg) -> Result<(TypedReg, TypedReg)>, M: MacroAssembler,866 pub fn binop128<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> 867 where 868 F: FnOnce(&mut M, Reg, Reg, Reg, Reg) -> Result<(TypedReg, TypedReg)>, 869 M: MacroAssembler, 870 { 871 let rhs_hi = self.pop_to_reg(masm, None)?; 872 let rhs_lo = self.pop_to_reg(masm, None)?; 873 let lhs_hi = self.pop_to_reg(masm, None)?; 874 let lhs_lo = self.pop_to_reg(masm, None)?; 875 let (lo, hi) = emit(masm, lhs_lo.reg, lhs_hi.reg, rhs_lo.reg, rhs_hi.reg)?; 876 self.free_reg(rhs_hi); 877 self.free_reg(rhs_lo); 878 self.stack.push(lo.into()); 879 self.stack.push(hi.into()); 880 881 Ok(()) 882 } 883 884 /// Prepares to emit a vector `all_true` operation. v128_all_true_op<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> where F: FnOnce(&mut M, Reg, Reg) -> Result<()>, M: MacroAssembler,885 pub fn v128_all_true_op<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> 886 where 887 F: FnOnce(&mut M, Reg, Reg) -> Result<()>, 888 M: MacroAssembler, 889 { 890 let src = self.pop_to_reg(masm, None)?; 891 let dst = self.any_gpr(masm)?; 892 emit(masm, src.reg, dst)?; 893 self.free_reg(src); 894 self.stack.push(TypedReg::i32(dst).into()); 895 896 Ok(()) 897 } 898 899 /// Prepares to emit a vector `bitmask` operation. v128_bitmask_op<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> where F: FnOnce(&mut M, Reg, Reg) -> Result<()>, M: MacroAssembler,900 pub fn v128_bitmask_op<F, M>(&mut self, masm: &mut M, emit: F) -> Result<()> 901 where 902 F: FnOnce(&mut M, Reg, Reg) -> Result<()>, 903 M: MacroAssembler, 904 { 905 let src = self.pop_to_reg(masm, None)?; 906 let dst = self.any_gpr(masm)?; 907 emit(masm, src.reg, dst)?; 908 self.free_reg(src); 909 self.stack.push(TypedReg::i32(dst).into()); 910 911 Ok(()) 912 } 913 914 /// Pops a register from the stack and then immediately frees it. Used to 915 /// discard values from the last operation, for example. pop_and_free<M: MacroAssembler>(&mut self, masm: &mut M) -> Result<()>916 pub fn pop_and_free<M: MacroAssembler>(&mut self, masm: &mut M) -> Result<()> { 917 let reg = self.pop_to_reg(masm, None)?; 918 self.free_reg(reg.reg); 919 Ok(()) 920 } 921 } 922