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