dusk_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::{DefinedFuncIndex, FuncIndex, WasmFuncType, 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
35type CompiledFunctionsMetadata<'a> = PrimaryMap<DefinedFuncIndex, &'a CompiledFunctionMetadata>;
36
37/// Trap code used for debug assertions we emit in our JIT code.
38const DEBUG_ASSERT_TRAP_CODE: u16 = u16::MAX;
39
40/// Creates a new cranelift `Signature` with no wasm params/results for the
41/// given calling convention.
42///
43/// This will add the default vmctx/etc parameters to the signature returned.
44fn blank_sig(isa: &dyn TargetIsa, call_conv: CallConv) -> ir::Signature {
45    let pointer_type = isa.pointer_type();
46    let mut sig = ir::Signature::new(call_conv);
47    // Add the caller/callee `vmctx` parameters.
48    sig.params.push(ir::AbiParam::special(
49        pointer_type,
50        ir::ArgumentPurpose::VMContext,
51    ));
52    sig.params.push(ir::AbiParam::new(pointer_type));
53    return sig;
54}
55
56/// Emit code for the following unbarriered memory write of the given type:
57///
58/// ```ignore
59/// *(base + offset) = value
60/// ```
61///
62/// This is intended to be used with things like `ValRaw` and the array calling
63/// convention.
64fn unbarriered_store_type_at_offset(
65    isa: &dyn TargetIsa,
66    pos: &mut FuncCursor,
67    ty: WasmValType,
68    flags: ir::MemFlags,
69    base: ir::Value,
70    offset: i32,
71    value: ir::Value,
72) {
73    let ir_ty = value_type(isa, ty);
74    if ir_ty.is_ref() {
75        let value = pos
76            .ins()
77            .bitcast(ir_ty.as_int(), ir::MemFlags::new(), value);
78        let truncated = match isa.pointer_bytes() {
79            4 => value,
80            8 => pos.ins().ireduce(ir::types::I32, value),
81            _ => unreachable!(),
82        };
83        pos.ins().store(flags, truncated, base, offset);
84    } else {
85        pos.ins().store(flags, value, base, offset);
86    }
87}
88
89/// Emit code to do the following unbarriered memory read of the given type and
90/// with the given flags:
91///
92/// ```ignore
93/// result = *(base + offset)
94/// ```
95///
96/// This is intended to be used with things like `ValRaw` and the array calling
97/// convention.
98fn unbarriered_load_type_at_offset(
99    isa: &dyn TargetIsa,
100    pos: &mut FuncCursor,
101    ty: WasmValType,
102    flags: ir::MemFlags,
103    base: ir::Value,
104    offset: i32,
105) -> ir::Value {
106    let ir_ty = value_type(isa, ty);
107    if ir_ty.is_ref() {
108        let gc_ref = pos.ins().load(ir::types::I32, flags, base, offset);
109        let extended = match isa.pointer_bytes() {
110            4 => gc_ref,
111            8 => pos.ins().uextend(ir::types::I64, gc_ref),
112            _ => unreachable!(),
113        };
114        pos.ins().bitcast(ir_ty, ir::MemFlags::new(), extended)
115    } else {
116        pos.ins().load(ir_ty, flags, base, offset)
117    }
118}
119
120/// Returns the corresponding cranelift type for the provided wasm type.
121fn value_type(isa: &dyn TargetIsa, ty: WasmValType) -> ir::types::Type {
122    match ty {
123        WasmValType::I32 => ir::types::I32,
124        WasmValType::I64 => ir::types::I64,
125        WasmValType::F32 => ir::types::F32,
126        WasmValType::F64 => ir::types::F64,
127        WasmValType::V128 => ir::types::I8X16,
128        WasmValType::Ref(rt) => reference_type(rt.heap_type, isa.pointer_type()),
129    }
130}
131
132/// Get the Cranelift signature with the native calling convention for the given
133/// Wasm function type.
134///
135/// This parameters will start with the callee and caller VM contexts, followed
136/// by the translation of each of the Wasm parameter types to native types. The
137/// results are the Wasm result types translated to native types.
138///
139/// The signature uses the wasmtime variant of the target's default calling
140/// convention. The only difference from the default calling convention is how
141/// multiple results are handled.
142///
143/// When there is only a single result, or zero results, these signatures are
144/// suitable for calling from the host via
145///
146/// ```ignore
147/// unsafe extern "C" fn(
148///     callee_vmctx: *mut VMOpaqueContext,
149///     caller_vmctx: *mut VMOpaqueContext,
150///     // ...wasm parameter types...
151/// ) -> // ...wasm result type...
152/// ```
153///
154/// When there are more than one results, these signatures are suitable for
155/// calling from the host via
156///
157/// ```ignore
158/// unsafe extern "C" fn(
159///     callee_vmctx: *mut VMOpaqueContext,
160///     caller_vmctx: *mut VMOpaqueContext,
161///     // ...wasm parameter types...
162///     retptr: *mut (),
163/// ) -> // ...wasm result type 0...
164/// ```
165///
166/// where the first result is returned directly and the rest via the return
167/// pointer.
168fn native_call_signature(isa: &dyn TargetIsa, wasm: &WasmFuncType) -> ir::Signature {
169    let mut sig = blank_sig(isa, CallConv::triple_default(isa.triple()));
170    let cvt = |ty: &WasmValType| ir::AbiParam::new(value_type(isa, *ty));
171    sig.params.extend(wasm.params().iter().map(&cvt));
172    if let Some(first_ret) = wasm.returns().get(0) {
173        sig.returns.push(cvt(first_ret));
174    }
175    if wasm.returns().len() > 1 {
176        sig.params.push(ir::AbiParam::new(isa.pointer_type()));
177    }
178    sig
179}
180
181/// Get the Cranelift signature for all array-call functions, that is:
182///
183/// ```ignore
184/// unsafe extern "C" fn(
185///     callee_vmctx: *mut VMOpaqueContext,
186///     caller_vmctx: *mut VMOpaqueContext,
187///     values_ptr: *mut ValRaw,
188///     values_len: usize,
189/// )
190/// ```
191///
192/// This signature uses the target's default calling convention.
193///
194/// Note that regardless of the Wasm function type, the array-call calling
195/// convention always uses that same signature.
196fn array_call_signature(isa: &dyn TargetIsa) -> ir::Signature {
197    let mut sig = blank_sig(isa, CallConv::triple_default(isa.triple()));
198    // The array-call signature has an added parameter for the `values_vec`
199    // input/output buffer in addition to the size of the buffer, in units
200    // of `ValRaw`.
201    sig.params.push(ir::AbiParam::new(isa.pointer_type()));
202    sig.params.push(ir::AbiParam::new(isa.pointer_type()));
203    sig
204}
205
206/// Get the internal Wasm calling convention signature for the given type.
207fn wasm_call_signature(
208    isa: &dyn TargetIsa,
209    wasm_func_ty: &WasmFuncType,
210    tunables: &Tunables,
211) -> ir::Signature {
212    // NB: this calling convention in the near future is expected to be
213    // unconditionally switched to the "tail" calling convention once all
214    // platforms have support for tail calls.
215    //
216    // Also note that the calling convention for wasm functions is purely an
217    // internal implementation detail of cranelift and Wasmtime. Native Rust
218    // code does not interact with raw wasm functions and instead always
219    // operates through trampolines either using the `array_call_signature` or
220    // `native_call_signature` where the default platform ABI is used.
221    let call_conv = match isa.triple().architecture {
222        // If the tail calls proposal is enabled, we must use the tail calling
223        // convention. We don't use it by default yet because of
224        // https://github.com/bytecodealliance/wasmtime/issues/6759
225        arch if tunables.tail_callable => {
226            assert_ne!(
227                arch,
228                Architecture::S390x,
229                "https://github.com/bytecodealliance/wasmtime/issues/6530"
230            );
231            CallConv::Tail
232        }
233
234        // The winch calling convention is only implemented for x64 and aarch64
235        arch if tunables.winch_callable => {
236            assert!(
237                matches!(arch, Architecture::X86_64 | Architecture::Aarch64(_)),
238                "The Winch calling convention is only implemented for x86_64 and aarch64"
239            );
240            CallConv::Winch
241        }
242
243        // On s390x the "wasmtime" calling convention is used to give vectors
244        // little-endian lane order at the ABI layer which should reduce the
245        // need for conversion when operating on vector function arguments. By
246        // default vectors on s390x are otherwise in big-endian lane order which
247        // would require conversions.
248        Architecture::S390x => CallConv::WasmtimeSystemV,
249
250        // All other platforms pick "fast" as the calling convention since it's
251        // presumably, well, the fastest.
252        _ => CallConv::Fast,
253    };
254    let mut sig = blank_sig(isa, call_conv);
255    let cvt = |ty: &WasmValType| ir::AbiParam::new(value_type(isa, *ty));
256    sig.params.extend(wasm_func_ty.params().iter().map(&cvt));
257    sig.returns.extend(wasm_func_ty.returns().iter().map(&cvt));
258    sig
259}
260
261/// Returns the reference type to use for the provided wasm type.
262fn reference_type(wasm_ht: WasmHeapType, pointer_type: ir::Type) -> ir::Type {
263    match wasm_ht {
264        WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => pointer_type,
265        WasmHeapType::Extern | WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
266            match pointer_type {
267                ir::types::I32 => ir::types::R32,
268                ir::types::I64 => ir::types::R64,
269                _ => panic!("unsupported pointer type"),
270            }
271        }
272    }
273}
274
275// List of namespaces which are processed in `mach_reloc_to_reloc` below.
276
277/// Namespace corresponding to wasm functions, the index is the index of the
278/// defined function that's being referenced.
279pub const NS_WASM_FUNC: u32 = 0;
280
281/// Namespace for builtin function trampolines. The index is the index of the
282/// builtin that's being referenced. These trampolines invoke the real host
283/// function through an indirect function call loaded by the `VMContext`.
284pub const NS_WASMTIME_BUILTIN: u32 = 1;
285
286/// A record of a relocation to perform.
287#[derive(Debug, Clone, PartialEq, Eq)]
288pub struct Relocation {
289    /// The relocation code.
290    pub reloc: binemit::Reloc,
291    /// Relocation target.
292    pub reloc_target: RelocationTarget,
293    /// The offset where to apply the relocation.
294    pub offset: binemit::CodeOffset,
295    /// The addend to add to the relocation value.
296    pub addend: binemit::Addend,
297}
298
299/// Converts cranelift_codegen settings to the wasmtime_environ equivalent.
300pub fn clif_flags_to_wasmtime(
301    flags: impl IntoIterator<Item = settings::Value>,
302) -> Vec<(&'static str, FlagValue<'static>)> {
303    flags
304        .into_iter()
305        .map(|val| (val.name, to_flag_value(&val)))
306        .collect()
307}
308
309fn to_flag_value(v: &settings::Value) -> FlagValue<'static> {
310    match v.kind() {
311        settings::SettingKind::Enum => FlagValue::Enum(v.as_enum().unwrap()),
312        settings::SettingKind::Num => FlagValue::Num(v.as_num().unwrap()),
313        settings::SettingKind::Bool => FlagValue::Bool(v.as_bool().unwrap()),
314        settings::SettingKind::Preset => unreachable!(),
315    }
316}
317
318/// A custom code with `TrapCode::User` which is used by always-trap shims which
319/// indicates that, as expected, the always-trapping function indeed did trap.
320/// This effectively provides a better error message as opposed to a bland
321/// "unreachable code reached"
322pub const ALWAYS_TRAP_CODE: u16 = 100;
323
324/// A custom code with `TrapCode::User` corresponding to being unable to reenter
325/// a component due to its reentrance limitations. This is used in component
326/// adapters to provide a more useful error message in such situations.
327pub const CANNOT_ENTER_CODE: u16 = 101;
328
329/// Converts machine traps to trap information.
330pub fn mach_trap_to_trap(trap: &MachTrap) -> Option<TrapInformation> {
331    let &MachTrap { offset, code } = trap;
332    Some(TrapInformation {
333        code_offset: offset,
334        trap_code: match code {
335            ir::TrapCode::StackOverflow => Trap::StackOverflow,
336            ir::TrapCode::HeapOutOfBounds => Trap::MemoryOutOfBounds,
337            ir::TrapCode::HeapMisaligned => Trap::HeapMisaligned,
338            ir::TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,
339            ir::TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,
340            ir::TrapCode::BadSignature => Trap::BadSignature,
341            ir::TrapCode::IntegerOverflow => Trap::IntegerOverflow,
342            ir::TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,
343            ir::TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,
344            ir::TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,
345            ir::TrapCode::Interrupt => Trap::Interrupt,
346            ir::TrapCode::User(ALWAYS_TRAP_CODE) => Trap::AlwaysTrapAdapter,
347            ir::TrapCode::User(CANNOT_ENTER_CODE) => Trap::CannotEnterComponent,
348            ir::TrapCode::NullReference => Trap::NullReference,
349            ir::TrapCode::NullI31Ref => Trap::NullI31Ref,
350
351            // These do not get converted to wasmtime traps, since they
352            // shouldn't ever be hit in theory. Instead of catching and handling
353            // these, we let the signal crash the process.
354            ir::TrapCode::User(DEBUG_ASSERT_TRAP_CODE) => return None,
355
356            // these should never be emitted by wasmtime-cranelift
357            ir::TrapCode::User(_) => unreachable!(),
358        },
359    })
360}
361
362/// Converts machine relocations to relocation information
363/// to perform.
364fn mach_reloc_to_reloc(
365    reloc: &FinalizedMachReloc,
366    name_map: &PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>,
367) -> Relocation {
368    let &FinalizedMachReloc {
369        offset,
370        kind,
371        ref target,
372        addend,
373    } = reloc;
374    let reloc_target = match *target {
375        FinalizedRelocTarget::ExternalName(ExternalName::User(user_func_ref)) => {
376            let name = &name_map[user_func_ref];
377            match name.namespace {
378                NS_WASM_FUNC => RelocationTarget::Wasm(FuncIndex::from_u32(name.index)),
379                NS_WASMTIME_BUILTIN => {
380                    RelocationTarget::Builtin(BuiltinFunctionIndex::from_u32(name.index))
381                }
382                _ => panic!("unknown namespace {}", name.namespace),
383            }
384        }
385        FinalizedRelocTarget::ExternalName(ExternalName::LibCall(libcall)) => {
386            let libcall = libcall_cranelift_to_wasmtime(libcall);
387            RelocationTarget::HostLibcall(libcall)
388        }
389        _ => panic!("unrecognized external name"),
390    };
391    Relocation {
392        reloc: kind,
393        reloc_target,
394        offset,
395        addend,
396    }
397}
398
399fn libcall_cranelift_to_wasmtime(call: ir::LibCall) -> wasmtime_environ::obj::LibCall {
400    use wasmtime_environ::obj::LibCall as LC;
401    match call {
402        ir::LibCall::FloorF32 => LC::FloorF32,
403        ir::LibCall::FloorF64 => LC::FloorF64,
404        ir::LibCall::NearestF32 => LC::NearestF32,
405        ir::LibCall::NearestF64 => LC::NearestF64,
406        ir::LibCall::CeilF32 => LC::CeilF32,
407        ir::LibCall::CeilF64 => LC::CeilF64,
408        ir::LibCall::TruncF32 => LC::TruncF32,
409        ir::LibCall::TruncF64 => LC::TruncF64,
410        ir::LibCall::FmaF32 => LC::FmaF32,
411        ir::LibCall::FmaF64 => LC::FmaF64,
412        ir::LibCall::X86Pshufb => LC::X86Pshufb,
413        _ => panic!("cranelift emitted a libcall wasmtime does not support: {call:?}"),
414    }
415}
416
417/// Helper structure for creating a `Signature` for all builtins.
418struct BuiltinFunctionSignatures {
419    pointer_type: ir::Type,
420
421    #[cfg(feature = "gc")]
422    reference_type: ir::Type,
423
424    call_conv: CallConv,
425}
426
427impl BuiltinFunctionSignatures {
428    fn new(isa: &dyn TargetIsa) -> Self {
429        Self {
430            pointer_type: isa.pointer_type(),
431            #[cfg(feature = "gc")]
432            reference_type: match isa.pointer_type() {
433                ir::types::I32 => ir::types::R32,
434                ir::types::I64 => ir::types::R64,
435                _ => panic!(),
436            },
437            call_conv: CallConv::triple_default(isa.triple()),
438        }
439    }
440
441    fn vmctx(&self) -> AbiParam {
442        AbiParam::special(self.pointer_type, ArgumentPurpose::VMContext)
443    }
444
445    #[cfg(feature = "gc")]
446    fn reference(&self) -> AbiParam {
447        AbiParam::new(self.reference_type)
448    }
449
450    fn pointer(&self) -> AbiParam {
451        AbiParam::new(self.pointer_type)
452    }
453
454    fn i32(&self) -> AbiParam {
455        // Some platform ABIs require i32 values to be zero- or sign-
456        // extended to the full register width.  We need to indicate
457        // this here by using the appropriate .uext or .sext attribute.
458        // The attribute can be added unconditionally; platforms whose
459        // ABI does not require such extensions will simply ignore it.
460        // Note that currently all i32 arguments or return values used
461        // by builtin functions are unsigned, so we always use .uext.
462        // If that ever changes, we will have to add a second type
463        // marker here.
464        AbiParam::new(ir::types::I32).uext()
465    }
466
467    fn i64(&self) -> AbiParam {
468        AbiParam::new(ir::types::I64)
469    }
470
471    fn signature(&self, builtin: BuiltinFunctionIndex) -> Signature {
472        let mut _cur = 0;
473        macro_rules! iter {
474            (
475                $(
476                    $( #[$attr:meta] )*
477                    $name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
478                )*
479            ) => {
480                $(
481                    $( #[$attr] )*
482                    if _cur == builtin.index() {
483                        return Signature {
484                            params: vec![ $( self.$param() ),* ],
485                            returns: vec![ $( self.$result() )? ],
486                            call_conv: self.call_conv,
487                        };
488                    }
489                    _cur += 1;
490                )*
491            };
492        }
493
494        wasmtime_environ::foreach_builtin_function!(iter);
495
496        unreachable!();
497    }
498}
499
500/// If this bit is set on a GC reference, then the GC reference is actually an
501/// unboxed `i31`.
502///
503/// Must be kept in sync with
504/// `wasmtime_runtime::gc::VMGcRef::I31_REF_DISCRIMINANT`.
505const I31_REF_DISCRIMINANT: u32 = 1;