xref: /wasmtime-44.0.1/crates/cranelift/src/lib.rs (revision fee9be21)
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