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