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