Skip to content

Commit cf737a8

Browse files
committed
Replace setjmp/longjmp usage in Wasmtime
Since Wasmtime's inception it's used the `setjmp` and `longjmp` functions in C to implement handling of traps. While this solution was easy to implement, relatively portable, and performant enough, there are a number of downsides that have evolved over time to make this an unattractive approach in the long run: * Using `setjmp` fundamentally requires using C because Rust does not understand a function that returns twice. It's fundamentally unsound to invoke `setjmp` in Rust meaning that Wasmtime has forever needed a C compiler configured and set up to build. This notably means that `cargo check` cannot check other targets easily. * Using `longjmp` means that Rust function frames are unwound on the stack without running destructors. This is a dangerous operation of which we get no protection from the compiler about. Both frames entering wasm and frames exiting wasm are all skipped. Absolutely minimizing this has been beneficial for portability to platforms such as Pulley. * Currently the no_std implementation of Wasmtime requires embedders to provide `wasmtime_{setjmp,longjmp}` which is a thorn in the side of what is otherwise a mostly entirely independent implementation of Wasmtime. * There is a performance floor to using `setjmp` and `longjmp`. Calling `setjmp` requires using C but Wasmtime is otherwise written in Rust meaning that there's a Rust->C->Rust->Wasm boundary which fundamentally can't be inlined without cross-language LTO which is difficult to configure. * With the implementation of the WebAssembly exceptions proposal Wasmtime now has two means of unwinding the stack. Ideally Wasmtime would only have one, and the more general one is the method of exceptions. * Jumping out of a signal handler on Unix is tricky business. While we've made it work it's generally most robust of the signal handler simply returns which it now does. With all of that in mind the purpose of this commit is to replace the setjmp/longjmp mechanism of handling traps with the recently implemented support for exceptions in Cranelift. That is intended to resolve all of the above points in one swoop. One point in particular though that's nice about setjmp/longjmp is that unwinding the stack on a trap is an O(1) operation. For situations such as stack overflow that's a particularly nice property to have as we can guarantee embedders that traps are a constant time (albeit somewhat expensive with signals) operation. Exceptions naively require unwinding the entire stack, and although frame pointers mean we're just traversing a linked list I wanted to preserve the O(1) property here nonetheless. To achieve this a solution is implemented where the array-to-wasm (host-to-wasm) trampolines setup state in `VMStoreContext` so looking up the current trap handler frame is an O(1) operation. Namely the sp/fp/pc values for a `Handler` are stored inline. Implementing this feature required supporting relocations-to-offsets-in-functions which was not previously supported by Wasmtime. This required Cranelift refactorings such as #11570, #11585, and #11576. This then additionally required some more refactoring in this commit which was difficult to split out as it otherwise wouldn't be tested. Apart from the relocation-related business much of this change is about updating the platform signal handlers to use exceptions instead of longjmp to return. For example on Unix this means updating the `ucontext_t` with register values that the handler specifies. Windows involves updating similar contexts, and macOS mach ports ended up not needing too many changes. In terms of overall performance the relevant benchmark from this repository, compared to before this commit, is: sync/no-hook/core - host-to-wasm - typed - nop time: [10.552 ns 10.561 ns 10.571 ns] change: [−7.5238% −7.4011% −7.2786%] (p = 0.00 < 0.05) Performance has improved. Closes #3927 cc #10923 prtest:full
1 parent 42f08aa commit cf737a8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1025
-932
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ jobs:
318318
micro_checks:
319319
name: Check ${{matrix.name}}
320320
strategy:
321-
fail-fast: true
321+
fail-fast: ${{ github.event_name != 'pull_request' }}
322322
matrix:
323323
include:
324324
- name: wasmtime
@@ -504,7 +504,7 @@ jobs:
504504
name: "Platform: ${{ matrix.target }}"
505505
runs-on: ${{ matrix.os }}
506506
strategy:
507-
fail-fast: true
507+
fail-fast: ${{ github.event_name != 'pull_request' }}
508508
matrix:
509509
include:
510510
- target: x86_64-unknown-freebsd
@@ -627,7 +627,7 @@ jobs:
627627
if: needs.determine.outputs.test-capi
628628

629629
strategy:
630-
fail-fast: true
630+
fail-fast: ${{ github.event_name != 'pull_request' }}
631631
matrix:
632632
os: [ubuntu-24.04, macos-15, windows-2025]
633633

cranelift/codegen/src/isa/aarch64/inst/mod.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2905,6 +2905,10 @@ pub enum LabelUse {
29052905
/// 21-bit offset for ADR (get address of label). PC-rel, offset is not shifted. Immediate is
29062906
/// 21 signed bits, with high 19 bits in bits 23:5 and low 2 bits in bits 30:29.
29072907
Adr21,
2908+
/// Corresponds to `Reloc::Aarch64AdrPrelPgHi21`
2909+
AdrpHi21,
2910+
/// Corresponds to `Reloc::Aarch64AddAbsLo12Nc`
2911+
AddLo12,
29082912
/// 32-bit PC relative constant offset (from address of constant itself),
29092913
/// signed. Used in jump tables.
29102914
PCRel32,
@@ -2928,6 +2932,10 @@ impl MachInstLabelUse for LabelUse {
29282932
// range.
29292933
LabelUse::Adr21 => (1 << 20) - 1,
29302934
LabelUse::PCRel32 => 0x7fffffff,
2935+
2936+
// Similar to `PCRel32`
2937+
LabelUse::AdrpHi21 => 0x7fffffff,
2938+
LabelUse::AddLo12 => 0x7fffffff,
29312939
}
29322940
}
29332941

@@ -2958,6 +2966,8 @@ impl MachInstLabelUse for LabelUse {
29582966
LabelUse::Ldr19 => 0x00ffffe0, // bits 23..5 inclusive
29592967
LabelUse::Adr21 => 0x60ffffe0, // bits 30..29, 25..5 inclusive
29602968
LabelUse::PCRel32 => 0xffffffff,
2969+
LabelUse::AdrpHi21 => (0x3 << 29) | (0x1ffffc << 3),
2970+
LabelUse::AddLo12 => 0xfff << 10,
29612971
};
29622972
let pc_rel_shifted = match self {
29632973
LabelUse::Adr21 | LabelUse::PCRel32 => pc_rel,
@@ -2972,6 +2982,13 @@ impl MachInstLabelUse for LabelUse {
29722982
LabelUse::Branch26 => pc_rel_shifted & 0x3ffffff,
29732983
LabelUse::Adr21 => (pc_rel_shifted & 0x7ffff) << 5 | (pc_rel_shifted & 0x180000) << 10,
29742984
LabelUse::PCRel32 => pc_rel_shifted,
2985+
LabelUse::AdrpHi21 => {
2986+
let page = |offset| (offset & !0xfff) as i64;
2987+
let pc_rel = page(label_offset) - page(use_offset);
2988+
let pc_rel_shifted = (pc_rel >> 12) as u32;
2989+
((pc_rel_shifted & 0x3) << 29) | ((pc_rel_shifted & 0x1ffffc) << 3)
2990+
}
2991+
LabelUse::AddLo12 => (label_offset & 0xfff) << 10,
29752992
};
29762993
let is_add = match self {
29772994
LabelUse::PCRel32 => true,
@@ -3059,9 +3076,11 @@ impl MachInstLabelUse for LabelUse {
30593076
}
30603077
}
30613078

3062-
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<LabelUse> {
3063-
match (reloc, addend) {
3064-
(Reloc::Arm64Call, 0) => Some(LabelUse::Branch26),
3079+
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<(LabelUse, Addend)> {
3080+
match reloc {
3081+
Reloc::Arm64Call => Some((LabelUse::Branch26, addend)),
3082+
Reloc::Aarch64AdrPrelPgHi21 => Some((LabelUse::AdrpHi21, addend)),
3083+
Reloc::Aarch64AddAbsLo12Nc => Some((LabelUse::AddLo12, addend)),
30653084
_ => None,
30663085
}
30673086
}

cranelift/codegen/src/isa/call_conv.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl CallConv {
8484
/// Does this calling convention support exceptions?
8585
pub fn supports_exceptions(&self) -> bool {
8686
match self {
87-
CallConv::Tail | CallConv::SystemV => true,
87+
CallConv::Tail | CallConv::SystemV | CallConv::Winch => true,
8888
_ => false,
8989
}
9090
}

cranelift/codegen/src/isa/pulley_shared/inst.isle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -705,9 +705,9 @@
705705

706706
;; Helper for creating `MInst.LoadExtName*` instructions.
707707
(decl load_ext_name (BoxExternalName i64 RelocDistance) XReg)
708-
(rule 1 (load_ext_name name offset (RelocDistance.Near))
708+
(rule (load_ext_name name offset (RelocDistance.Near))
709709
(load_ext_name_near name offset))
710-
(rule 0 (load_ext_name name offset (RelocDistance.Far))
710+
(rule (load_ext_name name offset (RelocDistance.Far))
711711
(load_ext_name_far name offset))
712712

713713
;;;; Helpers for Emitting Calls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

cranelift/codegen/src/isa/pulley_shared/inst/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -906,9 +906,9 @@ impl MachInstLabelUse for LabelUse {
906906
}
907907
}
908908

909-
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<LabelUse> {
910-
match (reloc, addend) {
911-
(Reloc::PulleyPcRel, 0) => Some(LabelUse::PcRel),
909+
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<(LabelUse, Addend)> {
910+
match reloc {
911+
Reloc::PulleyPcRel => Some((LabelUse::PcRel, addend)),
912912
_ => None,
913913
}
914914
}

cranelift/codegen/src/isa/riscv64/inst/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,9 +1796,11 @@ impl MachInstLabelUse for LabelUse {
17961796
(veneer_offset, Self::PCRel32)
17971797
}
17981798

1799-
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<LabelUse> {
1800-
match (reloc, addend) {
1801-
(Reloc::RiscvCallPlt, _) => Some(Self::PCRel32),
1799+
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<(LabelUse, Addend)> {
1800+
match reloc {
1801+
Reloc::RiscvCallPlt => Some((Self::PCRel32, addend)),
1802+
Reloc::RiscvPCRelHi20 => Some((Self::PCRelHi20, addend)),
1803+
Reloc::RiscvPCRelLo12I => Some((Self::PCRelLo12I, addend)),
18021804
_ => None,
18031805
}
18041806
}

cranelift/codegen/src/isa/s390x/inst/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3513,10 +3513,10 @@ impl MachInstLabelUse for LabelUse {
35133513
unreachable!();
35143514
}
35153515

3516-
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<Self> {
3517-
match (reloc, addend) {
3518-
(Reloc::S390xPCRel32Dbl, 2) => Some(LabelUse::PCRel32Dbl),
3519-
(Reloc::S390xPLTRel32Dbl, 2) => Some(LabelUse::PCRel32Dbl),
3516+
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<(Self, Addend)> {
3517+
match reloc {
3518+
Reloc::S390xPCRel32Dbl => Some((LabelUse::PCRel32Dbl, addend - 2)),
3519+
Reloc::S390xPLTRel32Dbl => Some((LabelUse::PCRel32Dbl, addend - 2)),
35203520
_ => None,
35213521
}
35223522
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,9 +1630,9 @@ impl MachInstLabelUse for LabelUse {
16301630
}
16311631
}
16321632

1633-
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<Self> {
1634-
match (reloc, addend) {
1635-
(Reloc::X86CallPCRel4, -4) => Some(LabelUse::JmpRel32),
1633+
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<(Self, Addend)> {
1634+
match reloc {
1635+
Reloc::X86CallPCRel4 => Some((LabelUse::JmpRel32, addend + 4)),
16361636
_ => None,
16371637
}
16381638
}

cranelift/codegen/src/machinst/buffer.rs

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -658,15 +658,8 @@ impl<I: VCodeInst> MachBuffer<I> {
658658

659659
/// Bind a label to the current offset. A label can only be bound once.
660660
pub fn bind_label(&mut self, label: MachLabel, ctrl_plane: &mut ControlPlane) {
661-
trace!(
662-
"MachBuffer: bind label {:?} at offset {}",
663-
label,
664-
self.cur_offset()
665-
);
666-
debug_assert_eq!(self.label_offsets[label.0 as usize], UNKNOWN_LABEL_OFFSET);
667-
debug_assert_eq!(self.label_aliases[label.0 as usize], UNKNOWN_LABEL);
668661
let offset = self.cur_offset();
669-
self.label_offsets[label.0 as usize] = offset;
662+
self.bind_label_at_offset(label, offset);
670663
self.lazily_clear_labels_at_tail();
671664
self.labels_at_tail.push(label);
672665

@@ -681,6 +674,14 @@ impl<I: VCodeInst> MachBuffer<I> {
681674
// Post-invariant: by `optimize_branches()` (see argument there).
682675
}
683676

677+
/// Bind a label to the specified offset. A label can only be bound once.
678+
fn bind_label_at_offset(&mut self, label: MachLabel, offset: CodeOffset) {
679+
trace!("MachBuffer: bind label {label:?} at offset {offset}");
680+
debug_assert_eq!(self.label_offsets[label.0 as usize], UNKNOWN_LABEL_OFFSET);
681+
debug_assert_eq!(self.label_aliases[label.0 as usize], UNKNOWN_LABEL);
682+
self.label_offsets[label.0 as usize] = offset;
683+
}
684+
684685
/// Lazily clear `labels_at_tail` if the tail offset has moved beyond the
685686
/// offset that it applies to.
686687
fn lazily_clear_labels_at_tail(&mut self) {
@@ -2127,7 +2128,17 @@ impl MachBranch {
21272128
pub struct MachTextSectionBuilder<I: VCodeInst> {
21282129
buf: MachBuffer<I>,
21292130
next_func: usize,
2131+
expected_funcs: usize,
21302132
force_veneers: ForceVeneers,
2133+
/// A list of (new_label, label, offset_from_label) to bind once all
2134+
/// functions have been added.
2135+
///
2136+
/// This list is pushed to in `resolve_reloc` below whenever the target of a
2137+
/// relocation is not a function itself but a relative location within a
2138+
/// function. When that happens `new_label` is created and binding it is
2139+
/// deferred until the end of the builder when all functions are known. This
2140+
/// is used to bind `new_label` to `offset(label) + offset_from_label`.
2141+
labels_to_bind: Vec<(MachLabel, MachLabel, i64)>,
21312142
}
21322143

21332144
impl<I: VCodeInst> MachTextSectionBuilder<I> {
@@ -2139,7 +2150,9 @@ impl<I: VCodeInst> MachTextSectionBuilder<I> {
21392150
MachTextSectionBuilder {
21402151
buf,
21412152
next_func: 0,
2153+
expected_funcs: num_funcs,
21422154
force_veneers: ForceVeneers::No,
2155+
labels_to_bind: Vec::new(),
21432156
}
21442157
}
21452158
}
@@ -2151,7 +2164,7 @@ impl<I: VCodeInst> TextSectionBuilder for MachTextSectionBuilder<I> {
21512164
func: &[u8],
21522165
align: u32,
21532166
ctrl_plane: &mut ControlPlane,
2154-
) -> u64 {
2167+
) -> (u64, Option<usize>) {
21552168
// Conditionally emit an island if it's necessary to resolve jumps
21562169
// between functions which are too far away.
21572170
let size = func.len() as u32;
@@ -2162,15 +2175,18 @@ impl<I: VCodeInst> TextSectionBuilder for MachTextSectionBuilder<I> {
21622175

21632176
self.buf.align_to(align);
21642177
let pos = self.buf.cur_offset();
2165-
if labeled {
2166-
self.buf.bind_label(
2167-
MachLabel::from_block(BlockIndex::new(self.next_func)),
2168-
ctrl_plane,
2169-
);
2178+
let label = if labeled {
21702179
self.next_func += 1;
2180+
Some(MachLabel::from_block(BlockIndex::new(self.next_func - 1)))
2181+
} else {
2182+
None
2183+
};
2184+
2185+
if let Some(label) = label {
2186+
self.buf.bind_label(label, ctrl_plane);
21712187
}
21722188
self.buf.put_data(func);
2173-
u64::from(pos)
2189+
(u64::from(pos), label.map(|l| l.0 as usize))
21742190
}
21752191

21762192
fn resolve_reloc(&mut self, offset: u64, reloc: Reloc, addend: Addend, target: usize) -> bool {
@@ -2180,10 +2196,21 @@ impl<I: VCodeInst> TextSectionBuilder for MachTextSectionBuilder<I> {
21802196
let label = MachLabel::from_block(BlockIndex::new(target));
21812197
let offset = u32::try_from(offset).unwrap();
21822198
match I::LabelUse::from_reloc(reloc, addend) {
2183-
Some(label_use) => {
2199+
// If the offset from `label` is exactly 0, we can bind to the
2200+
// existing label.
2201+
Some((label_use, 0)) => {
21842202
self.buf.use_label_at_offset(offset, label, label_use);
21852203
true
21862204
}
2205+
2206+
// If `addend` is nonzero, however, make a new label and defer its
2207+
// resolution while binding `label_use` to the new label.
2208+
Some((label_use, addend)) => {
2209+
let new_label = self.buf.get_label();
2210+
self.buf.use_label_at_offset(offset, new_label, label_use);
2211+
self.labels_to_bind.push((new_label, label, addend));
2212+
true
2213+
}
21872214
None => false,
21882215
}
21892216
}
@@ -2198,7 +2225,16 @@ impl<I: VCodeInst> TextSectionBuilder for MachTextSectionBuilder<I> {
21982225

21992226
fn finish(&mut self, ctrl_plane: &mut ControlPlane) -> Vec<u8> {
22002227
// Double-check all functions were pushed.
2201-
assert_eq!(self.next_func, self.buf.label_offsets.len());
2228+
assert_eq!(self.next_func, self.expected_funcs);
2229+
2230+
// If any new labels were introduced relative to where functions ended
2231+
// up then bind those all here.
2232+
for (new_label, label, addend) in self.labels_to_bind.drain(..) {
2233+
let offset = self.buf.resolve_label_offset(label);
2234+
assert!(offset != UNKNOWN_LABEL_OFFSET);
2235+
let new_offset = u32::try_from(i64::from(offset) + addend).unwrap();
2236+
self.buf.bind_label_at_offset(new_label, new_offset);
2237+
}
22022238

22032239
// Finish up any veneers, if necessary.
22042240
self.buf

cranelift/codegen/src/machinst/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,12 @@ pub trait MachInstLabelUse: Clone + Copy + Debug + Eq {
260260

261261
/// Returns the corresponding label-use for the relocation specified.
262262
///
263-
/// This returns `None` if the relocation doesn't have a corresponding
264-
/// representation for the target architecture.
265-
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<Self>;
263+
/// The `reloc` and `addend` specified are the kind of relocation and static
264+
/// offset to the target. This function returns `None` if the relocation
265+
/// isn't modeled by `LabelUse`. When this function returns `Some` it can
266+
/// return a new `Addend` as some relocations modify the addend that they
267+
/// are applied to.
268+
fn from_reloc(reloc: Reloc, addend: Addend) -> Option<(Self, Addend)>;
266269
}
267270

268271
/// Describes a block terminator (not call) in the VCode.
@@ -527,14 +530,15 @@ pub trait TextSectionBuilder {
527530
/// `resolve_reloc`.
528531
///
529532
/// This function returns the offset at which the data was placed in the
530-
/// text section.
533+
/// text section along with the optional label that was created for this
534+
/// function (suitable to pass to `resolve_reloc`).
531535
fn append(
532536
&mut self,
533537
labeled: bool,
534538
data: &[u8],
535539
align: u32,
536540
ctrl_plane: &mut ControlPlane,
537-
) -> u64;
541+
) -> (u64, Option<usize>);
538542

539543
/// Attempts to resolve a relocation for this function.
540544
///

0 commit comments

Comments
 (0)