wasmtime_cranelift/
lib.rs

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// See documentation in crates/wasmtime/src/runtime.rs for why this is
7// selectively enabled here.
8#![warn(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
9
10use cranelift_codegen::{
11    binemit,
12    cursor::FuncCursor,
13    ir::{self, AbiParam, ArgumentPurpose, ExternalName, InstBuilder, Signature, TrapCode},
14    isa::{CallConv, TargetIsa},
15    settings, FinalizedMachReloc, FinalizedRelocTarget, MachTrap,
16};
17use cranelift_entity::PrimaryMap;
18
19use target_lexicon::Architecture;
20use wasmtime_environ::{
21    BuiltinFunctionIndex, FlagValue, FuncIndex, RelocationTarget, Trap, TrapInformation, Tunables,
22    WasmFuncType, WasmHeapTopType, WasmHeapType, WasmValType,
23};
24
25pub use builder::builder;
26
27pub mod isa_builder;
28mod obj;
29pub use obj::*;
30mod compiled_function;
31pub use compiled_function::*;
32
33mod builder;
34mod compiler;
35mod debug;
36mod func_environ;
37mod translate;
38
39use self::compiler::Compiler;
40
41const TRAP_INTERNAL_ASSERT: TrapCode = TrapCode::unwrap_user(1);
42const TRAP_OFFSET: u8 = 2;
43pub const TRAP_ALWAYS: TrapCode =
44    TrapCode::unwrap_user(Trap::AlwaysTrapAdapter as u8 + TRAP_OFFSET);
45pub const TRAP_CANNOT_ENTER: TrapCode =
46    TrapCode::unwrap_user(Trap::CannotEnterComponent as u8 + TRAP_OFFSET);
47pub const TRAP_INDIRECT_CALL_TO_NULL: TrapCode =
48    TrapCode::unwrap_user(Trap::IndirectCallToNull as u8 + TRAP_OFFSET);
49pub const TRAP_BAD_SIGNATURE: TrapCode =
50    TrapCode::unwrap_user(Trap::BadSignature as u8 + TRAP_OFFSET);
51pub const TRAP_NULL_REFERENCE: TrapCode =
52    TrapCode::unwrap_user(Trap::NullReference as u8 + TRAP_OFFSET);
53pub const TRAP_ALLOCATION_TOO_LARGE: TrapCode =
54    TrapCode::unwrap_user(Trap::AllocationTooLarge as u8 + TRAP_OFFSET);
55pub const TRAP_ARRAY_OUT_OF_BOUNDS: TrapCode =
56    TrapCode::unwrap_user(Trap::ArrayOutOfBounds as u8 + TRAP_OFFSET);
57pub const TRAP_UNREACHABLE: TrapCode =
58    TrapCode::unwrap_user(Trap::UnreachableCodeReached as u8 + TRAP_OFFSET);
59pub const TRAP_HEAP_MISALIGNED: TrapCode =
60    TrapCode::unwrap_user(Trap::HeapMisaligned as u8 + TRAP_OFFSET);
61pub const TRAP_TABLE_OUT_OF_BOUNDS: TrapCode =
62    TrapCode::unwrap_user(Trap::TableOutOfBounds as u8 + TRAP_OFFSET);
63pub const TRAP_CAST_FAILURE: TrapCode =
64    TrapCode::unwrap_user(Trap::CastFailure as u8 + TRAP_OFFSET);
65
66/// Creates a new cranelift `Signature` with no wasm params/results for the
67/// given calling convention.
68///
69/// This will add the default vmctx/etc parameters to the signature returned.
70fn blank_sig(isa: &dyn TargetIsa, call_conv: CallConv) -> ir::Signature {
71    let pointer_type = isa.pointer_type();
72    let mut sig = ir::Signature::new(call_conv);
73    // Add the caller/callee `vmctx` parameters.
74    sig.params.push(ir::AbiParam::special(
75        pointer_type,
76        ir::ArgumentPurpose::VMContext,
77    ));
78    sig.params.push(ir::AbiParam::new(pointer_type));
79    return sig;
80}
81
82/// Emit code for the following unbarriered memory write of the given type:
83///
84/// ```ignore
85/// *(base + offset) = value
86/// ```
87///
88/// This is intended to be used with things like `ValRaw` and the array calling
89/// convention.
90fn unbarriered_store_type_at_offset(
91    pos: &mut FuncCursor,
92    flags: ir::MemFlags,
93    base: ir::Value,
94    offset: i32,
95    value: ir::Value,
96) {
97    pos.ins().store(flags, value, base, offset);
98}
99
100/// Emit code to do the following unbarriered memory read of the given type and
101/// with the given flags:
102///
103/// ```ignore
104/// result = *(base + offset)
105/// ```
106///
107/// This is intended to be used with things like `ValRaw` and the array calling
108/// convention.
109fn unbarriered_load_type_at_offset(
110    isa: &dyn TargetIsa,
111    pos: &mut FuncCursor,
112    ty: WasmValType,
113    flags: ir::MemFlags,
114    base: ir::Value,
115    offset: i32,
116) -> ir::Value {
117    let ir_ty = value_type(isa, ty);
118    pos.ins().load(ir_ty, flags, base, offset)
119}
120
121/// Returns the corresponding cranelift type for the provided wasm type.
122fn value_type(isa: &dyn TargetIsa, ty: WasmValType) -> ir::types::Type {
123    match ty {
124        WasmValType::I32 => ir::types::I32,
125        WasmValType::I64 => ir::types::I64,
126        WasmValType::F32 => ir::types::F32,
127        WasmValType::F64 => ir::types::F64,
128        WasmValType::V128 => ir::types::I8X16,
129        WasmValType::Ref(rt) => reference_type(rt.heap_type, isa.pointer_type()),
130    }
131}
132
133/// Get the Cranelift signature for all array-call functions, that is:
134///
135/// ```ignore
136/// unsafe extern "C" fn(
137///     callee_vmctx: *mut VMOpaqueContext,
138///     caller_vmctx: *mut VMOpaqueContext,
139///     values_ptr: *mut ValRaw,
140///     values_len: usize,
141/// )
142/// ```
143///
144/// This signature uses the target's default calling convention.
145///
146/// Note that regardless of the Wasm function type, the array-call calling
147/// convention always uses that same signature.
148fn array_call_signature(isa: &dyn TargetIsa) -> ir::Signature {
149    let mut sig = blank_sig(isa, CallConv::triple_default(isa.triple()));
150    // The array-call signature has an added parameter for the `values_vec`
151    // input/output buffer in addition to the size of the buffer, in units
152    // of `ValRaw`.
153    sig.params.push(ir::AbiParam::new(isa.pointer_type()));
154    sig.params.push(ir::AbiParam::new(isa.pointer_type()));
155    // boolean return value of whether this function trapped
156    sig.returns.push(ir::AbiParam::new(ir::types::I8));
157    sig
158}
159
160/// Get the internal Wasm calling convention for the target/tunables combo
161fn wasm_call_conv(isa: &dyn TargetIsa, tunables: &Tunables) -> CallConv {
162    // The default calling convention is `CallConv::Tail` to enable the use of
163    // tail calls in modules when needed. Note that this is used even if the
164    // tail call proposal is disabled in wasm. This is not interacted with on
165    // the host so it's purely an internal detail of wasm itself.
166    //
167    // The Winch calling convention is used instead when generating trampolines
168    // which call Winch-generated functions. The winch calling convention is
169    // only implemented for x64 and aarch64, so assert that here and panic on
170    // other architectures.
171    if tunables.winch_callable {
172        assert!(
173            matches!(
174                isa.triple().architecture,
175                Architecture::X86_64 | Architecture::Aarch64(_)
176            ),
177            "The Winch calling convention is only implemented for x86_64 and aarch64"
178        );
179        CallConv::Winch
180    } else {
181        CallConv::Tail
182    }
183}
184
185/// Get the internal Wasm calling convention signature for the given type.
186fn wasm_call_signature(
187    isa: &dyn TargetIsa,
188    wasm_func_ty: &WasmFuncType,
189    tunables: &Tunables,
190) -> ir::Signature {
191    let call_conv = wasm_call_conv(isa, tunables);
192    let mut sig = blank_sig(isa, call_conv);
193    let cvt = |ty: &WasmValType| ir::AbiParam::new(value_type(isa, *ty));
194    sig.params.extend(wasm_func_ty.params().iter().map(&cvt));
195    sig.returns.extend(wasm_func_ty.returns().iter().map(&cvt));
196    sig
197}
198
199/// Returns the reference type to use for the provided wasm type.
200fn reference_type(wasm_ht: WasmHeapType, pointer_type: ir::Type) -> ir::Type {
201    match wasm_ht.top() {
202        WasmHeapTopType::Func => pointer_type,
203        WasmHeapTopType::Any | WasmHeapTopType::Extern => ir::types::I32,
204        WasmHeapTopType::Cont => todo!(), // FIXME: #10248 stack switching support.
205    }
206}
207
208// List of namespaces which are processed in `mach_reloc_to_reloc` below.
209
210/// Namespace corresponding to wasm functions, the index is the index of the
211/// defined function that's being referenced.
212pub const NS_WASM_FUNC: u32 = 0;
213
214/// Namespace for builtin function trampolines. The index is the index of the
215/// builtin that's being referenced. These trampolines invoke the real host
216/// function through an indirect function call loaded by the `VMContext`.
217pub const NS_WASMTIME_BUILTIN: u32 = 1;
218
219/// Namespace used to when a call from Pulley to the host is being made. This is
220/// used with a `colocated: false` name to trigger codegen for a special opcode
221/// for pulley-to-host communication. The index of the functions used in this
222/// namespace correspond to the function signature of `for_each_host_signature!`
223/// in the pulley_interpreter crate.
224pub const NS_PULLEY_HOSTCALL: u32 = 2;
225
226/// A record of a relocation to perform.
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub struct Relocation {
229    /// The relocation code.
230    pub reloc: binemit::Reloc,
231    /// Relocation target.
232    pub reloc_target: RelocationTarget,
233    /// The offset where to apply the relocation.
234    pub offset: binemit::CodeOffset,
235    /// The addend to add to the relocation value.
236    pub addend: binemit::Addend,
237}
238
239/// Converts cranelift_codegen settings to the wasmtime_environ equivalent.
240pub fn clif_flags_to_wasmtime(
241    flags: impl IntoIterator<Item = settings::Value>,
242) -> Vec<(&'static str, FlagValue<'static>)> {
243    flags
244        .into_iter()
245        .map(|val| (val.name, to_flag_value(&val)))
246        .collect()
247}
248
249fn to_flag_value(v: &settings::Value) -> FlagValue<'static> {
250    match v.kind() {
251        settings::SettingKind::Enum => FlagValue::Enum(v.as_enum().unwrap()),
252        settings::SettingKind::Num => FlagValue::Num(v.as_num().unwrap()),
253        settings::SettingKind::Bool => FlagValue::Bool(v.as_bool().unwrap()),
254        settings::SettingKind::Preset => unreachable!(),
255    }
256}
257
258/// Converts machine traps to trap information.
259pub fn mach_trap_to_trap(trap: &MachTrap) -> Option<TrapInformation> {
260    let &MachTrap { offset, code } = trap;
261    Some(TrapInformation {
262        code_offset: offset,
263        trap_code: clif_trap_to_env_trap(code)?,
264    })
265}
266
267fn clif_trap_to_env_trap(trap: ir::TrapCode) -> Option<Trap> {
268    Some(match trap {
269        ir::TrapCode::STACK_OVERFLOW => Trap::StackOverflow,
270        ir::TrapCode::HEAP_OUT_OF_BOUNDS => Trap::MemoryOutOfBounds,
271        ir::TrapCode::INTEGER_OVERFLOW => Trap::IntegerOverflow,
272        ir::TrapCode::INTEGER_DIVISION_BY_ZERO => Trap::IntegerDivisionByZero,
273        ir::TrapCode::BAD_CONVERSION_TO_INTEGER => Trap::BadConversionToInteger,
274
275        // These do not get converted to wasmtime traps, since they
276        // shouldn't ever be hit in theory. Instead of catching and handling
277        // these, we let the signal crash the process.
278        TRAP_INTERNAL_ASSERT => return None,
279
280        other => Trap::from_u8(other.as_raw().get() - TRAP_OFFSET).unwrap(),
281    })
282}
283
284/// Converts machine relocations to relocation information
285/// to perform.
286fn mach_reloc_to_reloc(
287    reloc: &FinalizedMachReloc,
288    name_map: &PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>,
289) -> Relocation {
290    let &FinalizedMachReloc {
291        offset,
292        kind,
293        ref target,
294        addend,
295    } = reloc;
296    let reloc_target = match *target {
297        FinalizedRelocTarget::ExternalName(ExternalName::User(user_func_ref)) => {
298            let name = &name_map[user_func_ref];
299            match name.namespace {
300                NS_WASM_FUNC => RelocationTarget::Wasm(FuncIndex::from_u32(name.index)),
301                NS_WASMTIME_BUILTIN => {
302                    RelocationTarget::Builtin(BuiltinFunctionIndex::from_u32(name.index))
303                }
304                NS_PULLEY_HOSTCALL => RelocationTarget::PulleyHostcall(name.index),
305                _ => panic!("unknown namespace {}", name.namespace),
306            }
307        }
308        FinalizedRelocTarget::ExternalName(ExternalName::LibCall(libcall)) => {
309            let libcall = libcall_cranelift_to_wasmtime(libcall);
310            RelocationTarget::HostLibcall(libcall)
311        }
312        _ => panic!("unrecognized external name"),
313    };
314    Relocation {
315        reloc: kind,
316        reloc_target,
317        offset,
318        addend,
319    }
320}
321
322fn libcall_cranelift_to_wasmtime(call: ir::LibCall) -> wasmtime_environ::obj::LibCall {
323    use wasmtime_environ::obj::LibCall as LC;
324    match call {
325        ir::LibCall::FloorF32 => LC::FloorF32,
326        ir::LibCall::FloorF64 => LC::FloorF64,
327        ir::LibCall::NearestF32 => LC::NearestF32,
328        ir::LibCall::NearestF64 => LC::NearestF64,
329        ir::LibCall::CeilF32 => LC::CeilF32,
330        ir::LibCall::CeilF64 => LC::CeilF64,
331        ir::LibCall::TruncF32 => LC::TruncF32,
332        ir::LibCall::TruncF64 => LC::TruncF64,
333        ir::LibCall::FmaF32 => LC::FmaF32,
334        ir::LibCall::FmaF64 => LC::FmaF64,
335        ir::LibCall::X86Pshufb => LC::X86Pshufb,
336        _ => panic!("cranelift emitted a libcall wasmtime does not support: {call:?}"),
337    }
338}
339
340/// Helper structure for creating a `Signature` for all builtins.
341struct BuiltinFunctionSignatures {
342    pointer_type: ir::Type,
343
344    host_call_conv: CallConv,
345    wasm_call_conv: CallConv,
346    argument_extension: ir::ArgumentExtension,
347}
348
349impl BuiltinFunctionSignatures {
350    fn new(compiler: &Compiler) -> Self {
351        Self {
352            pointer_type: compiler.isa().pointer_type(),
353            host_call_conv: CallConv::triple_default(compiler.isa().triple()),
354            wasm_call_conv: wasm_call_conv(compiler.isa(), compiler.tunables()),
355            argument_extension: compiler.isa().default_argument_extension(),
356        }
357    }
358
359    fn vmctx(&self) -> AbiParam {
360        AbiParam::special(self.pointer_type, ArgumentPurpose::VMContext)
361    }
362
363    fn pointer(&self) -> AbiParam {
364        AbiParam::new(self.pointer_type)
365    }
366
367    fn u32(&self) -> AbiParam {
368        AbiParam::new(ir::types::I32)
369    }
370
371    fn u64(&self) -> AbiParam {
372        AbiParam::new(ir::types::I64)
373    }
374
375    fn u8(&self) -> AbiParam {
376        AbiParam::new(ir::types::I8)
377    }
378
379    fn bool(&self) -> AbiParam {
380        AbiParam::new(ir::types::I8)
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`.
433const I31_REF_DISCRIMINANT: u32 = 1;