Skip to main content

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, FuncKey, Trap, TrapInformation, Tunables, WasmFuncType,
29    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;
46mod trap;
47
48use self::compiler::Compiler;
49
50const TRAP_INTERNAL_ASSERT: TrapCode = TrapCode::unwrap_user(1);
51const TRAP_OFFSET: u8 = 2;
52pub const TRAP_CANNOT_LEAVE_COMPONENT: TrapCode =
53    TrapCode::unwrap_user(Trap::CannotLeaveComponent as u8 + TRAP_OFFSET);
54pub const TRAP_INDIRECT_CALL_TO_NULL: TrapCode =
55    TrapCode::unwrap_user(Trap::IndirectCallToNull as u8 + TRAP_OFFSET);
56pub const TRAP_BAD_SIGNATURE: TrapCode =
57    TrapCode::unwrap_user(Trap::BadSignature as u8 + TRAP_OFFSET);
58pub const TRAP_NULL_REFERENCE: TrapCode =
59    TrapCode::unwrap_user(Trap::NullReference as u8 + TRAP_OFFSET);
60pub const TRAP_ALLOCATION_TOO_LARGE: TrapCode =
61    TrapCode::unwrap_user(Trap::AllocationTooLarge as u8 + TRAP_OFFSET);
62pub const TRAP_ARRAY_OUT_OF_BOUNDS: TrapCode =
63    TrapCode::unwrap_user(Trap::ArrayOutOfBounds as u8 + TRAP_OFFSET);
64pub const TRAP_UNREACHABLE: TrapCode =
65    TrapCode::unwrap_user(Trap::UnreachableCodeReached as u8 + TRAP_OFFSET);
66pub const TRAP_HEAP_MISALIGNED: TrapCode =
67    TrapCode::unwrap_user(Trap::HeapMisaligned as u8 + TRAP_OFFSET);
68pub const TRAP_TABLE_OUT_OF_BOUNDS: TrapCode =
69    TrapCode::unwrap_user(Trap::TableOutOfBounds as u8 + TRAP_OFFSET);
70pub const TRAP_UNHANDLED_TAG: TrapCode =
71    TrapCode::unwrap_user(Trap::UnhandledTag as u8 + TRAP_OFFSET);
72pub const TRAP_CONTINUATION_ALREADY_CONSUMED: TrapCode =
73    TrapCode::unwrap_user(Trap::ContinuationAlreadyConsumed as u8 + TRAP_OFFSET);
74pub const TRAP_CAST_FAILURE: TrapCode =
75    TrapCode::unwrap_user(Trap::CastFailure as u8 + TRAP_OFFSET);
76
77/// Creates a new cranelift `Signature` with no wasm params/results for the
78/// given calling convention.
79///
80/// This will add the default vmctx/etc parameters to the signature returned.
81fn blank_sig(isa: &dyn TargetIsa, call_conv: CallConv) -> ir::Signature {
82    let pointer_type = isa.pointer_type();
83    let mut sig = ir::Signature::new(call_conv);
84    // Add the caller/callee `vmctx` parameters.
85    sig.params.push(ir::AbiParam::special(
86        pointer_type,
87        ir::ArgumentPurpose::VMContext,
88    ));
89    sig.params.push(ir::AbiParam::new(pointer_type));
90    return sig;
91}
92
93/// Emit code for the following unbarriered memory write of the given type:
94///
95/// ```ignore
96/// *(base + offset) = value
97/// ```
98///
99/// This is intended to be used with things like `ValRaw` and the array calling
100/// convention.
101fn unbarriered_store_type_at_offset(
102    pos: &mut FuncCursor,
103    flags: ir::MemFlags,
104    base: ir::Value,
105    offset: i32,
106    value: ir::Value,
107) {
108    pos.ins().store(flags, value, base, offset);
109}
110
111/// Emit code to do the following unbarriered memory read of the given type and
112/// with the given flags:
113///
114/// ```ignore
115/// result = *(base + offset)
116/// ```
117///
118/// This is intended to be used with things like `ValRaw` and the array calling
119/// convention.
120fn unbarriered_load_type_at_offset(
121    isa: &dyn TargetIsa,
122    pos: &mut FuncCursor,
123    ty: WasmValType,
124    flags: ir::MemFlags,
125    base: ir::Value,
126    offset: i32,
127) -> ir::Value {
128    let ir_ty = value_type(isa, ty);
129    pos.ins().load(ir_ty, flags, base, offset)
130}
131
132/// Returns the corresponding cranelift type for the provided wasm type.
133fn value_type(isa: &dyn TargetIsa, ty: WasmValType) -> ir::types::Type {
134    match ty {
135        WasmValType::I32 => ir::types::I32,
136        WasmValType::I64 => ir::types::I64,
137        WasmValType::F32 => ir::types::F32,
138        WasmValType::F64 => ir::types::F64,
139        WasmValType::V128 => ir::types::I8X16,
140        WasmValType::Ref(rt) => reference_type(rt.heap_type, isa.pointer_type()),
141    }
142}
143
144/// Get the Cranelift signature for all array-call functions, that is:
145///
146/// ```ignore
147/// unsafe extern "C" fn(
148///     callee_vmctx: *mut VMOpaqueContext,
149///     caller_vmctx: *mut VMOpaqueContext,
150///     values_ptr: *mut ValRaw,
151///     values_len: usize,
152/// )
153/// ```
154///
155/// This signature uses the target's default calling convention.
156///
157/// Note that regardless of the Wasm function type, the array-call calling
158/// convention always uses that same signature.
159fn array_call_signature(isa: &dyn TargetIsa) -> ir::Signature {
160    let mut sig = blank_sig(isa, CallConv::triple_default(isa.triple()));
161    // The array-call signature has an added parameter for the `values_vec`
162    // input/output buffer in addition to the size of the buffer, in units
163    // of `ValRaw`.
164    sig.params.push(ir::AbiParam::new(isa.pointer_type()));
165    sig.params.push(ir::AbiParam::new(isa.pointer_type()));
166    // boolean return value of whether this function trapped
167    sig.returns.push(ir::AbiParam::new(ir::types::I8));
168    sig
169}
170
171/// Get the internal Wasm calling convention for the target/tunables combo
172fn wasm_call_conv(isa: &dyn TargetIsa, tunables: &Tunables) -> CallConv {
173    // The default calling convention is `CallConv::Tail` to enable the use of
174    // tail calls in modules when needed. Note that this is used even if the
175    // tail call proposal is disabled in wasm. This is not interacted with on
176    // the host so it's purely an internal detail of wasm itself.
177    //
178    // The Winch calling convention is used instead when generating trampolines
179    // which call Winch-generated functions. The winch calling convention is
180    // only implemented for x64 and aarch64, so assert that here and panic on
181    // other architectures.
182    if tunables.winch_callable {
183        assert!(
184            matches!(
185                isa.triple().architecture,
186                Architecture::X86_64 | Architecture::Aarch64(_)
187            ),
188            "The Winch calling convention is only implemented for x86_64 and aarch64"
189        );
190        CallConv::Winch
191    } else {
192        CallConv::Tail
193    }
194}
195
196/// Get the internal Wasm calling convention signature for the given type.
197fn wasm_call_signature(
198    isa: &dyn TargetIsa,
199    wasm_func_ty: &WasmFuncType,
200    tunables: &Tunables,
201) -> ir::Signature {
202    let call_conv = wasm_call_conv(isa, tunables);
203    let mut sig = blank_sig(isa, call_conv);
204    let cvt = |ty: &WasmValType| ir::AbiParam::new(value_type(isa, *ty));
205    sig.params.extend(wasm_func_ty.params().iter().map(&cvt));
206    sig.returns.extend(wasm_func_ty.results().iter().map(&cvt));
207    sig
208}
209
210/// Returns the reference type to use for the provided wasm type.
211fn reference_type(wasm_ht: WasmHeapType, pointer_type: ir::Type) -> ir::Type {
212    match wasm_ht.top() {
213        WasmHeapTopType::Func => pointer_type,
214        WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => ir::types::I32,
215        WasmHeapTopType::Cont => {
216            // VMContObj is 2 * pointer_size (pointer + usize revision)
217            ir::Type::int((2 * pointer_type.bits()).try_into().unwrap()).unwrap()
218        }
219    }
220}
221
222// List of namespaces which are processed in `mach_reloc_to_reloc` below.
223
224/// A record of a relocation to perform.
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub struct Relocation {
227    /// The relocation code.
228    pub reloc: binemit::Reloc,
229    /// Relocation target.
230    pub reloc_target: FuncKey,
231    /// The offset where to apply the relocation.
232    pub offset: binemit::CodeOffset,
233    /// The addend to add to the relocation value.
234    pub addend: binemit::Addend,
235}
236
237/// Converts cranelift_codegen settings to the wasmtime_environ equivalent.
238pub fn clif_flags_to_wasmtime(
239    flags: impl IntoIterator<Item = settings::Value>,
240) -> Vec<(&'static str, FlagValue<'static>)> {
241    flags
242        .into_iter()
243        .map(|val| (val.name, to_flag_value(&val)))
244        .collect()
245}
246
247fn to_flag_value(v: &settings::Value) -> FlagValue<'static> {
248    match v.kind() {
249        settings::SettingKind::Enum => FlagValue::Enum(v.as_enum().unwrap()),
250        settings::SettingKind::Num => FlagValue::Num(v.as_num().unwrap()),
251        settings::SettingKind::Bool => FlagValue::Bool(v.as_bool().unwrap()),
252        settings::SettingKind::Preset => unreachable!(),
253    }
254}
255
256/// Converts machine traps to trap information.
257pub fn mach_trap_to_trap(trap: &MachTrap) -> Option<TrapInformation> {
258    let &MachTrap { offset, code } = trap;
259    Some(TrapInformation {
260        code_offset: offset,
261        trap_code: clif_trap_to_env_trap(code)?,
262    })
263}
264
265fn clif_trap_to_env_trap(trap: ir::TrapCode) -> Option<Trap> {
266    Some(match trap {
267        ir::TrapCode::STACK_OVERFLOW => Trap::StackOverflow,
268        ir::TrapCode::HEAP_OUT_OF_BOUNDS => Trap::MemoryOutOfBounds,
269        ir::TrapCode::INTEGER_OVERFLOW => Trap::IntegerOverflow,
270        ir::TrapCode::INTEGER_DIVISION_BY_ZERO => Trap::IntegerDivisionByZero,
271        ir::TrapCode::BAD_CONVERSION_TO_INTEGER => Trap::BadConversionToInteger,
272
273        // These do not get converted to wasmtime traps, since they
274        // shouldn't ever be hit in theory. Instead of catching and handling
275        // these, we let the signal crash the process.
276        TRAP_INTERNAL_ASSERT => return None,
277
278        other => Trap::from_u8(other.as_raw().get() - TRAP_OFFSET).unwrap(),
279    })
280}
281
282/// Converts machine relocations to relocation information
283/// to perform.
284fn mach_reloc_to_reloc(
285    reloc: &FinalizedMachReloc,
286    name_map: &PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>,
287) -> Relocation {
288    let &FinalizedMachReloc {
289        offset,
290        kind,
291        ref target,
292        addend,
293    } = reloc;
294    let reloc_target = match *target {
295        FinalizedRelocTarget::ExternalName(ExternalName::User(user_func_ref)) => {
296            let name = &name_map[user_func_ref];
297            FuncKey::from_raw_parts(name.namespace, name.index)
298        }
299        FinalizedRelocTarget::ExternalName(ExternalName::LibCall(libcall)) => {
300            // We should have avoided any code that needs this style of libcalls
301            // in the Wasm-to-Cranelift translator.
302            panic!("unexpected libcall {libcall:?}");
303        }
304        _ => panic!("unrecognized external name {target:?}"),
305    };
306    Relocation {
307        reloc: kind,
308        reloc_target,
309        offset,
310        addend,
311    }
312}
313
314/// Helper structure for creating a `Signature` for all builtins.
315struct BuiltinFunctionSignatures {
316    pointer_type: ir::Type,
317
318    host_call_conv: CallConv,
319    wasm_call_conv: CallConv,
320    argument_extension: ir::ArgumentExtension,
321}
322
323impl BuiltinFunctionSignatures {
324    fn new(compiler: &Compiler) -> Self {
325        Self {
326            pointer_type: compiler.isa().pointer_type(),
327            host_call_conv: CallConv::triple_default(compiler.isa().triple()),
328            wasm_call_conv: wasm_call_conv(compiler.isa(), compiler.tunables()),
329            argument_extension: compiler.isa().default_argument_extension(),
330        }
331    }
332
333    fn vmctx(&self) -> AbiParam {
334        AbiParam::special(self.pointer_type, ArgumentPurpose::VMContext)
335    }
336
337    fn pointer(&self) -> AbiParam {
338        AbiParam::new(self.pointer_type)
339    }
340
341    fn u32(&self) -> AbiParam {
342        AbiParam::new(ir::types::I32)
343    }
344
345    fn u64(&self) -> AbiParam {
346        AbiParam::new(ir::types::I64)
347    }
348
349    fn f32(&self) -> AbiParam {
350        AbiParam::new(ir::types::F32)
351    }
352
353    fn f64(&self) -> AbiParam {
354        AbiParam::new(ir::types::F64)
355    }
356
357    fn u8(&self) -> AbiParam {
358        AbiParam::new(ir::types::I8)
359    }
360
361    fn i8x16(&self) -> AbiParam {
362        AbiParam::new(ir::types::I8X16)
363    }
364
365    fn f32x4(&self) -> AbiParam {
366        AbiParam::new(ir::types::F32X4)
367    }
368
369    fn f64x2(&self) -> AbiParam {
370        AbiParam::new(ir::types::F64X2)
371    }
372
373    fn bool(&self) -> AbiParam {
374        AbiParam::new(ir::types::I8)
375    }
376
377    #[cfg(feature = "stack-switching")]
378    fn size(&self) -> AbiParam {
379        AbiParam::new(self.pointer_type)
380    }
381
382    fn wasm_signature(&self, builtin: BuiltinFunctionIndex) -> Signature {
383        let mut _cur = 0;
384        macro_rules! iter {
385            (
386                $(
387                    $( #[$attr:meta] )*
388                    $name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
389                )*
390            ) => {
391                $(
392                    $( #[$attr] )*
393                    if _cur == builtin.index() {
394                        return Signature {
395                            params: vec![ $( self.$param() ),* ],
396                            returns: vec![ $( self.$result() )? ],
397                            call_conv: self.wasm_call_conv,
398                        };
399                    }
400                    _cur += 1;
401                )*
402            };
403        }
404
405        wasmtime_environ::foreach_builtin_function!(iter);
406
407        unreachable!();
408    }
409
410    fn host_signature(&self, builtin: BuiltinFunctionIndex) -> Signature {
411        let mut sig = self.wasm_signature(builtin);
412        sig.call_conv = self.host_call_conv;
413
414        // Once we're declaring the signature of a host function we must
415        // respect the default ABI of the platform which is where argument
416        // extension of params/results may come into play.
417        for arg in sig.params.iter_mut().chain(sig.returns.iter_mut()) {
418            if arg.value_type.is_int() {
419                arg.extension = self.argument_extension;
420            }
421        }
422
423        sig
424    }
425}
426
427/// If this bit is set on a GC reference, then the GC reference is actually an
428/// unboxed `i31`.
429///
430/// Must be kept in sync with
431/// `crate::runtime::vm::gc::VMGcRef::I31_REF_DISCRIMINANT`.
432const I31_REF_DISCRIMINANT: u32 = 1;
433
434/// Like `Option<T>` but specifically for passing information about transitions
435/// from reachable to unreachable state and the like from callees to callers.
436///
437/// Marked `must_use` to force callers to update
438/// `FuncTranslationStacks::reachable` as necessary.
439#[derive(PartialEq, Eq)]
440#[must_use]
441enum Reachability<T> {
442    /// The Wasm execution state is reachable, here is a `T`.
443    Reachable(T),
444    /// The Wasm execution state has been determined to be statically
445    /// unreachable. It is the receiver of this value's responsibility to update
446    /// `FuncTranslationStacks::reachable` as necessary.
447    Unreachable,
448}