Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/cranelift/src/compiler.rs
3067 views
1
use crate::TRAP_INTERNAL_ASSERT;
2
use crate::debug::DwarfSectionRelocTarget;
3
use crate::func_environ::FuncEnvironment;
4
use crate::translate::FuncTranslator;
5
use crate::{BuiltinFunctionSignatures, builder::LinkOptions, wasm_call_signature};
6
use crate::{CompiledFunction, ModuleTextBuilder, array_call_signature};
7
use cranelift_codegen::binemit::CodeOffset;
8
use cranelift_codegen::inline::InlineCommand;
9
use cranelift_codegen::ir::condcodes::IntCC;
10
use cranelift_codegen::ir::{self, InstBuilder, MemFlags, UserExternalName, UserFuncName, Value};
11
use cranelift_codegen::isa::CallConv;
12
use cranelift_codegen::isa::{
13
OwnedTargetIsa, TargetIsa,
14
unwind::{UnwindInfo, UnwindInfoKind},
15
};
16
use cranelift_codegen::print_errors::pretty_error;
17
use cranelift_codegen::{
18
CompiledCode, Context, FinalizedMachCallSite, MachBufferDebugTagList, MachBufferFrameLayout,
19
MachDebugTagPos,
20
};
21
use cranelift_entity::PrimaryMap;
22
use cranelift_frontend::FunctionBuilder;
23
use object::write::{Object, StandardSegment, SymbolId};
24
use object::{RelocationEncoding, RelocationFlags, RelocationKind, SectionKind};
25
use std::any::Any;
26
use std::borrow::Cow;
27
use std::cmp;
28
use std::collections::HashMap;
29
use std::mem;
30
use std::ops::Range;
31
use std::path;
32
use std::sync::{Arc, Mutex};
33
use wasmparser::{FuncValidatorAllocations, FunctionBody};
34
use wasmtime_environ::error::{Context as _, Result};
35
use wasmtime_environ::obj::{ELF_WASMTIME_EXCEPTIONS, ELF_WASMTIME_FRAMES};
36
use wasmtime_environ::{
37
Abi, AddressMapSection, BuiltinFunctionIndex, CacheStore, CompileError, CompiledFunctionBody,
38
DefinedFuncIndex, FlagValue, FrameInstPos, FrameStackShape, FrameStateSlotBuilder,
39
FrameTableBuilder, FuncKey, FunctionBodyData, FunctionLoc, HostCall, InliningCompiler,
40
ModuleTranslation, ModuleTypesBuilder, PtrSize, StackMapSection, StaticModuleIndex,
41
TrapEncodingBuilder, TrapSentinel, TripleExt, Tunables, WasmFuncType, WasmValType, prelude::*,
42
};
43
use wasmtime_unwinder::ExceptionTableBuilder;
44
45
#[cfg(feature = "component-model")]
46
mod component;
47
48
struct IncrementalCacheContext {
49
#[cfg(feature = "incremental-cache")]
50
cache_store: Arc<dyn CacheStore>,
51
num_hits: usize,
52
num_cached: usize,
53
}
54
55
struct CompilerContext {
56
func_translator: FuncTranslator,
57
codegen_context: Context,
58
incremental_cache_ctx: Option<IncrementalCacheContext>,
59
validator_allocations: FuncValidatorAllocations,
60
debug_slot_descriptor: Option<FrameStateSlotBuilder>,
61
abi: Option<Abi>,
62
}
63
64
impl Default for CompilerContext {
65
fn default() -> Self {
66
Self {
67
func_translator: FuncTranslator::new(),
68
codegen_context: Context::new(),
69
incremental_cache_ctx: None,
70
validator_allocations: Default::default(),
71
debug_slot_descriptor: None,
72
abi: None,
73
}
74
}
75
}
76
77
/// A compiler that compiles a WebAssembly module with Compiler, translating
78
/// the Wasm to Compiler IR, optimizing it and then translating to assembly.
79
pub struct Compiler {
80
tunables: Tunables,
81
contexts: Mutex<Vec<CompilerContext>>,
82
isa: OwnedTargetIsa,
83
emit_debug_checks: bool,
84
linkopts: LinkOptions,
85
cache_store: Option<Arc<dyn CacheStore>>,
86
clif_dir: Option<path::PathBuf>,
87
#[cfg(feature = "wmemcheck")]
88
pub(crate) wmemcheck: bool,
89
}
90
91
impl Drop for Compiler {
92
fn drop(&mut self) {
93
if self.cache_store.is_none() {
94
return;
95
}
96
97
let mut num_hits = 0;
98
let mut num_cached = 0;
99
for ctx in self.contexts.lock().unwrap().iter() {
100
if let Some(ref cache_ctx) = ctx.incremental_cache_ctx {
101
num_hits += cache_ctx.num_hits;
102
num_cached += cache_ctx.num_cached;
103
}
104
}
105
106
let total = num_hits + num_cached;
107
if num_hits + num_cached > 0 {
108
log::trace!(
109
"Incremental compilation cache stats: {}/{} = {}% (hits/lookup)\ncached: {}",
110
num_hits,
111
total,
112
(num_hits as f32) / (total as f32) * 100.0,
113
num_cached
114
);
115
}
116
}
117
}
118
119
impl Compiler {
120
pub fn new(
121
tunables: Tunables,
122
isa: OwnedTargetIsa,
123
cache_store: Option<Arc<dyn CacheStore>>,
124
emit_debug_checks: bool,
125
linkopts: LinkOptions,
126
clif_dir: Option<path::PathBuf>,
127
wmemcheck: bool,
128
) -> Compiler {
129
let _ = wmemcheck;
130
Compiler {
131
contexts: Default::default(),
132
tunables,
133
isa,
134
emit_debug_checks,
135
linkopts,
136
cache_store,
137
clif_dir,
138
#[cfg(feature = "wmemcheck")]
139
wmemcheck,
140
}
141
}
142
143
/// Perform an indirect call from Cranelift-generated code to native code in
144
/// Wasmtime itself.
145
///
146
/// For native platforms this is a simple `call_indirect` instruction but
147
/// for the Pulley backend this is special as it's transitioning from
148
/// Cranelift-generated bytecode to native code on the host. That requires a
149
/// special opcode in the interpreter and is modeled slightly differently in
150
/// Cranelift IR.
151
fn call_indirect_host(
152
&self,
153
builder: &mut FunctionBuilder<'_>,
154
hostcall: impl Into<HostCall>,
155
sig: ir::SigRef,
156
addr: Value,
157
args: &[Value],
158
) -> ir::Inst {
159
let signature = &builder.func.dfg.signatures[sig];
160
161
// When calling the host we should always be using the platform's
162
// default calling convention since it'll be calling Rust code in
163
// Wasmtime itself.
164
assert_eq!(signature.call_conv, self.isa.default_call_conv());
165
166
// If this target is actually pulley then the goal is to emit the custom
167
// `call_indirect_host` pulley opcode. That's encoded in Cranelift as a
168
// `call` instruction where the name is `colocated: false`. This will
169
// force a pulley-specific relocation to get emitted in addition to
170
// using the `call_indirect_host` instruction.
171
if self.isa.triple().is_pulley() {
172
let mut new_signature = signature.clone();
173
new_signature
174
.params
175
.insert(0, ir::AbiParam::new(self.isa.pointer_type()));
176
let new_sig = builder.func.import_signature(new_signature);
177
let key = FuncKey::PulleyHostCall(hostcall.into());
178
let (namespace, index) = key.into_raw_parts();
179
let name = ir::ExternalName::User(
180
builder
181
.func
182
.declare_imported_user_function(ir::UserExternalName { namespace, index }),
183
);
184
let func = builder.func.import_function(ir::ExtFuncData {
185
name,
186
signature: new_sig,
187
// This is the signal that a special `call_indirect_host`
188
// opcode is used to jump from pulley to the host.
189
colocated: false,
190
patchable: false,
191
});
192
let mut raw_args = vec![addr];
193
raw_args.extend_from_slice(args);
194
return builder.ins().call(func, &raw_args);
195
}
196
197
builder.ins().call_indirect(sig, addr, args)
198
}
199
}
200
201
fn box_dyn_any_compiled_function(f: CompiledFunction) -> Box<dyn Any + Send + Sync> {
202
let b = box_dyn_any(f);
203
debug_assert!(b.is::<CompiledFunction>());
204
b
205
}
206
207
fn box_dyn_any_compiler_context(ctx: Option<CompilerContext>) -> Box<dyn Any + Send + Sync> {
208
let b = box_dyn_any(ctx);
209
debug_assert!(b.is::<Option<CompilerContext>>());
210
b
211
}
212
213
fn box_dyn_any(x: impl Any + Send + Sync) -> Box<dyn Any + Send + Sync> {
214
log::trace!(
215
"making Box<dyn Any + Send + Sync> of {}",
216
std::any::type_name_of_val(&x)
217
);
218
let b = Box::new(x);
219
let r: &(dyn Any + Sync + Send) = &*b;
220
log::trace!(" --> {r:#p}");
221
b
222
}
223
224
impl wasmtime_environ::Compiler for Compiler {
225
fn inlining_compiler(&self) -> Option<&dyn wasmtime_environ::InliningCompiler> {
226
Some(self)
227
}
228
229
fn compile_function(
230
&self,
231
translation: &ModuleTranslation<'_>,
232
key: FuncKey,
233
input: FunctionBodyData<'_>,
234
types: &ModuleTypesBuilder,
235
symbol: &str,
236
) -> Result<CompiledFunctionBody, CompileError> {
237
log::trace!("compiling Wasm function: {key:?} = {symbol:?}");
238
239
let isa = &*self.isa;
240
let module = &translation.module;
241
242
let (module_index, def_func_index) = key.unwrap_defined_wasm_function();
243
debug_assert_eq!(translation.module_index(), module_index);
244
245
let func_index = module.func_index(def_func_index);
246
let sig = translation.module.functions[func_index]
247
.signature
248
.unwrap_module_type_index();
249
let wasm_func_ty = types[sig].unwrap_func();
250
251
let mut compiler = self.function_compiler();
252
253
let context = &mut compiler.cx.codegen_context;
254
context.func.signature = wasm_call_signature(isa, wasm_func_ty, &self.tunables);
255
let (namespace, index) = key.into_raw_parts();
256
context.func.name = UserFuncName::User(UserExternalName { namespace, index });
257
258
if self.tunables.debug_native {
259
context.func.collect_debug_info();
260
}
261
262
let mut func_env = FuncEnvironment::new(self, translation, types, wasm_func_ty, key);
263
264
// The `stack_limit` global value below is the implementation of stack
265
// overflow checks in Wasmtime.
266
//
267
// The Wasm spec defines that stack overflows will raise a trap, and
268
// there's also an added constraint where as an embedder you frequently
269
// are running host-provided code called from wasm. WebAssembly and
270
// native code currently share the same call stack, so Wasmtime needs to
271
// make sure that host-provided code will have enough call-stack
272
// available to it.
273
//
274
// The way that stack overflow is handled here is by adding a prologue
275
// check to all functions for how much native stack is remaining. The
276
// `VMContext` pointer is the first argument to all functions, and the
277
// first field of this structure is `*const VMStoreContext` and the
278
// third field of that is the stack limit. Note that the stack limit in
279
// this case means "if the stack pointer goes below this, trap". Each
280
// function which consumes stack space or isn't a leaf function starts
281
// off by loading the stack limit, checking it against the stack
282
// pointer, and optionally traps.
283
//
284
// This manual check allows the embedder to give wasm a relatively
285
// precise amount of stack allocation. Using this scheme we reserve a
286
// chunk of stack for wasm code relative from where wasm code was
287
// called. This ensures that native code called by wasm should have
288
// native stack space to run, and the numbers of stack spaces here
289
// should all be configurable for various embeddings.
290
//
291
// Note that this check is independent of each thread's stack guard page
292
// here. If the stack guard page is reached that's still considered an
293
// abort for the whole program since the runtime limits configured by
294
// the embedder should cause wasm to trap before it reaches that
295
// (ensuring the host has enough space as well for its functionality).
296
if !isa.triple().is_pulley() {
297
let vmctx = context
298
.func
299
.create_global_value(ir::GlobalValueData::VMContext);
300
let interrupts_ptr = context.func.create_global_value(ir::GlobalValueData::Load {
301
base: vmctx,
302
offset: i32::from(func_env.offsets.ptr.vmctx_store_context()).into(),
303
global_type: isa.pointer_type(),
304
flags: MemFlags::trusted().with_readonly(),
305
});
306
let stack_limit = context.func.create_global_value(ir::GlobalValueData::Load {
307
base: interrupts_ptr,
308
offset: i32::from(func_env.offsets.ptr.vmstore_context_stack_limit()).into(),
309
global_type: isa.pointer_type(),
310
flags: MemFlags::trusted(),
311
});
312
if self.tunables.signals_based_traps {
313
context.func.stack_limit = Some(stack_limit);
314
} else {
315
func_env.stack_limit_at_function_entry = Some(stack_limit);
316
}
317
}
318
let FunctionBodyData { validator, body } = input;
319
let mut validator =
320
validator.into_validator(mem::take(&mut compiler.cx.validator_allocations));
321
compiler.cx.func_translator.translate_body(
322
&mut validator,
323
body.clone(),
324
&mut context.func,
325
&mut func_env,
326
)?;
327
328
if self.tunables.inlining {
329
compiler
330
.cx
331
.codegen_context
332
.legalize(isa)
333
.map_err(|e| CompileError::Codegen(e.to_string()))?;
334
}
335
336
let needs_gc_heap = func_env.needs_gc_heap();
337
338
if let Some((_, slot_builder)) = func_env.state_slot {
339
compiler.cx.debug_slot_descriptor = Some(slot_builder);
340
}
341
342
let timing = cranelift_codegen::timing::take_current();
343
log::debug!("`{symbol}` translated to CLIF in {:?}", timing.total());
344
log::trace!("`{symbol}` timing info\n{timing}");
345
346
Ok(CompiledFunctionBody {
347
code: box_dyn_any_compiler_context(Some(compiler.cx)),
348
needs_gc_heap,
349
})
350
}
351
352
fn compile_array_to_wasm_trampoline(
353
&self,
354
translation: &ModuleTranslation<'_>,
355
types: &ModuleTypesBuilder,
356
key: FuncKey,
357
symbol: &str,
358
) -> Result<CompiledFunctionBody, CompileError> {
359
let (module_index, def_func_index) = key.unwrap_array_to_wasm_trampoline();
360
let func_index = translation.module.func_index(def_func_index);
361
let sig = translation.module.functions[func_index]
362
.signature
363
.unwrap_module_type_index();
364
self.array_to_wasm_trampoline(
365
key,
366
FuncKey::DefinedWasmFunction(module_index, def_func_index),
367
types[sig].unwrap_func(),
368
symbol,
369
self.isa.pointer_bytes().vmctx_store_context().into(),
370
wasmtime_environ::VMCONTEXT_MAGIC,
371
)
372
}
373
374
fn compile_wasm_to_array_trampoline(
375
&self,
376
wasm_func_ty: &WasmFuncType,
377
key: FuncKey,
378
symbol: &str,
379
) -> Result<CompiledFunctionBody, CompileError> {
380
log::trace!("compiling wasm-to-array trampoline: {key:?} = {symbol:?}");
381
382
let isa = &*self.isa;
383
let pointer_type = isa.pointer_type();
384
let wasm_call_sig = wasm_call_signature(isa, wasm_func_ty, &self.tunables);
385
let array_call_sig = array_call_signature(isa);
386
387
let mut compiler = self.function_compiler();
388
let func = ir::Function::with_name_signature(key_to_name(key), wasm_call_sig);
389
let (mut builder, block0) = compiler.builder(func);
390
391
let args = builder.func.dfg.block_params(block0).to_vec();
392
let callee_vmctx = args[0];
393
let caller_vmctx = args[1];
394
395
// We are exiting Wasm, so save our PC and FP.
396
//
397
// Assert that the caller vmctx really is a core Wasm vmctx, since
398
// that's what we are assuming with our offsets below.
399
self.debug_assert_vmctx_kind(
400
&mut builder,
401
caller_vmctx,
402
wasmtime_environ::VMCONTEXT_MAGIC,
403
);
404
let ptr = isa.pointer_bytes();
405
let vm_store_context = builder.ins().load(
406
pointer_type,
407
MemFlags::trusted(),
408
caller_vmctx,
409
i32::from(ptr.vmcontext_store_context()),
410
);
411
save_last_wasm_exit_fp_and_pc(&mut builder, pointer_type, &ptr, vm_store_context);
412
413
// Spill all wasm arguments to the stack in `ValRaw` slots.
414
let (args_base, args_len) =
415
self.allocate_stack_array_and_spill_args(wasm_func_ty, &mut builder, &args[2..]);
416
let args_len = builder.ins().iconst(pointer_type, i64::from(args_len));
417
418
// Load the actual callee out of the
419
// `VMArrayCallHostFuncContext::host_func`.
420
let ptr_size = isa.pointer_bytes();
421
let callee = builder.ins().load(
422
pointer_type,
423
MemFlags::trusted(),
424
callee_vmctx,
425
ptr_size.vmarray_call_host_func_context_func_ref() + ptr_size.vm_func_ref_array_call(),
426
);
427
428
// Do an indirect call to the callee.
429
let callee_signature = builder.func.import_signature(array_call_sig);
430
let call = self.call_indirect_host(
431
&mut builder,
432
HostCall::ArrayCall,
433
callee_signature,
434
callee,
435
&[callee_vmctx, caller_vmctx, args_base, args_len],
436
);
437
let succeeded = builder.func.dfg.inst_results(call)[0];
438
self.raise_if_host_trapped(&mut builder, caller_vmctx, succeeded);
439
let results =
440
self.load_values_from_array(wasm_func_ty.returns(), &mut builder, args_base, args_len);
441
builder.ins().return_(&results);
442
builder.finalize();
443
444
Ok(CompiledFunctionBody {
445
code: box_dyn_any_compiler_context(Some(compiler.cx)),
446
needs_gc_heap: false,
447
})
448
}
449
450
fn append_code(
451
&self,
452
obj: &mut Object<'static>,
453
funcs: &[(String, FuncKey, Box<dyn Any + Send + Sync>)],
454
resolve_reloc: &dyn Fn(usize, FuncKey) -> usize,
455
) -> Result<Vec<(SymbolId, FunctionLoc)>> {
456
log::trace!(
457
"appending functions to object file: {:#?}",
458
funcs.iter().map(|(sym, _, _)| sym).collect::<Vec<_>>()
459
);
460
461
let mut builder =
462
ModuleTextBuilder::new(obj, self, self.isa.text_section_builder(funcs.len()));
463
if self.linkopts.force_jump_veneers {
464
builder.force_veneers();
465
}
466
let mut addrs = AddressMapSection::default();
467
let mut traps = TrapEncodingBuilder::default();
468
let mut stack_maps = StackMapSection::default();
469
let mut exception_tables = ExceptionTableBuilder::default();
470
let mut frame_tables = FrameTableBuilder::default();
471
472
let funcs = funcs
473
.iter()
474
.map(|(sym, key, func)| {
475
debug_assert!(!func.is::<Option<CompilerContext>>());
476
debug_assert!(func.is::<CompiledFunction>());
477
let func = func.downcast_ref::<CompiledFunction>().unwrap();
478
(sym, *key, func)
479
})
480
.collect::<Vec<_>>();
481
482
let mut frame_descriptors = HashMap::new();
483
if self.tunables.debug_guest {
484
for (_, key, func) in &funcs {
485
frame_descriptors.insert(
486
*key,
487
func.debug_slot_descriptor
488
.as_ref()
489
.map(|builder| builder.serialize())
490
.unwrap_or_else(|| vec![]),
491
);
492
}
493
}
494
495
let mut breakpoint_table = Vec::new();
496
let mut nop_units = None;
497
498
let mut ret = Vec::with_capacity(funcs.len());
499
for (i, (sym, _key, func)) in funcs.iter().enumerate() {
500
let (sym_id, range) = builder.append_func(&sym, func, |idx| resolve_reloc(i, idx));
501
log::trace!("symbol id {sym_id:?} = {sym:?}");
502
503
if self.tunables.generate_address_map {
504
let addr = func.address_map();
505
addrs.push(range.clone(), &addr.instructions);
506
}
507
508
clif_to_env_stack_maps(
509
&mut stack_maps,
510
range.clone(),
511
func.buffer.user_stack_maps(),
512
);
513
514
traps.push(range.clone(), &func.traps().collect::<Vec<_>>());
515
clif_to_env_exception_tables(
516
&mut exception_tables,
517
range.clone(),
518
func.buffer.call_sites(),
519
)?;
520
if self.tunables.debug_guest
521
&& let Some(frame_layout) = func.buffer.frame_layout()
522
{
523
clif_to_env_frame_tables(
524
&mut frame_tables,
525
range.clone(),
526
func.buffer.debug_tags(),
527
frame_layout,
528
&frame_descriptors,
529
)?;
530
}
531
if self.tunables.debug_guest {
532
clif_to_env_breakpoints(
533
range.clone(),
534
func.breakpoint_patches(),
535
&mut breakpoint_table,
536
)?;
537
nop_units.get_or_insert_with(|| func.buffer.nop_units.clone());
538
}
539
builder.append_padding(self.linkopts.padding_between_functions);
540
541
let info = FunctionLoc {
542
start: u32::try_from(range.start).unwrap(),
543
length: u32::try_from(range.end - range.start).unwrap(),
544
};
545
ret.push((sym_id, info));
546
}
547
548
// Sort breakpoints by Wasm PC now. Note that the same Wasm PC
549
// may appear in multiple functions (due to inlining) so it is
550
// only now that we can aggregate all appearances of a PC
551
// together for breakpoint metadata indexed by that PC.
552
breakpoint_table.sort_by_key(|(wasm_pc, _text_range)| *wasm_pc);
553
554
builder.finish(|text| {
555
if !breakpoint_table.is_empty() {
556
let nop_units = nop_units.as_ref().unwrap();
557
let fill_with_nops = |mut slice: &mut [u8]| {
558
while !slice.is_empty() {
559
let nop_unit = nop_units
560
.iter()
561
.rev()
562
.find(|u| u.len() <= slice.len())
563
.expect("no NOP is small enough for remaining slice");
564
let (nop_sized_chunk, rest) = slice.split_at_mut(nop_unit.len());
565
nop_sized_chunk.copy_from_slice(&nop_unit);
566
slice = rest;
567
}
568
};
569
570
for (wasm_pc, text_range) in &breakpoint_table {
571
let start = usize::try_from(text_range.start).unwrap();
572
let end = usize::try_from(text_range.end).unwrap();
573
let text = &mut text[start..end];
574
frame_tables.add_breakpoint_patch(*wasm_pc, text_range.start, text);
575
fill_with_nops(text);
576
}
577
}
578
});
579
580
if self.tunables.generate_address_map {
581
addrs.append_to(obj);
582
}
583
stack_maps.append_to(obj);
584
traps.append_to(obj);
585
586
let exception_section = obj.add_section(
587
obj.segment_name(StandardSegment::Data).to_vec(),
588
ELF_WASMTIME_EXCEPTIONS.as_bytes().to_vec(),
589
SectionKind::ReadOnlyData,
590
);
591
exception_tables.serialize(|bytes| {
592
obj.append_section_data(exception_section, bytes, 1);
593
});
594
595
if self.tunables.debug_guest {
596
let frame_table_section = obj.add_section(
597
obj.segment_name(StandardSegment::Data).to_vec(),
598
ELF_WASMTIME_FRAMES.as_bytes().to_vec(),
599
SectionKind::ReadOnlyData,
600
);
601
frame_tables.serialize(|bytes| {
602
obj.append_section_data(frame_table_section, bytes, 1);
603
});
604
}
605
606
Ok(ret)
607
}
608
609
fn triple(&self) -> &target_lexicon::Triple {
610
self.isa.triple()
611
}
612
613
fn flags(&self) -> Vec<(&'static str, FlagValue<'static>)> {
614
crate::clif_flags_to_wasmtime(self.isa.flags().iter())
615
}
616
617
fn isa_flags(&self) -> Vec<(&'static str, FlagValue<'static>)> {
618
crate::clif_flags_to_wasmtime(self.isa.isa_flags())
619
}
620
621
fn is_branch_protection_enabled(&self) -> bool {
622
self.isa.is_branch_protection_enabled()
623
}
624
625
#[cfg(feature = "component-model")]
626
fn component_compiler(&self) -> &dyn wasmtime_environ::component::ComponentCompiler {
627
self
628
}
629
630
fn append_dwarf<'a>(
631
&self,
632
obj: &mut Object<'_>,
633
translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>,
634
get_func: &'a dyn Fn(
635
StaticModuleIndex,
636
DefinedFuncIndex,
637
) -> (SymbolId, &'a (dyn Any + Send + Sync)),
638
dwarf_package_bytes: Option<&'a [u8]>,
639
tunables: &'a Tunables,
640
) -> Result<()> {
641
log::trace!("appending DWARF debug info");
642
643
let get_func = move |m, f| {
644
let (sym, any) = get_func(m, f);
645
log::trace!("get_func({m:?}, {f:?}) -> ({sym:?}, {any:#p})");
646
debug_assert!(!any.is::<Option<CompilerContext>>());
647
debug_assert!(any.is::<CompiledFunction>());
648
(
649
sym,
650
any.downcast_ref::<CompiledFunction>().unwrap().metadata(),
651
)
652
};
653
654
let mut compilation = crate::debug::Compilation::new(
655
&*self.isa,
656
translations,
657
&get_func,
658
dwarf_package_bytes,
659
tunables,
660
);
661
let dwarf_sections = crate::debug::emit_dwarf(&*self.isa, &mut compilation)
662
.with_context(|| "failed to emit DWARF debug information")?;
663
664
let (debug_bodies, debug_relocs): (Vec<_>, Vec<_>) = dwarf_sections
665
.iter()
666
.map(|s| ((s.name, &s.body), (s.name, &s.relocs)))
667
.unzip();
668
let mut dwarf_sections_ids = HashMap::new();
669
for (name, body) in debug_bodies {
670
let segment = obj.segment_name(StandardSegment::Debug).to_vec();
671
let section_id = obj.add_section(segment, name.as_bytes().to_vec(), SectionKind::Debug);
672
dwarf_sections_ids.insert(name, section_id);
673
obj.append_section_data(section_id, &body, 1);
674
}
675
676
// Write all debug data relocations.
677
for (name, relocs) in debug_relocs {
678
let section_id = *dwarf_sections_ids.get(name).unwrap();
679
for reloc in relocs {
680
let target_symbol = match reloc.target {
681
DwarfSectionRelocTarget::Func(id) => compilation.symbol_id(id),
682
DwarfSectionRelocTarget::Section(name) => {
683
obj.section_symbol(dwarf_sections_ids[name])
684
}
685
};
686
obj.add_relocation(
687
section_id,
688
object::write::Relocation {
689
offset: u64::from(reloc.offset),
690
symbol: target_symbol,
691
addend: i64::from(reloc.addend),
692
flags: RelocationFlags::Generic {
693
size: reloc.size << 3,
694
kind: RelocationKind::Absolute,
695
encoding: RelocationEncoding::Generic,
696
},
697
},
698
)?;
699
}
700
}
701
702
Ok(())
703
}
704
705
fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
706
self.isa.create_systemv_cie()
707
}
708
709
fn compile_wasm_to_builtin(
710
&self,
711
key: FuncKey,
712
symbol: &str,
713
) -> Result<CompiledFunctionBody, CompileError> {
714
log::trace!("compiling wasm-to-builtin trampoline: {key:?} = {symbol:?}");
715
716
let isa = &*self.isa;
717
let ptr_size = isa.pointer_bytes();
718
let pointer_type = isa.pointer_type();
719
let sigs = BuiltinFunctionSignatures::new(self);
720
721
let (builtin_func_index, wasm_sig) = match key {
722
FuncKey::WasmToBuiltinTrampoline(builtin) => (builtin, sigs.wasm_signature(builtin)),
723
FuncKey::PatchableToBuiltinTrampoline(builtin) => {
724
let mut sig = sigs.wasm_signature(builtin);
725
// Patchable functions cannot return anything. We
726
// raise any errors that occur below so this is fine.
727
sig.returns.clear();
728
sig.call_conv = CallConv::PreserveAll;
729
(builtin, sig)
730
}
731
_ => unreachable!(),
732
};
733
let host_sig = sigs.host_signature(builtin_func_index);
734
735
let mut compiler = self.function_compiler();
736
let func = ir::Function::with_name_signature(key_to_name(key), wasm_sig.clone());
737
let (mut builder, block0) = compiler.builder(func);
738
let vmctx = builder.block_params(block0)[0];
739
740
// Debug-assert that this is the right kind of vmctx, and then
741
// additionally perform the "routine of the exit trampoline" of saving
742
// fp/pc/etc.
743
self.debug_assert_vmctx_kind(&mut builder, vmctx, wasmtime_environ::VMCONTEXT_MAGIC);
744
let vm_store_context = builder.ins().load(
745
pointer_type,
746
MemFlags::trusted(),
747
vmctx,
748
ptr_size.vmcontext_store_context(),
749
);
750
save_last_wasm_exit_fp_and_pc(&mut builder, pointer_type, &ptr_size, vm_store_context);
751
752
// Now it's time to delegate to the actual builtin. Forward all our own
753
// arguments to the libcall itself.
754
let args = builder.block_params(block0).to_vec();
755
let call = self.call_builtin(&mut builder, vmctx, &args, builtin_func_index, host_sig);
756
let results = builder.func.dfg.inst_results(call).to_vec();
757
758
// Libcalls do not explicitly jump/raise on traps but instead return a
759
// code indicating whether they trapped or not. This means that it's the
760
// responsibility of the trampoline to check for an trapping return
761
// value and raise a trap as appropriate. With the `results` above check
762
// what `index` is and for each libcall that has a trapping return value
763
// process it here.
764
match builtin_func_index.trap_sentinel() {
765
Some(TrapSentinel::Falsy) => {
766
self.raise_if_host_trapped(&mut builder, vmctx, results[0]);
767
}
768
Some(TrapSentinel::NegativeTwo) => {
769
let ty = builder.func.dfg.value_type(results[0]);
770
let trapped = builder.ins().iconst(ty, -2);
771
let succeeded = builder.ins().icmp(IntCC::NotEqual, results[0], trapped);
772
self.raise_if_host_trapped(&mut builder, vmctx, succeeded);
773
}
774
Some(TrapSentinel::Negative) => {
775
let ty = builder.func.dfg.value_type(results[0]);
776
let zero = builder.ins().iconst(ty, 0);
777
let succeeded =
778
builder
779
.ins()
780
.icmp(IntCC::SignedGreaterThanOrEqual, results[0], zero);
781
self.raise_if_host_trapped(&mut builder, vmctx, succeeded);
782
}
783
Some(TrapSentinel::NegativeOne) => {
784
let ty = builder.func.dfg.value_type(results[0]);
785
let minus_one = builder.ins().iconst(ty, -1);
786
let succeeded = builder.ins().icmp(IntCC::NotEqual, results[0], minus_one);
787
self.raise_if_host_trapped(&mut builder, vmctx, succeeded);
788
}
789
None => {}
790
}
791
792
// And finally, return all the results of this libcall.
793
if !wasm_sig.returns.is_empty() {
794
builder.ins().return_(&results);
795
} else {
796
builder.ins().return_(&[]);
797
}
798
builder.finalize();
799
800
Ok(CompiledFunctionBody {
801
code: box_dyn_any_compiler_context(Some(compiler.cx)),
802
needs_gc_heap: false,
803
})
804
}
805
806
fn compiled_function_relocation_targets<'a>(
807
&'a self,
808
func: &'a dyn Any,
809
) -> Box<dyn Iterator<Item = FuncKey> + 'a> {
810
debug_assert!(!func.is::<Option<CompilerContext>>());
811
debug_assert!(func.is::<CompiledFunction>());
812
let func = func.downcast_ref::<CompiledFunction>().unwrap();
813
Box::new(func.relocations().map(|r| r.reloc_target))
814
}
815
}
816
817
impl InliningCompiler for Compiler {
818
fn calls(&self, func_body: &CompiledFunctionBody, calls: &mut IndexSet<FuncKey>) -> Result<()> {
819
debug_assert!(!func_body.code.is::<CompiledFunction>());
820
debug_assert!(func_body.code.is::<Option<CompilerContext>>());
821
let cx = func_body
822
.code
823
.downcast_ref::<Option<CompilerContext>>()
824
.unwrap()
825
.as_ref()
826
.unwrap();
827
let func = &cx.codegen_context.func;
828
calls.extend(
829
func.params
830
.user_named_funcs()
831
.values()
832
.map(|name| FuncKey::from_raw_parts(name.namespace, name.index))
833
.filter(|key| match key {
834
FuncKey::DefinedWasmFunction(..) => true,
835
#[cfg(feature = "component-model")]
836
FuncKey::UnsafeIntrinsic(..) => true,
837
_ => false,
838
}),
839
);
840
Ok(())
841
}
842
843
fn size(&self, func_body: &CompiledFunctionBody) -> u32 {
844
debug_assert!(!func_body.code.is::<CompiledFunction>());
845
debug_assert!(func_body.code.is::<Option<CompilerContext>>());
846
let cx = func_body
847
.code
848
.downcast_ref::<Option<CompilerContext>>()
849
.unwrap()
850
.as_ref()
851
.unwrap();
852
let func = &cx.codegen_context.func;
853
let size = func.dfg.values().len();
854
u32::try_from(size).unwrap()
855
}
856
857
fn inline<'a>(
858
&self,
859
func_body: &mut CompiledFunctionBody,
860
get_callee: &'a mut dyn FnMut(FuncKey) -> Option<&'a CompiledFunctionBody>,
861
) -> Result<()> {
862
debug_assert!(!func_body.code.is::<CompiledFunction>());
863
debug_assert!(func_body.code.is::<Option<CompilerContext>>());
864
let code = func_body
865
.code
866
.downcast_mut::<Option<CompilerContext>>()
867
.unwrap();
868
let cx = code.as_mut().unwrap();
869
870
cx.codegen_context.inline(Inliner(get_callee))?;
871
return Ok(());
872
873
struct Inliner<'a>(&'a mut dyn FnMut(FuncKey) -> Option<&'a CompiledFunctionBody>);
874
875
impl cranelift_codegen::inline::Inline for Inliner<'_> {
876
fn inline(
877
&mut self,
878
caller: &ir::Function,
879
_call_inst: ir::Inst,
880
_call_opcode: ir::Opcode,
881
callee: ir::FuncRef,
882
_call_args: &[ir::Value],
883
) -> InlineCommand<'_> {
884
let callee = &caller.dfg.ext_funcs[callee].name;
885
let callee = match callee {
886
ir::ExternalName::User(callee) => *callee,
887
ir::ExternalName::TestCase(_)
888
| ir::ExternalName::LibCall(_)
889
| ir::ExternalName::KnownSymbol(_) => return InlineCommand::KeepCall,
890
};
891
let callee = &caller.params.user_named_funcs()[callee];
892
let callee = FuncKey::from_raw_parts(callee.namespace, callee.index);
893
match callee {
894
FuncKey::DefinedWasmFunction(..) => {}
895
#[cfg(feature = "component-model")]
896
FuncKey::UnsafeIntrinsic(..) => {}
897
_ => return InlineCommand::KeepCall,
898
}
899
match (self.0)(callee) {
900
None => InlineCommand::KeepCall,
901
Some(func_body) => {
902
debug_assert!(!func_body.code.is::<CompiledFunction>());
903
debug_assert!(func_body.code.is::<Option<CompilerContext>>());
904
let cx = func_body
905
.code
906
.downcast_ref::<Option<CompilerContext>>()
907
.unwrap();
908
InlineCommand::Inline {
909
callee: Cow::Borrowed(&cx.as_ref().unwrap().codegen_context.func),
910
// We've already visited the callee for inlining
911
// due to our bottom-up approach, no need to
912
// visit it again.
913
visit_callee: false,
914
}
915
}
916
}
917
}
918
}
919
}
920
921
fn finish_compiling(
922
&self,
923
func_body: &mut CompiledFunctionBody,
924
input: Option<wasmparser::FunctionBody<'_>>,
925
symbol: &str,
926
) -> Result<()> {
927
log::trace!("finish compiling {symbol:?}");
928
debug_assert!(!func_body.code.is::<CompiledFunction>());
929
debug_assert!(func_body.code.is::<Option<CompilerContext>>());
930
let cx = func_body
931
.code
932
.downcast_mut::<Option<CompilerContext>>()
933
.unwrap()
934
.take()
935
.unwrap();
936
let compiler = FunctionCompiler { compiler: self, cx };
937
938
let symbol = match compiler.cx.abi {
939
None => Cow::Borrowed(symbol),
940
Some(Abi::Wasm) => Cow::Owned(format!("{symbol}_wasm_call")),
941
Some(Abi::Array) => Cow::Owned(format!("{symbol}_array_call")),
942
Some(Abi::Patchable) => Cow::Owned(format!("{symbol}_patchable_call")),
943
};
944
945
let compiled_func = if let Some(input) = input {
946
compiler.finish_with_info(Some((&input, &self.tunables)), &symbol)?
947
} else {
948
compiler.finish(&symbol)?
949
};
950
951
let timing = cranelift_codegen::timing::take_current();
952
log::debug!("`{symbol}` compiled in {:?}", timing.total());
953
log::trace!("`{symbol}` timing info\n{timing}");
954
955
func_body.code = box_dyn_any_compiled_function(compiled_func);
956
Ok(())
957
}
958
}
959
960
#[cfg(feature = "incremental-cache")]
961
mod incremental_cache {
962
use super::*;
963
964
struct CraneliftCacheStore(Arc<dyn CacheStore>);
965
966
impl cranelift_codegen::incremental_cache::CacheKvStore for CraneliftCacheStore {
967
fn get(&self, key: &[u8]) -> Option<std::borrow::Cow<'_, [u8]>> {
968
self.0.get(key)
969
}
970
fn insert(&mut self, key: &[u8], val: Vec<u8>) {
971
self.0.insert(key, val);
972
}
973
}
974
975
pub(super) fn compile_maybe_cached<'a>(
976
context: &'a mut Context,
977
isa: &dyn TargetIsa,
978
cache_ctx: Option<&mut IncrementalCacheContext>,
979
) -> Result<CompiledCode, CompileError> {
980
let cache_ctx = match cache_ctx {
981
Some(ctx) => ctx,
982
None => return compile_uncached(context, isa),
983
};
984
985
let mut cache_store = CraneliftCacheStore(cache_ctx.cache_store.clone());
986
let (_compiled_code, from_cache) = context
987
.compile_with_cache(isa, &mut cache_store, &mut Default::default())
988
.map_err(|error| CompileError::Codegen(pretty_error(&error.func, error.inner)))?;
989
990
if from_cache {
991
cache_ctx.num_hits += 1;
992
} else {
993
cache_ctx.num_cached += 1;
994
}
995
996
Ok(context.take_compiled_code().unwrap())
997
}
998
}
999
1000
#[cfg(feature = "incremental-cache")]
1001
use incremental_cache::*;
1002
1003
#[cfg(not(feature = "incremental-cache"))]
1004
fn compile_maybe_cached<'a>(
1005
context: &'a mut Context,
1006
isa: &dyn TargetIsa,
1007
_cache_ctx: Option<&mut IncrementalCacheContext>,
1008
) -> Result<CompiledCode, CompileError> {
1009
compile_uncached(context, isa)
1010
}
1011
1012
fn compile_uncached<'a>(
1013
context: &'a mut Context,
1014
isa: &dyn TargetIsa,
1015
) -> Result<CompiledCode, CompileError> {
1016
context
1017
.compile(isa, &mut Default::default())
1018
.map_err(|error| CompileError::Codegen(pretty_error(&error.func, error.inner)))?;
1019
Ok(context.take_compiled_code().unwrap())
1020
}
1021
1022
impl Compiler {
1023
/// This function will allocate a stack slot suitable for storing both the
1024
/// arguments and return values of the function, and then the arguments will
1025
/// all be stored in this block.
1026
///
1027
/// `block0` must be the entry block of the function and `ty` must be the
1028
/// Wasm function type of the trampoline.
1029
///
1030
/// The stack slot pointer is returned in addition to the size, in units of
1031
/// `ValRaw`, of the stack slot.
1032
fn allocate_stack_array_and_spill_args(
1033
&self,
1034
ty: &WasmFuncType,
1035
builder: &mut FunctionBuilder,
1036
args: &[ir::Value],
1037
) -> (Value, u32) {
1038
let isa = &*self.isa;
1039
let pointer_type = isa.pointer_type();
1040
1041
// Compute the size of the values vector.
1042
let value_size = mem::size_of::<u128>();
1043
let values_vec_len = cmp::max(ty.params().len(), ty.returns().len());
1044
let values_vec_byte_size = u32::try_from(value_size * values_vec_len).unwrap();
1045
let values_vec_len = u32::try_from(values_vec_len).unwrap();
1046
1047
let slot = builder.func.create_sized_stack_slot(ir::StackSlotData::new(
1048
ir::StackSlotKind::ExplicitSlot,
1049
values_vec_byte_size,
1050
4,
1051
));
1052
let values_vec_ptr = builder.ins().stack_addr(pointer_type, slot, 0);
1053
1054
{
1055
let values_vec_len = builder
1056
.ins()
1057
.iconst(ir::types::I32, i64::from(values_vec_len));
1058
self.store_values_to_array(builder, ty.params(), args, values_vec_ptr, values_vec_len);
1059
}
1060
1061
(values_vec_ptr, values_vec_len)
1062
}
1063
1064
/// Store values to an array in the array calling convention.
1065
///
1066
/// Used either to store arguments to the array when calling a function
1067
/// using the array calling convention, or used to store results to the
1068
/// array when implementing a function that exposes the array calling
1069
/// convention.
1070
fn store_values_to_array(
1071
&self,
1072
builder: &mut FunctionBuilder,
1073
types: &[WasmValType],
1074
values: &[Value],
1075
values_vec_ptr: Value,
1076
values_vec_capacity: Value,
1077
) {
1078
debug_assert_eq!(types.len(), values.len());
1079
self.debug_assert_enough_capacity_for_length(builder, types.len(), values_vec_capacity);
1080
1081
// Note that loads and stores are unconditionally done in the
1082
// little-endian format rather than the host's native-endianness,
1083
// despite this load/store being unrelated to execution in Wasm itself.
1084
// For more details on this see the `ValRaw` type in
1085
// `wasmtime::runtime::vm`.
1086
let flags = ir::MemFlags::new()
1087
.with_notrap()
1088
.with_endianness(ir::Endianness::Little);
1089
1090
let value_size = mem::size_of::<u128>();
1091
for (i, val) in values.iter().copied().enumerate() {
1092
crate::unbarriered_store_type_at_offset(
1093
&mut builder.cursor(),
1094
flags,
1095
values_vec_ptr,
1096
i32::try_from(i * value_size).unwrap(),
1097
val,
1098
);
1099
}
1100
}
1101
1102
/// Used for loading the values of an array-call host function's value
1103
/// array.
1104
///
1105
/// This can be used to load arguments out of the array if the trampoline we
1106
/// are building exposes the array calling convention, or it can be used to
1107
/// load results out of the array if the trampoline we are building calls a
1108
/// function that uses the array calling convention.
1109
fn load_values_from_array(
1110
&self,
1111
types: &[WasmValType],
1112
builder: &mut FunctionBuilder,
1113
values_vec_ptr: Value,
1114
values_vec_capacity: Value,
1115
) -> Vec<ir::Value> {
1116
let isa = &*self.isa;
1117
let value_size = mem::size_of::<u128>();
1118
1119
self.debug_assert_enough_capacity_for_length(builder, types.len(), values_vec_capacity);
1120
1121
// Note that this is little-endian like `store_values_to_array` above,
1122
// see notes there for more information.
1123
let flags = MemFlags::new()
1124
.with_notrap()
1125
.with_endianness(ir::Endianness::Little);
1126
1127
let mut results = Vec::new();
1128
for (i, ty) in types.iter().enumerate() {
1129
results.push(crate::unbarriered_load_type_at_offset(
1130
isa,
1131
&mut builder.cursor(),
1132
*ty,
1133
flags,
1134
values_vec_ptr,
1135
i32::try_from(i * value_size).unwrap(),
1136
));
1137
}
1138
results
1139
}
1140
1141
fn function_compiler(&self) -> FunctionCompiler<'_> {
1142
let saved_context = self.contexts.lock().unwrap().pop();
1143
FunctionCompiler {
1144
compiler: self,
1145
cx: saved_context
1146
.map(|mut ctx| {
1147
ctx.codegen_context.clear();
1148
ctx
1149
})
1150
.unwrap_or_else(|| CompilerContext {
1151
#[cfg(feature = "incremental-cache")]
1152
incremental_cache_ctx: self.cache_store.as_ref().map(|cache_store| {
1153
IncrementalCacheContext {
1154
cache_store: cache_store.clone(),
1155
num_hits: 0,
1156
num_cached: 0,
1157
}
1158
}),
1159
..Default::default()
1160
}),
1161
}
1162
}
1163
1164
/// Invokes the `raise` libcall in `vmctx` if the `succeeded` value
1165
/// indicates if a trap happened.
1166
///
1167
/// This helper is used when the host returns back to WebAssembly. The host
1168
/// returns a `bool` indicating whether the call succeeded. If the call
1169
/// failed then Cranelift needs to unwind back to the original invocation
1170
/// point. The unwind right now is then implemented in Wasmtime with an
1171
/// exceptional resume, one day this might be implemented differently with
1172
/// an unwind inside of Cranelift.
1173
///
1174
/// Additionally in the future for pulley this will emit a special trap
1175
/// opcode for Pulley itself to cease interpretation and exit the
1176
/// interpreter.
1177
pub fn raise_if_host_trapped(
1178
&self,
1179
builder: &mut FunctionBuilder<'_>,
1180
vmctx: ir::Value,
1181
succeeded: ir::Value,
1182
) {
1183
let trapped_block = builder.create_block();
1184
let continuation_block = builder.create_block();
1185
builder.set_cold_block(trapped_block);
1186
builder
1187
.ins()
1188
.brif(succeeded, continuation_block, &[], trapped_block, &[]);
1189
1190
builder.seal_block(trapped_block);
1191
builder.seal_block(continuation_block);
1192
1193
builder.switch_to_block(trapped_block);
1194
let sigs = BuiltinFunctionSignatures::new(self);
1195
let sig = sigs.host_signature(BuiltinFunctionIndex::raise());
1196
self.call_builtin(builder, vmctx, &[vmctx], BuiltinFunctionIndex::raise(), sig);
1197
builder.ins().trap(TRAP_INTERNAL_ASSERT);
1198
1199
builder.switch_to_block(continuation_block);
1200
}
1201
1202
/// Helper to load the core `builtin` from `vmctx` and invoke it with
1203
/// `args`.
1204
fn call_builtin(
1205
&self,
1206
builder: &mut FunctionBuilder<'_>,
1207
vmctx: ir::Value,
1208
args: &[ir::Value],
1209
builtin: BuiltinFunctionIndex,
1210
sig: ir::Signature,
1211
) -> ir::Inst {
1212
let isa = &*self.isa;
1213
let ptr_size = isa.pointer_bytes();
1214
let pointer_type = isa.pointer_type();
1215
1216
// Builtins are stored in an array in all `VMContext`s. First load the
1217
// base pointer of the array and then load the entry of the array that
1218
// corresponds to this builtin.
1219
let mem_flags = ir::MemFlags::trusted().with_readonly();
1220
let array_addr = builder.ins().load(
1221
pointer_type,
1222
mem_flags,
1223
vmctx,
1224
i32::from(ptr_size.vmcontext_builtin_functions()),
1225
);
1226
let body_offset = i32::try_from(builtin.index() * pointer_type.bytes()).unwrap();
1227
let func_addr = builder
1228
.ins()
1229
.load(pointer_type, mem_flags, array_addr, body_offset);
1230
1231
let sig = builder.func.import_signature(sig);
1232
self.call_indirect_host(builder, builtin, sig, func_addr, args)
1233
}
1234
1235
pub fn isa(&self) -> &dyn TargetIsa {
1236
&*self.isa
1237
}
1238
1239
pub fn tunables(&self) -> &Tunables {
1240
&self.tunables
1241
}
1242
1243
fn debug_assert_enough_capacity_for_length(
1244
&self,
1245
builder: &mut FunctionBuilder,
1246
length: usize,
1247
capacity: ir::Value,
1248
) {
1249
if !self.emit_debug_checks {
1250
return;
1251
}
1252
let enough_capacity = builder.ins().icmp_imm(
1253
ir::condcodes::IntCC::UnsignedGreaterThanOrEqual,
1254
capacity,
1255
ir::immediates::Imm64::new(length.try_into().unwrap()),
1256
);
1257
builder.ins().trapz(enough_capacity, TRAP_INTERNAL_ASSERT);
1258
}
1259
1260
fn debug_assert_vmctx_kind(
1261
&self,
1262
builder: &mut FunctionBuilder,
1263
vmctx: ir::Value,
1264
expected_vmctx_magic: u32,
1265
) {
1266
if !self.emit_debug_checks {
1267
return;
1268
}
1269
let magic = builder.ins().load(
1270
ir::types::I32,
1271
MemFlags::trusted().with_endianness(self.isa.endianness()),
1272
vmctx,
1273
0,
1274
);
1275
let is_expected_vmctx = builder.ins().icmp_imm(
1276
ir::condcodes::IntCC::Equal,
1277
magic,
1278
i64::from(expected_vmctx_magic),
1279
);
1280
builder.ins().trapz(is_expected_vmctx, TRAP_INTERNAL_ASSERT);
1281
}
1282
1283
fn array_to_wasm_trampoline(
1284
&self,
1285
trampoline_key: FuncKey,
1286
callee_key: FuncKey,
1287
callee_sig: &WasmFuncType,
1288
symbol: &str,
1289
vm_store_context_offset: u32,
1290
expected_vmctx_magic: u32,
1291
) -> Result<CompiledFunctionBody, CompileError> {
1292
log::trace!("compiling array-to-wasm trampoline: {trampoline_key:?} = {symbol:?}");
1293
1294
let isa = &*self.isa;
1295
let pointer_type = isa.pointer_type();
1296
let wasm_call_sig = wasm_call_signature(isa, callee_sig, &self.tunables);
1297
let array_call_sig = array_call_signature(isa);
1298
1299
let mut compiler = self.function_compiler();
1300
let func = ir::Function::with_name_signature(key_to_name(trampoline_key), array_call_sig);
1301
let (mut builder, block0) = compiler.builder(func);
1302
1303
let try_call_block = builder.create_block();
1304
builder.ins().jump(try_call_block, []);
1305
builder.switch_to_block(try_call_block);
1306
1307
let (vmctx, caller_vmctx, values_vec_ptr, values_vec_len) = {
1308
let params = builder.func.dfg.block_params(block0);
1309
(params[0], params[1], params[2], params[3])
1310
};
1311
1312
// First load the actual arguments out of the array.
1313
let mut args = self.load_values_from_array(
1314
callee_sig.params(),
1315
&mut builder,
1316
values_vec_ptr,
1317
values_vec_len,
1318
);
1319
args.insert(0, caller_vmctx);
1320
args.insert(0, vmctx);
1321
1322
// Just before we enter Wasm, save our context information.
1323
//
1324
// Assert that we were really given a core Wasm vmctx, since that's
1325
// what we are assuming with our offsets below.
1326
self.debug_assert_vmctx_kind(&mut builder, vmctx, expected_vmctx_magic);
1327
save_last_wasm_entry_context(
1328
&mut builder,
1329
pointer_type,
1330
&self.isa.pointer_bytes(),
1331
vm_store_context_offset,
1332
vmctx,
1333
try_call_block,
1334
);
1335
1336
// Create the invocation of wasm, which is notably done with a
1337
// `try_call` with an exception handler that's used to handle traps.
1338
let normal_return = builder.create_block();
1339
let exceptional_return = builder.create_block();
1340
let normal_return_values = wasm_call_sig
1341
.returns
1342
.iter()
1343
.map(|ty| {
1344
builder
1345
.func
1346
.dfg
1347
.append_block_param(normal_return, ty.value_type)
1348
})
1349
.collect::<Vec<_>>();
1350
1351
// Then call the Wasm function with those arguments.
1352
let signature = builder.func.import_signature(wasm_call_sig.clone());
1353
let callee = {
1354
let (namespace, index) = callee_key.into_raw_parts();
1355
let name = ir::ExternalName::User(
1356
builder
1357
.func
1358
.declare_imported_user_function(ir::UserExternalName { namespace, index }),
1359
);
1360
builder.func.dfg.ext_funcs.push(ir::ExtFuncData {
1361
name,
1362
signature,
1363
colocated: true,
1364
patchable: false,
1365
})
1366
};
1367
1368
let dfg = &mut builder.func.dfg;
1369
let exception_table = dfg.exception_tables.push(ir::ExceptionTableData::new(
1370
signature,
1371
ir::BlockCall::new(
1372
normal_return,
1373
(0..wasm_call_sig.returns.len())
1374
.map(|i| ir::BlockArg::TryCallRet(i.try_into().unwrap())),
1375
&mut dfg.value_lists,
1376
),
1377
[ir::ExceptionTableItem::Default(ir::BlockCall::new(
1378
exceptional_return,
1379
None,
1380
&mut dfg.value_lists,
1381
))],
1382
));
1383
builder.ins().try_call(callee, &args, exception_table);
1384
1385
builder.seal_block(try_call_block);
1386
builder.seal_block(normal_return);
1387
builder.seal_block(exceptional_return);
1388
1389
// On the normal return path store all the results in the array we were
1390
// provided and return "true" for "returned successfully".
1391
builder.switch_to_block(normal_return);
1392
self.store_values_to_array(
1393
&mut builder,
1394
callee_sig.returns(),
1395
&normal_return_values,
1396
values_vec_ptr,
1397
values_vec_len,
1398
);
1399
let true_return = builder.ins().iconst(ir::types::I8, 1);
1400
builder.ins().return_(&[true_return]);
1401
1402
// On the exceptional return path just return "false" for "did not
1403
// succeed". Note that register restoration is part of the `try_call`
1404
// and handler implementation.
1405
builder.switch_to_block(exceptional_return);
1406
let false_return = builder.ins().iconst(ir::types::I8, 0);
1407
builder.ins().return_(&[false_return]);
1408
1409
builder.finalize();
1410
1411
Ok(CompiledFunctionBody {
1412
code: box_dyn_any_compiler_context(Some(compiler.cx)),
1413
needs_gc_heap: false,
1414
})
1415
}
1416
}
1417
1418
struct FunctionCompiler<'a> {
1419
compiler: &'a Compiler,
1420
cx: CompilerContext,
1421
}
1422
1423
impl FunctionCompiler<'_> {
1424
fn builder(&mut self, func: ir::Function) -> (FunctionBuilder<'_>, ir::Block) {
1425
self.cx.codegen_context.func = func;
1426
let mut builder = FunctionBuilder::new(
1427
&mut self.cx.codegen_context.func,
1428
self.cx.func_translator.context(),
1429
);
1430
1431
let block0 = builder.create_block();
1432
builder.append_block_params_for_function_params(block0);
1433
builder.switch_to_block(block0);
1434
builder.ensure_inserted_block();
1435
builder.seal_block(block0);
1436
(builder, block0)
1437
}
1438
1439
fn finish(self, symbol: &str) -> Result<CompiledFunction, CompileError> {
1440
self.finish_with_info(None, symbol)
1441
}
1442
1443
fn finish_with_info(
1444
mut self,
1445
body_and_tunables: Option<(&FunctionBody<'_>, &Tunables)>,
1446
symbol: &str,
1447
) -> Result<CompiledFunction, CompileError> {
1448
let context = &mut self.cx.codegen_context;
1449
let isa = &*self.compiler.isa;
1450
1451
// Run compilation, but don't propagate the error just yet. This'll
1452
// mutate `context` and the IR contained within (optionally) but it may
1453
// fail if the backend has a bug in it. Use `context` after this
1454
// finishes to optionally emit CLIF and then after that's done actually
1455
// propagate the error if one happened.
1456
let compilation_result =
1457
compile_maybe_cached(context, isa, self.cx.incremental_cache_ctx.as_mut());
1458
1459
if let Some(path) = &self.compiler.clif_dir {
1460
use std::io::Write;
1461
1462
let mut path = path.join(symbol.replace(":", "-"));
1463
path.set_extension("clif");
1464
1465
let mut output = std::fs::File::create(path).unwrap();
1466
write!(
1467
output,
1468
";; Intermediate Representation of function <{symbol}>:\n",
1469
)
1470
.unwrap();
1471
write!(output, "{}", context.func.display()).unwrap();
1472
}
1473
1474
let compiled_code = compilation_result?;
1475
1476
// Give wasm functions, user defined code, a "preferred" alignment
1477
// instead of the minimum alignment as this can help perf in niche
1478
// situations.
1479
let preferred_alignment = if body_and_tunables.is_some() {
1480
self.compiler.isa.function_alignment().preferred
1481
} else {
1482
1
1483
};
1484
1485
let alignment = compiled_code.buffer.alignment.max(preferred_alignment);
1486
let mut compiled_function = CompiledFunction::new(
1487
compiled_code.buffer.clone(),
1488
context.func.params.user_named_funcs().clone(),
1489
alignment,
1490
);
1491
1492
if let Some((body, tunables)) = body_and_tunables {
1493
let data = body.get_binary_reader();
1494
let offset = data.original_position();
1495
let len = data.bytes_remaining();
1496
compiled_function.set_address_map(
1497
offset.try_into().unwrap(),
1498
len.try_into().unwrap(),
1499
tunables.generate_address_map,
1500
);
1501
}
1502
1503
if isa.flags().unwind_info() {
1504
let unwind = compiled_code
1505
.create_unwind_info(isa)
1506
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;
1507
1508
if let Some(unwind_info) = unwind {
1509
compiled_function.set_unwind_info(unwind_info);
1510
}
1511
}
1512
1513
if let Some(builder) = self.cx.debug_slot_descriptor.take() {
1514
compiled_function.debug_slot_descriptor = Some(builder);
1515
}
1516
1517
if body_and_tunables
1518
.map(|(_, t)| t.debug_native)
1519
.unwrap_or(false)
1520
{
1521
compiled_function.set_value_labels_ranges(compiled_code.value_labels_ranges.clone());
1522
1523
// DWARF debugging needs the CFA-based unwind information even on Windows.
1524
if !matches!(
1525
compiled_function.metadata().unwind_info,
1526
Some(UnwindInfo::SystemV(_))
1527
) {
1528
let cfa_unwind = compiled_code
1529
.create_unwind_info_of_kind(isa, UnwindInfoKind::SystemV)
1530
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;
1531
1532
if let Some(UnwindInfo::SystemV(cfa_unwind_info)) = cfa_unwind {
1533
compiled_function.set_cfa_unwind_info(cfa_unwind_info);
1534
}
1535
}
1536
}
1537
1538
self.compiler.contexts.lock().unwrap().push(self.cx);
1539
1540
Ok(compiled_function)
1541
}
1542
}
1543
1544
/// Convert from Cranelift's representation of a stack map to Wasmtime's
1545
/// compiler-agnostic representation.
1546
///
1547
/// Here `section` is the wasmtime data section being created and `range` is the
1548
/// range of the function being added. The `clif_stack_maps` entry is the raw
1549
/// listing of stack maps from Cranelift.
1550
fn clif_to_env_stack_maps(
1551
section: &mut StackMapSection,
1552
range: Range<u64>,
1553
clif_stack_maps: &[(CodeOffset, u32, ir::UserStackMap)],
1554
) {
1555
for (offset, frame_size, stack_map) in clif_stack_maps {
1556
let mut frame_offsets = Vec::new();
1557
for (ty, frame_offset) in stack_map.entries() {
1558
assert_eq!(ty, ir::types::I32);
1559
frame_offsets.push(frame_offset);
1560
}
1561
let code_offset = range.start + u64::from(*offset);
1562
assert!(code_offset < range.end);
1563
section.push(code_offset, *frame_size, frame_offsets.into_iter());
1564
}
1565
}
1566
1567
/// Convert from Cranelift's representation of exception handler
1568
/// metadata to Wasmtime's compiler-agnostic representation.
1569
///
1570
/// Here `builder` is the wasmtime-unwinder exception section being
1571
/// created and `range` is the range of the function being added. The
1572
/// `call_sites` iterator is the raw iterator over callsite metadata
1573
/// (including exception handlers) from Cranelift.
1574
fn clif_to_env_exception_tables<'a>(
1575
builder: &mut ExceptionTableBuilder,
1576
range: Range<u64>,
1577
call_sites: impl Iterator<Item = FinalizedMachCallSite<'a>>,
1578
) -> Result<()> {
1579
builder.add_func(CodeOffset::try_from(range.start).unwrap(), call_sites)
1580
}
1581
1582
/// Convert from Cranelift's representation of frame state slots and
1583
/// debug tags to Wasmtime's serialized metadata.
1584
fn clif_to_env_frame_tables<'a>(
1585
builder: &mut FrameTableBuilder,
1586
range: Range<u64>,
1587
tag_sites: impl Iterator<Item = MachBufferDebugTagList<'a>>,
1588
frame_layout: &MachBufferFrameLayout,
1589
frame_descriptors: &HashMap<FuncKey, Vec<u8>>,
1590
) -> Result<()> {
1591
let mut frame_descriptor_indices = HashMap::new();
1592
for tag_site in tag_sites {
1593
// Split into frames; each has three debug tags.
1594
let mut frames = vec![];
1595
for frame_tags in tag_site.tags.chunks_exact(3) {
1596
let &[
1597
ir::DebugTag::StackSlot(slot),
1598
ir::DebugTag::User(wasm_pc),
1599
ir::DebugTag::User(stack_shape),
1600
] = frame_tags
1601
else {
1602
panic!("Invalid tags");
1603
};
1604
1605
let func_key = frame_layout.stackslots[slot]
1606
.key
1607
.expect("Key must be present on stackslot used as state slot")
1608
.bits();
1609
let func_key = FuncKey::from_raw_u64(func_key);
1610
let frame_descriptor = *frame_descriptor_indices.entry(slot).or_insert_with(|| {
1611
let slot_to_fp_offset =
1612
frame_layout.frame_to_fp_offset - frame_layout.stackslots[slot].offset;
1613
let descriptor = frame_descriptors
1614
.get(&func_key)
1615
.expect("frame descriptor not present for FuncKey");
1616
builder.add_frame_descriptor(slot_to_fp_offset, &descriptor)
1617
});
1618
1619
frames.push((
1620
wasm_pc,
1621
frame_descriptor,
1622
FrameStackShape::from_raw(stack_shape),
1623
));
1624
}
1625
1626
let native_pc_in_code_section = u32::try_from(range.start)
1627
.unwrap()
1628
.checked_add(tag_site.offset)
1629
.unwrap();
1630
let pos = match tag_site.pos {
1631
MachDebugTagPos::Post => FrameInstPos::Post,
1632
MachDebugTagPos::Pre => FrameInstPos::Pre,
1633
};
1634
builder.add_program_point(native_pc_in_code_section, pos, &frames);
1635
}
1636
1637
Ok(())
1638
}
1639
1640
/// Convert from Cranelift's representation of breakpoint patches to
1641
/// Wasmtime's serialized metadata.
1642
fn clif_to_env_breakpoints(
1643
range: Range<u64>,
1644
breakpoint_patches: impl Iterator<Item = (u32, Range<u32>)>,
1645
patch_table: &mut Vec<(u32, Range<u32>)>,
1646
) -> Result<()> {
1647
patch_table.extend(breakpoint_patches.map(|(wasm_pc, offset_range)| {
1648
let start = offset_range.start + u32::try_from(range.start).unwrap();
1649
let end = offset_range.end + u32::try_from(range.start).unwrap();
1650
(wasm_pc, start..end)
1651
}));
1652
Ok(())
1653
}
1654
1655
fn save_last_wasm_entry_context(
1656
builder: &mut FunctionBuilder,
1657
pointer_type: ir::Type,
1658
ptr_size: &dyn PtrSize,
1659
vm_store_context_offset: u32,
1660
vmctx: Value,
1661
block: ir::Block,
1662
) {
1663
// First we need to get the `VMStoreContext`.
1664
let vm_store_context = builder.ins().load(
1665
pointer_type,
1666
MemFlags::trusted(),
1667
vmctx,
1668
i32::try_from(vm_store_context_offset).unwrap(),
1669
);
1670
1671
// Save the current fp/sp of the entry trampoline into the `VMStoreContext`.
1672
let fp = builder.ins().get_frame_pointer(pointer_type);
1673
builder.ins().store(
1674
MemFlags::trusted(),
1675
fp,
1676
vm_store_context,
1677
ptr_size.vmstore_context_last_wasm_entry_fp(),
1678
);
1679
let sp = builder.ins().get_stack_pointer(pointer_type);
1680
builder.ins().store(
1681
MemFlags::trusted(),
1682
sp,
1683
vm_store_context,
1684
ptr_size.vmstore_context_last_wasm_entry_sp(),
1685
);
1686
1687
// Also save the address of this function's exception handler. This is used
1688
// as a resumption point for traps, for example.
1689
let trap_handler = builder
1690
.ins()
1691
.get_exception_handler_address(pointer_type, block, 0);
1692
builder.ins().store(
1693
MemFlags::trusted(),
1694
trap_handler,
1695
vm_store_context,
1696
ptr_size.vmstore_context_last_wasm_entry_trap_handler(),
1697
);
1698
}
1699
1700
fn save_last_wasm_exit_fp_and_pc(
1701
builder: &mut FunctionBuilder,
1702
pointer_type: ir::Type,
1703
ptr: &impl PtrSize,
1704
limits: Value,
1705
) {
1706
// Save the trampoline FP to the limits. Exception unwind needs
1707
// this so that it can know the SP (bottom of frame) for the very
1708
// last Wasm frame.
1709
let trampoline_fp = builder.ins().get_frame_pointer(pointer_type);
1710
builder.ins().store(
1711
MemFlags::trusted(),
1712
trampoline_fp,
1713
limits,
1714
ptr.vmstore_context_last_wasm_exit_trampoline_fp(),
1715
);
1716
1717
// Finally save the Wasm return address to the limits.
1718
let wasm_pc = builder.ins().get_return_address(pointer_type);
1719
builder.ins().store(
1720
MemFlags::trusted(),
1721
wasm_pc,
1722
limits,
1723
ptr.vmstore_context_last_wasm_exit_pc(),
1724
);
1725
}
1726
1727
fn key_to_name(key: FuncKey) -> ir::UserFuncName {
1728
let (namespace, index) = key.into_raw_parts();
1729
ir::UserFuncName::User(ir::UserExternalName { namespace, index })
1730
}
1731
1732