1 //! Support for compiling with Cranelift. 2 //! 3 //! This crate provides an implementation of the `wasmtime_environ::Compiler` 4 //! and `wasmtime_environ::CompilerBuilder` traits. 5 //! 6 //! > **⚠️ Warning ⚠️**: this crate is an internal-only crate for the Wasmtime 7 //! > project and is not intended for general use. APIs are not strictly 8 //! > reviewed for safety and usage outside of Wasmtime may have bugs. If 9 //! > you're interested in using this feel free to file an issue on the 10 //! > Wasmtime repository to start a discussion about doing so, but otherwise 11 //! > be aware that your usage of this crate is not supported. 12 13 // See documentation in crates/wasmtime/src/runtime.rs for why this is 14 // selectively enabled here. 15 #![warn(clippy::cast_possible_truncation, clippy::cast_sign_loss)] 16 17 use cranelift_codegen::{ 18 FinalizedMachReloc, FinalizedRelocTarget, MachTrap, binemit, 19 cursor::FuncCursor, 20 ir::{self, AbiParam, ArgumentPurpose, ExternalName, InstBuilder, Signature, TrapCode}, 21 isa::{CallConv, TargetIsa}, 22 settings, 23 }; 24 use cranelift_entity::PrimaryMap; 25 26 use target_lexicon::Architecture; 27 use wasmtime_environ::{ 28 BuiltinFunctionIndex, FlagValue, FuncKey, Trap, TrapInformation, Tunables, WasmFuncType, 29 WasmHeapTopType, WasmHeapType, WasmValType, 30 }; 31 32 pub use builder::builder; 33 34 pub mod isa_builder; 35 mod obj; 36 pub use obj::*; 37 mod compiled_function; 38 pub use compiled_function::*; 39 40 mod bounds_checks; 41 mod builder; 42 mod compiler; 43 mod debug; 44 mod func_environ; 45 mod translate; 46 47 use self::compiler::Compiler; 48 49 const TRAP_INTERNAL_ASSERT: TrapCode = TrapCode::unwrap_user(1); 50 const TRAP_OFFSET: u8 = 2; 51 pub const TRAP_CANNOT_LEAVE_COMPONENT: TrapCode = 52 TrapCode::unwrap_user(Trap::CannotLeaveComponent as u8 + TRAP_OFFSET); 53 pub const TRAP_INDIRECT_CALL_TO_NULL: TrapCode = 54 TrapCode::unwrap_user(Trap::IndirectCallToNull as u8 + TRAP_OFFSET); 55 pub const TRAP_BAD_SIGNATURE: TrapCode = 56 TrapCode::unwrap_user(Trap::BadSignature as u8 + TRAP_OFFSET); 57 pub const TRAP_NULL_REFERENCE: TrapCode = 58 TrapCode::unwrap_user(Trap::NullReference as u8 + TRAP_OFFSET); 59 pub const TRAP_ALLOCATION_TOO_LARGE: TrapCode = 60 TrapCode::unwrap_user(Trap::AllocationTooLarge as u8 + TRAP_OFFSET); 61 pub const TRAP_ARRAY_OUT_OF_BOUNDS: TrapCode = 62 TrapCode::unwrap_user(Trap::ArrayOutOfBounds as u8 + TRAP_OFFSET); 63 pub const TRAP_UNREACHABLE: TrapCode = 64 TrapCode::unwrap_user(Trap::UnreachableCodeReached as u8 + TRAP_OFFSET); 65 pub const TRAP_HEAP_MISALIGNED: TrapCode = 66 TrapCode::unwrap_user(Trap::HeapMisaligned as u8 + TRAP_OFFSET); 67 pub const TRAP_TABLE_OUT_OF_BOUNDS: TrapCode = 68 TrapCode::unwrap_user(Trap::TableOutOfBounds as u8 + TRAP_OFFSET); 69 pub const TRAP_UNHANDLED_TAG: TrapCode = 70 TrapCode::unwrap_user(Trap::UnhandledTag as u8 + TRAP_OFFSET); 71 pub const TRAP_CONTINUATION_ALREADY_CONSUMED: TrapCode = 72 TrapCode::unwrap_user(Trap::ContinuationAlreadyConsumed as u8 + TRAP_OFFSET); 73 pub const TRAP_CAST_FAILURE: TrapCode = 74 TrapCode::unwrap_user(Trap::CastFailure as u8 + TRAP_OFFSET); 75 76 /// Creates a new cranelift `Signature` with no wasm params/results for the 77 /// given calling convention. 78 /// 79 /// This will add the default vmctx/etc parameters to the signature returned. 80 fn blank_sig(isa: &dyn TargetIsa, call_conv: CallConv) -> ir::Signature { 81 let pointer_type = isa.pointer_type(); 82 let mut sig = ir::Signature::new(call_conv); 83 // Add the caller/callee `vmctx` parameters. 84 sig.params.push(ir::AbiParam::special( 85 pointer_type, 86 ir::ArgumentPurpose::VMContext, 87 )); 88 sig.params.push(ir::AbiParam::new(pointer_type)); 89 return sig; 90 } 91 92 /// Emit code for the following unbarriered memory write of the given type: 93 /// 94 /// ```ignore 95 /// *(base + offset) = value 96 /// ``` 97 /// 98 /// This is intended to be used with things like `ValRaw` and the array calling 99 /// convention. 100 fn unbarriered_store_type_at_offset( 101 pos: &mut FuncCursor, 102 flags: ir::MemFlags, 103 base: ir::Value, 104 offset: i32, 105 value: ir::Value, 106 ) { 107 pos.ins().store(flags, value, base, offset); 108 } 109 110 /// Emit code to do the following unbarriered memory read of the given type and 111 /// with the given flags: 112 /// 113 /// ```ignore 114 /// result = *(base + offset) 115 /// ``` 116 /// 117 /// This is intended to be used with things like `ValRaw` and the array calling 118 /// convention. 119 fn unbarriered_load_type_at_offset( 120 isa: &dyn TargetIsa, 121 pos: &mut FuncCursor, 122 ty: WasmValType, 123 flags: ir::MemFlags, 124 base: ir::Value, 125 offset: i32, 126 ) -> ir::Value { 127 let ir_ty = value_type(isa, ty); 128 pos.ins().load(ir_ty, flags, base, offset) 129 } 130 131 /// Returns the corresponding cranelift type for the provided wasm type. 132 fn value_type(isa: &dyn TargetIsa, ty: WasmValType) -> ir::types::Type { 133 match ty { 134 WasmValType::I32 => ir::types::I32, 135 WasmValType::I64 => ir::types::I64, 136 WasmValType::F32 => ir::types::F32, 137 WasmValType::F64 => ir::types::F64, 138 WasmValType::V128 => ir::types::I8X16, 139 WasmValType::Ref(rt) => reference_type(rt.heap_type, isa.pointer_type()), 140 } 141 } 142 143 /// Get the Cranelift signature for all array-call functions, that is: 144 /// 145 /// ```ignore 146 /// unsafe extern "C" fn( 147 /// callee_vmctx: *mut VMOpaqueContext, 148 /// caller_vmctx: *mut VMOpaqueContext, 149 /// values_ptr: *mut ValRaw, 150 /// values_len: usize, 151 /// ) 152 /// ``` 153 /// 154 /// This signature uses the target's default calling convention. 155 /// 156 /// Note that regardless of the Wasm function type, the array-call calling 157 /// convention always uses that same signature. 158 fn array_call_signature(isa: &dyn TargetIsa) -> ir::Signature { 159 let mut sig = blank_sig(isa, CallConv::triple_default(isa.triple())); 160 // The array-call signature has an added parameter for the `values_vec` 161 // input/output buffer in addition to the size of the buffer, in units 162 // of `ValRaw`. 163 sig.params.push(ir::AbiParam::new(isa.pointer_type())); 164 sig.params.push(ir::AbiParam::new(isa.pointer_type())); 165 // boolean return value of whether this function trapped 166 sig.returns.push(ir::AbiParam::new(ir::types::I8)); 167 sig 168 } 169 170 /// Get the internal Wasm calling convention for the target/tunables combo 171 fn wasm_call_conv(isa: &dyn TargetIsa, tunables: &Tunables) -> CallConv { 172 // The default calling convention is `CallConv::Tail` to enable the use of 173 // tail calls in modules when needed. Note that this is used even if the 174 // tail call proposal is disabled in wasm. This is not interacted with on 175 // the host so it's purely an internal detail of wasm itself. 176 // 177 // The Winch calling convention is used instead when generating trampolines 178 // which call Winch-generated functions. The winch calling convention is 179 // only implemented for x64 and aarch64, so assert that here and panic on 180 // other architectures. 181 if tunables.winch_callable { 182 assert!( 183 matches!( 184 isa.triple().architecture, 185 Architecture::X86_64 | Architecture::Aarch64(_) 186 ), 187 "The Winch calling convention is only implemented for x86_64 and aarch64" 188 ); 189 CallConv::Winch 190 } else { 191 CallConv::Tail 192 } 193 } 194 195 /// Get the internal Wasm calling convention signature for the given type. 196 fn wasm_call_signature( 197 isa: &dyn TargetIsa, 198 wasm_func_ty: &WasmFuncType, 199 tunables: &Tunables, 200 ) -> ir::Signature { 201 let call_conv = wasm_call_conv(isa, tunables); 202 let mut sig = blank_sig(isa, call_conv); 203 let cvt = |ty: &WasmValType| ir::AbiParam::new(value_type(isa, *ty)); 204 sig.params.extend(wasm_func_ty.params().iter().map(&cvt)); 205 sig.returns.extend(wasm_func_ty.returns().iter().map(&cvt)); 206 sig 207 } 208 209 /// Returns the reference type to use for the provided wasm type. 210 fn reference_type(wasm_ht: WasmHeapType, pointer_type: ir::Type) -> ir::Type { 211 match wasm_ht.top() { 212 WasmHeapTopType::Func => pointer_type, 213 WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => ir::types::I32, 214 WasmHeapTopType::Cont => { 215 // VMContObj is 2 * pointer_size (pointer + usize revision) 216 ir::Type::int((2 * pointer_type.bits()).try_into().unwrap()).unwrap() 217 } 218 } 219 } 220 221 // List of namespaces which are processed in `mach_reloc_to_reloc` below. 222 223 /// A record of a relocation to perform. 224 #[derive(Debug, Clone, PartialEq, Eq)] 225 pub struct Relocation { 226 /// The relocation code. 227 pub reloc: binemit::Reloc, 228 /// Relocation target. 229 pub reloc_target: FuncKey, 230 /// The offset where to apply the relocation. 231 pub offset: binemit::CodeOffset, 232 /// The addend to add to the relocation value. 233 pub addend: binemit::Addend, 234 } 235 236 /// Converts cranelift_codegen settings to the wasmtime_environ equivalent. 237 pub fn clif_flags_to_wasmtime( 238 flags: impl IntoIterator<Item = settings::Value>, 239 ) -> Vec<(&'static str, FlagValue<'static>)> { 240 flags 241 .into_iter() 242 .map(|val| (val.name, to_flag_value(&val))) 243 .collect() 244 } 245 246 fn to_flag_value(v: &settings::Value) -> FlagValue<'static> { 247 match v.kind() { 248 settings::SettingKind::Enum => FlagValue::Enum(v.as_enum().unwrap()), 249 settings::SettingKind::Num => FlagValue::Num(v.as_num().unwrap()), 250 settings::SettingKind::Bool => FlagValue::Bool(v.as_bool().unwrap()), 251 settings::SettingKind::Preset => unreachable!(), 252 } 253 } 254 255 /// Converts machine traps to trap information. 256 pub fn mach_trap_to_trap(trap: &MachTrap) -> Option<TrapInformation> { 257 let &MachTrap { offset, code } = trap; 258 Some(TrapInformation { 259 code_offset: offset, 260 trap_code: clif_trap_to_env_trap(code)?, 261 }) 262 } 263 264 fn clif_trap_to_env_trap(trap: ir::TrapCode) -> Option<Trap> { 265 Some(match trap { 266 ir::TrapCode::STACK_OVERFLOW => Trap::StackOverflow, 267 ir::TrapCode::HEAP_OUT_OF_BOUNDS => Trap::MemoryOutOfBounds, 268 ir::TrapCode::INTEGER_OVERFLOW => Trap::IntegerOverflow, 269 ir::TrapCode::INTEGER_DIVISION_BY_ZERO => Trap::IntegerDivisionByZero, 270 ir::TrapCode::BAD_CONVERSION_TO_INTEGER => Trap::BadConversionToInteger, 271 272 // These do not get converted to wasmtime traps, since they 273 // shouldn't ever be hit in theory. Instead of catching and handling 274 // these, we let the signal crash the process. 275 TRAP_INTERNAL_ASSERT => return None, 276 277 other => Trap::from_u8(other.as_raw().get() - TRAP_OFFSET).unwrap(), 278 }) 279 } 280 281 /// Converts machine relocations to relocation information 282 /// to perform. 283 fn mach_reloc_to_reloc( 284 reloc: &FinalizedMachReloc, 285 name_map: &PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>, 286 ) -> Relocation { 287 let &FinalizedMachReloc { 288 offset, 289 kind, 290 ref target, 291 addend, 292 } = reloc; 293 let reloc_target = match *target { 294 FinalizedRelocTarget::ExternalName(ExternalName::User(user_func_ref)) => { 295 let name = &name_map[user_func_ref]; 296 FuncKey::from_raw_parts(name.namespace, name.index) 297 } 298 FinalizedRelocTarget::ExternalName(ExternalName::LibCall(libcall)) => { 299 // We should have avoided any code that needs this style of libcalls 300 // in the Wasm-to-Cranelift translator. 301 panic!("unexpected libcall {libcall:?}"); 302 } 303 _ => panic!("unrecognized external name {target:?}"), 304 }; 305 Relocation { 306 reloc: kind, 307 reloc_target, 308 offset, 309 addend, 310 } 311 } 312 313 /// Helper structure for creating a `Signature` for all builtins. 314 struct BuiltinFunctionSignatures { 315 pointer_type: ir::Type, 316 317 host_call_conv: CallConv, 318 wasm_call_conv: CallConv, 319 argument_extension: ir::ArgumentExtension, 320 } 321 322 impl BuiltinFunctionSignatures { 323 fn new(compiler: &Compiler) -> Self { 324 Self { 325 pointer_type: compiler.isa().pointer_type(), 326 host_call_conv: CallConv::triple_default(compiler.isa().triple()), 327 wasm_call_conv: wasm_call_conv(compiler.isa(), compiler.tunables()), 328 argument_extension: compiler.isa().default_argument_extension(), 329 } 330 } 331 332 fn vmctx(&self) -> AbiParam { 333 AbiParam::special(self.pointer_type, ArgumentPurpose::VMContext) 334 } 335 336 fn pointer(&self) -> AbiParam { 337 AbiParam::new(self.pointer_type) 338 } 339 340 fn u32(&self) -> AbiParam { 341 AbiParam::new(ir::types::I32) 342 } 343 344 fn u64(&self) -> AbiParam { 345 AbiParam::new(ir::types::I64) 346 } 347 348 fn f32(&self) -> AbiParam { 349 AbiParam::new(ir::types::F32) 350 } 351 352 fn f64(&self) -> AbiParam { 353 AbiParam::new(ir::types::F64) 354 } 355 356 fn u8(&self) -> AbiParam { 357 AbiParam::new(ir::types::I8) 358 } 359 360 fn i8x16(&self) -> AbiParam { 361 AbiParam::new(ir::types::I8X16) 362 } 363 364 fn f32x4(&self) -> AbiParam { 365 AbiParam::new(ir::types::F32X4) 366 } 367 368 fn f64x2(&self) -> AbiParam { 369 AbiParam::new(ir::types::F64X2) 370 } 371 372 fn bool(&self) -> AbiParam { 373 AbiParam::new(ir::types::I8) 374 } 375 376 #[cfg(feature = "stack-switching")] 377 fn size(&self) -> AbiParam { 378 AbiParam::new(self.pointer_type) 379 } 380 381 fn wasm_signature(&self, builtin: BuiltinFunctionIndex) -> Signature { 382 let mut _cur = 0; 383 macro_rules! iter { 384 ( 385 $( 386 $( #[$attr:meta] )* 387 $name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?; 388 )* 389 ) => { 390 $( 391 $( #[$attr] )* 392 if _cur == builtin.index() { 393 return Signature { 394 params: vec![ $( self.$param() ),* ], 395 returns: vec![ $( self.$result() )? ], 396 call_conv: self.wasm_call_conv, 397 }; 398 } 399 _cur += 1; 400 )* 401 }; 402 } 403 404 wasmtime_environ::foreach_builtin_function!(iter); 405 406 unreachable!(); 407 } 408 409 fn host_signature(&self, builtin: BuiltinFunctionIndex) -> Signature { 410 let mut sig = self.wasm_signature(builtin); 411 sig.call_conv = self.host_call_conv; 412 413 // Once we're declaring the signature of a host function we must 414 // respect the default ABI of the platform which is where argument 415 // extension of params/results may come into play. 416 for arg in sig.params.iter_mut().chain(sig.returns.iter_mut()) { 417 if arg.value_type.is_int() { 418 arg.extension = self.argument_extension; 419 } 420 } 421 422 sig 423 } 424 } 425 426 /// If this bit is set on a GC reference, then the GC reference is actually an 427 /// unboxed `i31`. 428 /// 429 /// Must be kept in sync with 430 /// `crate::runtime::vm::gc::VMGcRef::I31_REF_DISCRIMINANT`. 431 const I31_REF_DISCRIMINANT: u32 = 1; 432 433 /// Like `Option<T>` but specifically for passing information about transitions 434 /// from reachable to unreachable state and the like from callees to callers. 435 /// 436 /// Marked `must_use` to force callers to update 437 /// `FuncTranslationStacks::reachable` as necessary. 438 #[derive(PartialEq, Eq)] 439 #[must_use] 440 enum Reachability<T> { 441 /// The Wasm execution state is reachable, here is a `T`. 442 Reachable(T), 443 /// The Wasm execution state has been determined to be statically 444 /// unreachable. It is the receiver of this value's responsibility to update 445 /// `FuncTranslationStacks::reachable` as necessary. 446 Unreachable, 447 } 448