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