Skip to content

Commit 13e222b

Browse files
committed
Stow epoch-check code locations in a custom section of the native binary.
This will let the signal handler distinguish epoch interrupts from other segfaults. TODO: Tests
1 parent 1f50af1 commit 13e222b

6 files changed

Lines changed: 130 additions & 6 deletions

File tree

cranelift/codegen/src/isa/x64/inst/emit.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -649,8 +649,7 @@ pub(crate) fn emit(
649649
// The ISLE has already emitted the dead load. Put the address of
650650
// this instruction aside so we can later distinguish whether a
651651
// segfault is its fault.
652-
653-
// Search for "let pc_offset = layout.ip_offset as i32;" as a string to pull on.
652+
sink.add_epoch_check();
654653
}
655654

656655
Inst::JmpKnown { dst } => uncond_jmp(sink, *dst),

cranelift/codegen/src/machinst/buffer.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ pub struct MachBuffer<I: VCodeInst> {
252252
call_sites: SmallVec<[MachCallSite; 16]>,
253253
/// Any patchable call site locations.
254254
patchable_call_sites: SmallVec<[MachPatchableCallSite; 16]>,
255+
/// Any locations which do an MMU-based check for the end of an epoch.
256+
epoch_checks: SmallVec<[EpochCheckOffset; 16]>,
255257
/// Any exception-handler records referred to at call sites.
256258
exception_handlers: SmallVec<[MachExceptionHandler; 16]>,
257259
/// Any source location mappings referring to this code.
@@ -343,6 +345,7 @@ impl MachBufferFinalized<Stencil> {
343345
traps: self.traps,
344346
call_sites: self.call_sites,
345347
patchable_call_sites: self.patchable_call_sites,
348+
epoch_checks: self.epoch_checks,
346349
exception_handlers: self.exception_handlers,
347350
srclocs: self
348351
.srclocs
@@ -380,6 +383,8 @@ pub struct MachBufferFinalized<T: CompilePhase> {
380383
pub(crate) call_sites: SmallVec<[MachCallSite; 16]>,
381384
/// Any patchable call site locations refering to this code.
382385
pub(crate) patchable_call_sites: SmallVec<[MachPatchableCallSite; 16]>,
386+
/// Any locations which do an MMU-based check for the end of an epoch.
387+
pub epoch_checks: SmallVec<[EpochCheckOffset; 16]>,
383388
/// Any exception-handler records referred to at call sites.
384389
pub(crate) exception_handlers: SmallVec<[FinalizedMachExceptionHandler; 16]>,
385390
/// Any source location mappings referring to this code.
@@ -480,6 +485,7 @@ impl<I: VCodeInst> MachBuffer<I> {
480485
traps: SmallVec::new(),
481486
call_sites: SmallVec::new(),
482487
patchable_call_sites: SmallVec::new(),
488+
epoch_checks: SmallVec::new(),
483489
exception_handlers: SmallVec::new(),
484490
srclocs: SmallVec::new(),
485491
debug_tags: vec![],
@@ -1581,6 +1587,7 @@ impl<I: VCodeInst> MachBuffer<I> {
15811587
traps: self.traps,
15821588
call_sites: self.call_sites,
15831589
patchable_call_sites: self.patchable_call_sites,
1590+
epoch_checks: self.epoch_checks,
15841591
exception_handlers: finalized_exception_handlers,
15851592
srclocs,
15861593
debug_tags: self.debug_tags,
@@ -1696,6 +1703,14 @@ impl<I: VCodeInst> MachBuffer<I> {
16961703
});
16971704
}
16981705

1706+
/// Record that an MMU-based epoch interruption check occurs at the current
1707+
/// offset. The signal handler uses these annotations to distinguish that a
1708+
/// segfault is actually an epoch interruption in disguise. The
1709+
/// DeadLoadWithContext instruction is assumed to have already been emitted.
1710+
pub fn add_epoch_check(&mut self) {
1711+
self.epoch_checks.push(self.cur_offset());
1712+
}
1713+
16991714
/// Add an unwind record at the current offset.
17001715
pub fn add_unwind(&mut self, unwind: UnwindInst) {
17011716
self.unwind_info.push((self.cur_offset(), unwind));
@@ -2198,6 +2213,12 @@ pub struct MachPatchableCallSite {
21982213
pub len: u32,
21992214
}
22002215

2216+
/// The location of an epoch-end check, when using MMU-based epoch interruption.
2217+
///
2218+
/// Specifically, this points to the instruction after the one that does the
2219+
/// epoch-end check: the one at which to resume execution.
2220+
pub type EpochCheckOffset = CodeOffset;
2221+
22012222
/// A source-location mapping resulting from a compilation.
22022223
#[derive(PartialEq, Debug, Clone)]
22032224
#[cfg_attr(

crates/cranelift/src/compiler.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ use wasmparser::{FuncValidatorAllocations, FunctionBody};
3535
use wasmtime_environ::obj::{ELF_WASMTIME_EXCEPTIONS, ELF_WASMTIME_FRAMES};
3636
use wasmtime_environ::{
3737
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,
38+
DefinedFuncIndex, EpochCheckSection, FlagValue, FrameInstPos, FrameStackShape,
39+
FrameStateSlotBuilder, FrameTableBuilder, FuncKey, FunctionBodyData, FunctionLoc, HostCall,
40+
InliningCompiler, ModuleTranslation, ModuleTypesBuilder, PtrSize, StackMapSection,
41+
StaticModuleIndex, TrapEncodingBuilder, TrapSentinel, TripleExt, Tunables, WasmFuncType,
42+
WasmValType,
4243
};
4344
use wasmtime_unwinder::ExceptionTableBuilder;
4445

@@ -468,6 +469,7 @@ impl wasmtime_environ::Compiler for Compiler {
468469
let mut stack_maps = StackMapSection::default();
469470
let mut exception_tables = ExceptionTableBuilder::default();
470471
let mut frame_tables = FrameTableBuilder::default();
472+
let mut epoch_checks = EpochCheckSection::default();
471473

472474
let funcs = funcs
473475
.iter()
@@ -536,6 +538,9 @@ impl wasmtime_environ::Compiler for Compiler {
536538
)?;
537539
nop_units.get_or_insert_with(|| func.buffer.nop_units.clone());
538540
}
541+
if self.tunables.epoch_interruption_via_mmu {
542+
epoch_checks.push(range.clone(), &func.buffer.epoch_checks);
543+
}
539544
builder.append_padding(self.linkopts.padding_between_functions);
540545

541546
let info = FunctionLoc {
@@ -582,6 +587,9 @@ impl wasmtime_environ::Compiler for Compiler {
582587
}
583588
stack_maps.append_to(obj);
584589
traps.append_to(obj);
590+
if self.tunables.epoch_interruption_via_mmu {
591+
epoch_checks.append_to(obj);
592+
}
585593

586594
let exception_section = obj.add_section(
587595
obj.segment_name(StandardSegment::Data).to_vec(),
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//! Emission of compiled-artifact metadata describing where epoch-end checks
2+
//! occur in the code when using MMU-based epoch interruption. This lets the
3+
//! signal handler distinguish epoch interruptions from general segfaults.
4+
5+
use crate::obj::ELF_WASMTIME_EPOCH_CHECKS;
6+
use crate::prelude::*;
7+
use object::write::{Object, StandardSegment};
8+
use object::{LittleEndian, SectionKind, U32Bytes};
9+
use std::ops::Range;
10+
11+
/// Offset of an epoch check within its function, in bytes. Specifically, this
12+
/// points to The instruction after the one that does the epoch-end check: the
13+
/// one at which to resume execution.
14+
///
15+
/// This is parallel to cranelift's CodeOffset and exists to (1) avoid making it
16+
/// a dependency and (2) to pin it down to <= 32 bits, since the format of the
17+
/// custom-section we're emitting depends on that, (3) hold documentation.
18+
type EpochCheckOffset = u32;
19+
20+
/// A builder and emitter of the custom section which houses MMU-based
21+
/// epoch-end-check locations in a native binary.
22+
#[derive(Default)]
23+
pub struct EpochCheckSection {
24+
/// Offset of the instruction to resume at after the epoch end and task switch
25+
return_offsets: Vec<U32Bytes<LittleEndian>>,
26+
/// The largest (and most recent, because we accept them only in order)
27+
/// offset received so far for enforcing ordering. This is relative to the
28+
/// start of the code section so we can make sure functions are ordered too.
29+
last_offset: u32,
30+
}
31+
32+
impl EpochCheckSection {
33+
/// Adds an epoch-check location to the section.
34+
///
35+
/// Calls to this must be ordered by the location of `func`, and
36+
/// `check_offsets` must be ordered (within each function) as well.
37+
pub fn push(&mut self, func: Range<u64>, check_offsets: &[EpochCheckOffset]) {
38+
// Check that functions have been pushed in order so our section is
39+
// sorted for free.
40+
let func_start = u32::try_from(func.start).unwrap();
41+
let func_end = u32::try_from(func.end).unwrap();
42+
assert!(func_start >= self.last_offset);
43+
44+
// Remember each offset, ensuring they are in order.
45+
for offset in check_offsets {
46+
let text_section_relative = func_start + offset;
47+
assert!(text_section_relative > self.last_offset);
48+
self.return_offsets
49+
.push(U32Bytes::new(LittleEndian, text_section_relative));
50+
self.last_offset = text_section_relative;
51+
}
52+
self.last_offset = func_end;
53+
}
54+
55+
/// Encodes this section into an object.
56+
pub fn append_to(self, obj: &mut Object) {
57+
let section = obj.add_section(
58+
obj.segment_name(StandardSegment::Data).to_vec(),
59+
ELF_WASMTIME_EPOCH_CHECKS.as_bytes().to_vec(),
60+
SectionKind::ReadOnlyData,
61+
);
62+
63+
// Append length.
64+
obj.append_section_data(
65+
section,
66+
&u32::try_from(self.return_offsets.len())
67+
.unwrap()
68+
.to_le_bytes(),
69+
1,
70+
);
71+
72+
// Append offsets.
73+
obj.append_section_data(section, object::bytes_of_slice(&self.return_offsets), 1);
74+
}
75+
}

crates/environ/src/compile/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::path;
1616
use std::sync::Arc;
1717

1818
mod address_map;
19+
mod epoch_checks;
1920
mod frame_table;
2021
mod module_artifacts;
2122
mod module_environ;
@@ -24,6 +25,7 @@ mod stack_maps;
2425
mod trap_encoding;
2526

2627
pub use self::address_map::*;
28+
pub use self::epoch_checks::*;
2729
pub use self::frame_table::*;
2830
pub use self::module_artifacts::*;
2931
pub use self::module_environ::*;

crates/environ/src/obj.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,25 @@ pub const ELF_WASMTIME_STACK_MAP: &str = ".wasmtime.stackmap";
9898
/// to the 32-bit encodings for offsets this doesn't support images >=4gb.
9999
pub const ELF_WASMTIME_TRAPS: &str = ".wasmtime.traps";
100100

101+
/// A custom section which contains the offsets of instructions which check for
102+
/// the end of epochs when using `--epoch-interruption-via-mmu`.
103+
///
104+
/// The contents are examined at runtime by the signal handler to determine
105+
/// whether a segfault is due to an epoch ending (vs. a legitimate crash).
106+
///
107+
/// This section has a custom binary encoding:
108+
///
109+
/// * The section starts with a 32-bit little-endian integer which tell the
110+
/// number of items in the following array.
111+
/// * Next comes a sorted array of 32-bit unsigned little-endian integers which
112+
/// represent offsets from the beginning of the text section to the
113+
/// instruction following the load which triggers epoch-ending segfaults.
114+
/// TODO: We may point elsewhere if it's more useful to the signal handler. Be
115+
/// careful if this could end up pointing off the end of the text section.
116+
///
117+
/// The 32-bit encodings herein mean that >=4gb text sections are not supported.
118+
pub const ELF_WASMTIME_EPOCH_CHECKS: &str = ".wasmtime.epochchecks";
119+
101120
/// A custom binary-encoded section of the wasmtime compilation
102121
/// artifacts which encodes exception tables.
103122
///

0 commit comments

Comments
 (0)