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
6use cranelift_codegen::{
7    binemit,
8    cursor::FuncCursor,
9    ir::{self, AbiParam, ArgumentPurpose, ExternalName, InstBuilder, Signature},
10    isa::{CallConv, TargetIsa},
11    settings, FinalizedMachReloc, FinalizedRelocTarget, MachTrap,
12};
13use cranelift_entity::PrimaryMap;
14use cranelift_wasm::{FuncIndex, WasmFuncType, WasmHeapTopType, WasmHeapType, WasmValType};
15
16use target_lexicon::Architecture;
17use wasmtime_environ::{
18    BuiltinFunctionIndex, FlagValue, RelocationTarget, Trap, TrapInformation, Tunables,
19};
20
21pub use builder::builder;
22
23pub mod isa_builder;
24mod obj;
25pub use obj::*;
26mod compiled_function;
27pub use compiled_function::*;
28
29mod builder;
30mod compiler;
31mod debug;
32mod func_environ;
33mod gc;
34
35/// Trap code used for debug assertions we emit in our JIT code.
36const DEBUG_ASSERT_TRAP_CODE: u16 = u16::MAX;
37
38/// Creates a new cranelift `Signature` with no wasm params/results for the
39/// given calling convention.
40///
41/// This will add the default vmctx/etc parameters to the signature returned.
42fn blank_sig(isa: &dyn TargetIsa, call_conv: CallConv) -> ir::Signature {
43    let pointer_type = isa.pointer_type();
44    let mut sig = ir::Signature::new(call_conv);
45    // Add the caller/callee `vmctx` parameters.
46    sig.params.push(ir::AbiParam::special(
47        pointer_type,
48        ir::ArgumentPurpose::VMContext,
49    ));
50    sig.params.push(ir::AbiParam::new(pointer_type));
51    return sig;
52}
53
54/// Emit code for the following unbarriered memory write of the given type:
55///
56/// ```ignore
57/// *(base + offset) = value
58/// ```
59///
60/// This is intended to be used with things like `ValRaw` and the array calling
61/// convention.
62fn unbarriered_store_type_at_offset(
63    isa: &dyn TargetIsa,
64    pos: &mut FuncCursor,
65    ty: WasmValType,
66    flags: ir::MemFlags,
67    base: ir::Value,
68    offset: i32,
69    value: ir::Value,
70) {
71    let ir_ty = value_type(isa, ty);
72    if ir_ty.is_ref() {
73        let value = pos
74            .ins()
75            .bitcast(ir_ty.as_int(), ir::MemFlags::new(), value);
76        let truncated = match isa.pointer_bytes() {
77            4 => value,
78            8 => pos.ins().ireduce(ir::types::I32, value),
79            _ => unreachable!(),
80        };
81        pos.ins().store(flags, truncated, base, offset);
82    } else {
83        pos.ins().store(flags, value, base, offset);
84    }
85}
86
87/// Emit code to do the following unbarriered memory read of the given type and
88/// with the given flags:
89///
90/// ```ignore
91/// result = *(base + offset)
92/// ```
93///
94/// This is intended to be used with things like `ValRaw` and the array calling
95/// convention.
96fn unbarriered_load_type_at_offset(
97    isa: &dyn TargetIsa,
98    pos: &mut FuncCursor,
99    ty: WasmValType,
100    flags: ir::MemFlags,
101    base: ir::Value,
102    offset: i32,
103) -> ir::Value {
104    let ir_ty = value_type(isa, ty);
105    if ir_ty.is_ref() {
106        let gc_ref = pos.ins().load(ir::types::I32, flags, base, offset);
107        let extended = match isa.pointer_bytes() {
108            4 => gc_ref,
109            8 => pos.ins().uextend(ir::types::I64, gc_ref),
110            _ => unreachable!(),
111        };
112        pos.ins().bitcast(ir_ty, ir::MemFlags::new(), extended)
113    } else {
114        pos.ins().load(ir_ty, flags, base, offset)
115    }
116}
117
118/// Returns the corresponding cranelift type for the provided wasm type.
119fn value_type(isa: &dyn TargetIsa, ty: WasmValType) -> ir::types::Type {
120    match ty {
121        WasmValType::I32 => ir::types::I32,
122        WasmValType::I64 => ir::types::I64,
123        WasmValType::F32 => ir::types::F32,
124        WasmValType::F64 => ir::types::F64,
125        WasmValType::V128 => ir::types::I8X16,
126        WasmValType::Ref(rt) => reference_type(rt.heap_type, isa.pointer_type()),
127    }
128}
129
130/// Get the Cranelift signature for all array-call functions, that is:
131///
132/// ```ignore
133/// unsafe extern "C" fn(
134///     callee_vmctx: *mut VMOpaqueContext,
135///     caller_vmctx: *mut VMOpaqueContext,
136///     values_ptr: *mut ValRaw,
137///     values_len: usize,
138/// )
139/// ```
140///
141/// This signature uses the target's default calling convention.
142///
143/// Note that regardless of the Wasm function type, the array-call calling
144/// convention always uses that same signature.
145fn array_call_signature(isa: &dyn TargetIsa) -> ir::Signature {
146    let mut sig = blank_sig(isa, CallConv::triple_default(isa.triple()));
147    // The array-call signature has an added parameter for the `values_vec`
148    // input/output buffer in addition to the size of the buffer, in units
149    // of `ValRaw`.
150    sig.params.push(ir::AbiParam::new(isa.pointer_type()));
151    sig.params.push(ir::AbiParam::new(isa.pointer_type()));
152    sig
153}
154
155/// Get the internal Wasm calling convention signature for the given type.
156fn wasm_call_signature(
157    isa: &dyn TargetIsa,
158    wasm_func_ty: &WasmFuncType,
159    tunables: &Tunables,
160) -> ir::Signature {
161    // The default calling convention is `CallConv::Tail` to enable the use of
162    // tail calls in modules when needed. Note that this is used even if the
163    // tail call proposal is disabled in wasm. This is not interacted with on
164    // the host so it's purely an internal detail of wasm itself.
165    //
166    // The Winch calling convention is used instead when generating trampolines
167    // which call Winch-generated functions. The winch calling convention is
168    // only implemented for x64 and aarch64, so assert that here and panic on
169    // other architectures.
170    let call_conv = if tunables.winch_callable {
171        assert!(
172            matches!(
173                isa.triple().architecture,
174                Architecture::X86_64 | Architecture::Aarch64(_)
175            ),
176            "The Winch calling convention is only implemented for x86_64 and aarch64"
177        );
178        CallConv::Winch
179    } else {
180        CallConv::Tail
181    };
182    let mut sig = blank_sig(isa, call_conv);
183    let cvt = |ty: &WasmValType| ir::AbiParam::new(value_type(isa, *ty));
184    sig.params.extend(wasm_func_ty.params().iter().map(&cvt));
185    sig.returns.extend(wasm_func_ty.returns().iter().map(&cvt));
186    sig
187}
188
189/// Returns the reference type to use for the provided wasm type.
190fn reference_type(wasm_ht: WasmHeapType, pointer_type: ir::Type) -> ir::Type {
191    match wasm_ht.top() {
192        WasmHeapTopType::Func => pointer_type,
193        WasmHeapTopType::Any | WasmHeapTopType::Extern => match pointer_type {
194            ir::types::I32 => ir::types::R32,
195            ir::types::I64 => ir::types::R64,
196            _ => panic!("unsupported pointer type"),
197        },
198    }
199}
200
201// List of namespaces which are processed in `mach_reloc_to_reloc` below.
202
203/// Namespace corresponding to wasm functions, the index is the index of the
204/// defined function that's being referenced.
205pub const NS_WASM_FUNC: u32 = 0;
206
207/// Namespace for builtin function trampolines. The index is the index of the
208/// builtin that's being referenced. These trampolines invoke the real host
209/// function through an indirect function call loaded by the `VMContext`.
210pub const NS_WASMTIME_BUILTIN: u32 = 1;
211
212/// A record of a relocation to perform.
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct Relocation {
215    /// The relocation code.
216    pub reloc: binemit::Reloc,
217    /// Relocation target.
218    pub reloc_target: RelocationTarget,
219    /// The offset where to apply the relocation.
220    pub offset: binemit::CodeOffset,
221    /// The addend to add to the relocation value.
222    pub addend: binemit::Addend,
223}
224
225/// Converts cranelift_codegen settings to the wasmtime_environ equivalent.
226pub fn clif_flags_to_wasmtime(
227    flags: impl IntoIterator<Item = settings::Value>,
228) -> Vec<(&'static str, FlagValue<'static>)> {
229    flags
230        .into_iter()
231        .map(|val| (val.name, to_flag_value(&val)))
232        .collect()
233}
234
235fn to_flag_value(v: &settings::Value) -> FlagValue<'static> {
236    match v.kind() {
237        settings::SettingKind::Enum => FlagValue::Enum(v.as_enum().unwrap()),
238        settings::SettingKind::Num => FlagValue::Num(v.as_num().unwrap()),
239        settings::SettingKind::Bool => FlagValue::Bool(v.as_bool().unwrap()),
240        settings::SettingKind::Preset => unreachable!(),
241    }
242}
243
244/// A custom code with `TrapCode::User` which is used by always-trap shims which
245/// indicates that, as expected, the always-trapping function indeed did trap.
246/// This effectively provides a better error message as opposed to a bland
247/// "unreachable code reached"
248pub const ALWAYS_TRAP_CODE: u16 = 100;
249
250/// A custom code with `TrapCode::User` corresponding to being unable to reenter
251/// a component due to its reentrance limitations. This is used in component
252/// adapters to provide a more useful error message in such situations.
253pub const CANNOT_ENTER_CODE: u16 = 101;
254
255/// Converts machine traps to trap information.
256pub 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: match code {
261            ir::TrapCode::StackOverflow => Trap::StackOverflow,
262            ir::TrapCode::HeapOutOfBounds => Trap::MemoryOutOfBounds,
263            ir::TrapCode::HeapMisaligned => Trap::HeapMisaligned,
264            ir::TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,
265            ir::TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,
266            ir::TrapCode::BadSignature => Trap::BadSignature,
267            ir::TrapCode::IntegerOverflow => Trap::IntegerOverflow,
268            ir::TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,
269            ir::TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,
270            ir::TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,
271            ir::TrapCode::Interrupt => Trap::Interrupt,
272            ir::TrapCode::User(ALWAYS_TRAP_CODE) => Trap::AlwaysTrapAdapter,
273            ir::TrapCode::User(CANNOT_ENTER_CODE) => Trap::CannotEnterComponent,
274            ir::TrapCode::NullReference => Trap::NullReference,
275            ir::TrapCode::NullI31Ref => Trap::NullI31Ref,
276
277            // These do not get converted to wasmtime traps, since they
278            // shouldn't ever be hit in theory. Instead of catching and handling
279            // these, we let the signal crash the process.
280            ir::TrapCode::User(DEBUG_ASSERT_TRAP_CODE) => return None,
281
282            // these should never be emitted by wasmtime-cranelift
283            ir::TrapCode::User(_) => unreachable!(),
284        },
285    })
286}
287
288/// Converts machine relocations to relocation information
289/// to perform.
290fn mach_reloc_to_reloc(
291    reloc: &FinalizedMachReloc,
292    name_map: &PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>,
293) -> Relocation {
294    let &FinalizedMachReloc {
295        offset,
296        kind,
297        ref target,
298        addend,
299    } = reloc;
300    let reloc_target = match *target {
301        FinalizedRelocTarget::ExternalName(ExternalName::User(user_func_ref)) => {
302            let name = &name_map[user_func_ref];
303            match name.namespace {
304                NS_WASM_FUNC => RelocationTarget::Wasm(FuncIndex::from_u32(name.index)),
305                NS_WASMTIME_BUILTIN => {
306                    RelocationTarget::Builtin(BuiltinFunctionIndex::from_u32(name.index))
307                }
308                _ => panic!("unknown namespace {}", name.namespace),
309            }
310        }
311        FinalizedRelocTarget::ExternalName(ExternalName::LibCall(libcall)) => {
312            let libcall = libcall_cranelift_to_wasmtime(libcall);
313            RelocationTarget::HostLibcall(libcall)
314        }
315        _ => panic!("unrecognized external name"),
316    };
317    Relocation {
318        reloc: kind,
319        reloc_target,
320        offset,
321        addend,
322    }
323}
324
325fn libcall_cranelift_to_wasmtime(call: ir::LibCall) -> wasmtime_environ::obj::LibCall {
326    use wasmtime_environ::obj::LibCall as LC;
327    match call {
328        ir::LibCall::FloorF32 => LC::FloorF32,
329        ir::LibCall::FloorF64 => LC::FloorF64,
330        ir::LibCall::NearestF32 => LC::NearestF32,
331        ir::LibCall::NearestF64 => LC::NearestF64,
332        ir::LibCall::CeilF32 => LC::CeilF32,
333        ir::LibCall::CeilF64 => LC::CeilF64,
334        ir::LibCall::TruncF32 => LC::TruncF32,
335        ir::LibCall::TruncF64 => LC::TruncF64,
336        ir::LibCall::FmaF32 => LC::FmaF32,
337        ir::LibCall::FmaF64 => LC::FmaF64,
338        ir::LibCall::X86Pshufb => LC::X86Pshufb,
339        _ => panic!("cranelift emitted a libcall wasmtime does not support: {call:?}"),
340    }
341}
342
343/// Helper structure for creating a `Signature` for all builtins.
344struct BuiltinFunctionSignatures {
345    pointer_type: ir::Type,
346
347    #[cfg(feature = "gc")]
348    reference_type: ir::Type,
349
350    call_conv: CallConv,
351}
352
353impl BuiltinFunctionSignatures {
354    fn new(isa: &dyn TargetIsa) -> Self {
355        Self {
356            pointer_type: isa.pointer_type(),
357            #[cfg(feature = "gc")]
358            reference_type: match isa.pointer_type() {
359                ir::types::I32 => ir::types::R32,
360                ir::types::I64 => ir::types::R64,
361                _ => panic!(),
362            },
363            call_conv: CallConv::triple_default(isa.triple()),
364        }
365    }
366
367    fn vmctx(&self) -> AbiParam {
368        AbiParam::special(self.pointer_type, ArgumentPurpose::VMContext)
369    }
370
371    #[cfg(feature = "gc")]
372    fn reference(&self) -> AbiParam {
373        AbiParam::new(self.reference_type)
374    }
375
376    fn pointer(&self) -> AbiParam {
377        AbiParam::new(self.pointer_type)
378    }
379
380    fn i32(&self) -> AbiParam {
381        // Some platform ABIs require i32 values to be zero- or sign-
382        // extended to the full register width.  We need to indicate
383        // this here by using the appropriate .uext or .sext attribute.
384        // The attribute can be added unconditionally; platforms whose
385        // ABI does not require such extensions will simply ignore it.
386        // Note that currently all i32 arguments or return values used
387        // by builtin functions are unsigned, so we always use .uext.
388        // If that ever changes, we will have to add a second type
389        // marker here.
390        AbiParam::new(ir::types::I32).uext()
391    }
392
393    fn i64(&self) -> AbiParam {
394        AbiParam::new(ir::types::I64)
395    }
396
397    fn signature(&self, builtin: BuiltinFunctionIndex) -> Signature {
398        let mut _cur = 0;
399        macro_rules! iter {
400            (
401                $(
402                    $( #[$attr:meta] )*
403                    $name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
404                )*
405            ) => {
406                $(
407                    $( #[$attr] )*
408                    if _cur == builtin.index() {
409                        return Signature {
410                            params: vec![ $( self.$param() ),* ],
411                            returns: vec![ $( self.$result() )? ],
412                            call_conv: self.call_conv,
413                        };
414                    }
415                    _cur += 1;
416                )*
417            };
418        }
419
420        wasmtime_environ::foreach_builtin_function!(iter);
421
422        unreachable!();
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`.
431const I31_REF_DISCRIMINANT: u32 = 1;