wasmtime_internal_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//! > **⚠️ 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
17use 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};
24use cranelift_entity::PrimaryMap;
25
26use target_lexicon::Architecture;
27use wasmtime_environ::{
28    BuiltinFunctionIndex, FlagValue, FuncIndex, RelocationTarget, Trap, TrapInformation, Tunables,
29    WasmFuncType, WasmHeapTopType, WasmHeapType, WasmValType,
30};
31
32pub use builder::builder;
33
34pub mod isa_builder;
35mod obj;
36pub use obj::*;
37mod compiled_function;
38pub use compiled_function::*;
39
40mod bounds_checks;
41mod builder;
42mod compiler;
43mod debug;
44mod func_environ;
45mod translate;
46
47use self::compiler::Compiler;
48
49const TRAP_INTERNAL_ASSERT: TrapCode = TrapCode::unwrap_user(1);
50const TRAP_OFFSET: u8 = 2;
51pub const TRAP_ALWAYS: TrapCode =
52    TrapCode::unwrap_user(Trap::AlwaysTrapAdapter as u8 + TRAP_OFFSET);
53pub const TRAP_CANNOT_ENTER: TrapCode =
54    TrapCode::unwrap_user(Trap::CannotEnterComponent as u8 + TRAP_OFFSET);
55pub const TRAP_INDIRECT_CALL_TO_NULL: TrapCode =
56    TrapCode::unwrap_user(Trap::IndirectCallToNull as u8 + TRAP_OFFSET);
57pub const TRAP_BAD_SIGNATURE: TrapCode =
58    TrapCode::unwrap_user(Trap::BadSignature as u8 + TRAP_OFFSET);
59pub const TRAP_NULL_REFERENCE: TrapCode =
60    TrapCode::unwrap_user(Trap::NullReference as u8 + TRAP_OFFSET);
61pub const TRAP_ALLOCATION_TOO_LARGE: TrapCode =
62    TrapCode::unwrap_user(Trap::AllocationTooLarge as u8 + TRAP_OFFSET);
63pub const TRAP_ARRAY_OUT_OF_BOUNDS: TrapCode =
64    TrapCode::unwrap_user(Trap::ArrayOutOfBounds as u8 + TRAP_OFFSET);
65pub const TRAP_UNREACHABLE: TrapCode =
66    TrapCode::unwrap_user(Trap::UnreachableCodeReached as u8 + TRAP_OFFSET);
67pub const TRAP_HEAP_MISALIGNED: TrapCode =
68    TrapCode::unwrap_user(Trap::HeapMisaligned as u8 + TRAP_OFFSET);
69pub const TRAP_TABLE_OUT_OF_BOUNDS: TrapCode =
70    TrapCode::unwrap_user(Trap::TableOutOfBounds as u8 + TRAP_OFFSET);
71pub const TRAP_UNHANDLED_TAG: TrapCode =
72    TrapCode::unwrap_user(Trap::UnhandledTag as u8 + TRAP_OFFSET);
73pub const TRAP_CONTINUATION_ALREADY_CONSUMED: TrapCode =
74    TrapCode::unwrap_user(Trap::ContinuationAlreadyConsumed as u8 + TRAP_OFFSET);
75pub 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.
82fn 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.
102fn 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.
121fn 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.
134fn 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.
160fn 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
173fn 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.
198fn 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.
212fn 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        // TODO(10248) This is added in a follow-up PR
218        {
219            unimplemented!("codegen for stack switching types not implemented, yet")
220        }
221    }
222}
223
224// List of namespaces which are processed in `mach_reloc_to_reloc` below.
225
226/// Namespace corresponding to wasm functions, the index is the index of the
227/// defined function that's being referenced.
228pub const NS_WASM_FUNC: u32 = 0;
229
230/// Namespace for builtin function trampolines. The index is the index of the
231/// builtin that's being referenced. These trampolines invoke the real host
232/// function through an indirect function call loaded by the `VMContext`.
233pub const NS_WASMTIME_BUILTIN: u32 = 1;
234
235/// Namespace used to when a call from Pulley to the host is being made. This is
236/// used with a `colocated: false` name to trigger codegen for a special opcode
237/// for pulley-to-host communication. The index of the functions used in this
238/// namespace correspond to the function signature of `for_each_host_signature!`
239/// in the pulley_interpreter crate.
240pub const NS_PULLEY_HOSTCALL: u32 = 2;
241
242/// A record of a relocation to perform.
243#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct Relocation {
245    /// The relocation code.
246    pub reloc: binemit::Reloc,
247    /// Relocation target.
248    pub reloc_target: RelocationTarget,
249    /// The offset where to apply the relocation.
250    pub offset: binemit::CodeOffset,
251    /// The addend to add to the relocation value.
252    pub addend: binemit::Addend,
253}
254
255/// Converts cranelift_codegen settings to the wasmtime_environ equivalent.
256pub fn clif_flags_to_wasmtime(
257    flags: impl IntoIterator<Item = settings::Value>,
258) -> Vec<(&'static str, FlagValue<'static>)> {
259    flags
260        .into_iter()
261        .map(|val| (val.name, to_flag_value(&val)))
262        .collect()
263}
264
265fn to_flag_value(v: &settings::Value) -> FlagValue<'static> {
266    match v.kind() {
267        settings::SettingKind::Enum => FlagValue::Enum(v.as_enum().unwrap()),
268        settings::SettingKind::Num => FlagValue::Num(v.as_num().unwrap()),
269        settings::SettingKind::Bool => FlagValue::Bool(v.as_bool().unwrap()),
270        settings::SettingKind::Preset => unreachable!(),
271    }
272}
273
274/// Converts machine traps to trap information.
275pub fn mach_trap_to_trap(trap: &MachTrap) -> Option<TrapInformation> {
276    let &MachTrap { offset, code } = trap;
277    Some(TrapInformation {
278        code_offset: offset,
279        trap_code: clif_trap_to_env_trap(code)?,
280    })
281}
282
283fn clif_trap_to_env_trap(trap: ir::TrapCode) -> Option<Trap> {
284    Some(match trap {
285        ir::TrapCode::STACK_OVERFLOW => Trap::StackOverflow,
286        ir::TrapCode::HEAP_OUT_OF_BOUNDS => Trap::MemoryOutOfBounds,
287        ir::TrapCode::INTEGER_OVERFLOW => Trap::IntegerOverflow,
288        ir::TrapCode::INTEGER_DIVISION_BY_ZERO => Trap::IntegerDivisionByZero,
289        ir::TrapCode::BAD_CONVERSION_TO_INTEGER => Trap::BadConversionToInteger,
290
291        // These do not get converted to wasmtime traps, since they
292        // shouldn't ever be hit in theory. Instead of catching and handling
293        // these, we let the signal crash the process.
294        TRAP_INTERNAL_ASSERT => return None,
295
296        other => Trap::from_u8(other.as_raw().get() - TRAP_OFFSET).unwrap(),
297    })
298}
299
300/// Converts machine relocations to relocation information
301/// to perform.
302fn mach_reloc_to_reloc(
303    reloc: &FinalizedMachReloc,
304    name_map: &PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>,
305) -> Relocation {
306    let &FinalizedMachReloc {
307        offset,
308        kind,
309        ref target,
310        addend,
311    } = reloc;
312    let reloc_target = match *target {
313        FinalizedRelocTarget::ExternalName(ExternalName::User(user_func_ref)) => {
314            let name = &name_map[user_func_ref];
315            match name.namespace {
316                NS_WASM_FUNC => RelocationTarget::Wasm(FuncIndex::from_u32(name.index)),
317                NS_WASMTIME_BUILTIN => {
318                    RelocationTarget::Builtin(BuiltinFunctionIndex::from_u32(name.index))
319                }
320                NS_PULLEY_HOSTCALL => RelocationTarget::PulleyHostcall(name.index),
321                _ => panic!("unknown namespace {}", name.namespace),
322            }
323        }
324        FinalizedRelocTarget::ExternalName(ExternalName::LibCall(libcall)) => {
325            // We should have avoided any code that needs this style of libcalls
326            // in the Wasm-to-Cranelift translator.
327            panic!("unexpected libcall {libcall:?}");
328        }
329        _ => panic!("unrecognized external name"),
330    };
331    Relocation {
332        reloc: kind,
333        reloc_target,
334        offset,
335        addend,
336    }
337}
338
339/// Helper structure for creating a `Signature` for all builtins.
340struct BuiltinFunctionSignatures {
341    pointer_type: ir::Type,
342
343    host_call_conv: CallConv,
344    wasm_call_conv: CallConv,
345    argument_extension: ir::ArgumentExtension,
346}
347
348impl BuiltinFunctionSignatures {
349    fn new(compiler: &Compiler) -> Self {
350        Self {
351            pointer_type: compiler.isa().pointer_type(),
352            host_call_conv: CallConv::triple_default(compiler.isa().triple()),
353            wasm_call_conv: wasm_call_conv(compiler.isa(), compiler.tunables()),
354            argument_extension: compiler.isa().default_argument_extension(),
355        }
356    }
357
358    fn vmctx(&self) -> AbiParam {
359        AbiParam::special(self.pointer_type, ArgumentPurpose::VMContext)
360    }
361
362    fn pointer(&self) -> AbiParam {
363        AbiParam::new(self.pointer_type)
364    }
365
366    fn u32(&self) -> AbiParam {
367        AbiParam::new(ir::types::I32)
368    }
369
370    fn u64(&self) -> AbiParam {
371        AbiParam::new(ir::types::I64)
372    }
373
374    fn f32(&self) -> AbiParam {
375        AbiParam::new(ir::types::F32)
376    }
377
378    fn f64(&self) -> AbiParam {
379        AbiParam::new(ir::types::F64)
380    }
381
382    fn u8(&self) -> AbiParam {
383        AbiParam::new(ir::types::I8)
384    }
385
386    fn i8x16(&self) -> AbiParam {
387        AbiParam::new(ir::types::I8X16)
388    }
389
390    fn f32x4(&self) -> AbiParam {
391        AbiParam::new(ir::types::F32X4)
392    }
393
394    fn f64x2(&self) -> AbiParam {
395        AbiParam::new(ir::types::F64X2)
396    }
397
398    fn bool(&self) -> AbiParam {
399        AbiParam::new(ir::types::I8)
400    }
401
402    fn wasm_signature(&self, builtin: BuiltinFunctionIndex) -> Signature {
403        let mut _cur = 0;
404        macro_rules! iter {
405            (
406                $(
407                    $( #[$attr:meta] )*
408                    $name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
409                )*
410            ) => {
411                $(
412                    $( #[$attr] )*
413                    if _cur == builtin.index() {
414                        return Signature {
415                            params: vec![ $( self.$param() ),* ],
416                            returns: vec![ $( self.$result() )? ],
417                            call_conv: self.wasm_call_conv,
418                        };
419                    }
420                    _cur += 1;
421                )*
422            };
423        }
424
425        wasmtime_environ::foreach_builtin_function!(iter);
426
427        unreachable!();
428    }
429
430    fn host_signature(&self, builtin: BuiltinFunctionIndex) -> Signature {
431        let mut sig = self.wasm_signature(builtin);
432        sig.call_conv = self.host_call_conv;
433
434        // Once we're declaring the signature of a host function we must
435        // respect the default ABI of the platform which is where argument
436        // extension of params/results may come into play.
437        for arg in sig.params.iter_mut().chain(sig.returns.iter_mut()) {
438            if arg.value_type.is_int() {
439                arg.extension = self.argument_extension;
440            }
441        }
442
443        sig
444    }
445}
446
447/// If this bit is set on a GC reference, then the GC reference is actually an
448/// unboxed `i31`.
449///
450/// Must be kept in sync with
451/// `crate::runtime::vm::gc::VMGcRef::I31_REF_DISCRIMINANT`.
452const I31_REF_DISCRIMINANT: u32 = 1;
453
454/// Like `Option<T>` but specifically for passing information about transitions
455/// from reachable to unreachable state and the like from callees to callers.
456///
457/// Marked `must_use` to force callers to update
458/// `FuncTranslationState::reachable` as necessary.
459#[derive(PartialEq, Eq)]
460#[must_use]
461enum Reachability<T> {
462    /// The Wasm execution state is reachable, here is a `T`.
463    Reachable(T),
464    /// The Wasm execution state has been determined to be statically
465    /// unreachable. It is the receiver of this value's responsibility to update
466    /// `FuncTranslationState::reachable` as necessary.
467    Unreachable,
468}