x64: Take SIGFPE signals for divide traps (#6026)
* x64: Take SIGFPE signals for divide traps Prior to this commit Wasmtime would configure `avoid_div_traps=true` unconditionally for Cranelift. This, for the division-based instructions, would change emitted code to explicitly trap on trap conditions instead of letting the `div` x86 instruction trap. There's no specific reason for Wasmtime, however, to specifically avoid traps in the `div` instruction. This means that the extra generated branches on x86 aren't necessary since the `div` and `idiv` instructions already trap for similar conditions as wasm requires. This commit instead disables the `avoid_div_traps` setting for Wasmtime's usage of Cranelift. Subsequently the codegen rules were updated slightly: * When `avoid_div_traps=true`, traps are no longer emitted for `div` instructions. * The `udiv`/`urem` instructions now list their trap as divide-by-zero instead of integer overflow. * The lowering for `sdiv` was updated to still explicitly check for zero but the integer overflow case is deferred to the instruction itself. * The lowering of `srem` no longer checks for zero and the listed trap for the `div` instruction is a divide-by-zero. This means that the codegen for `udiv` and `urem` no longer have any branches. The codegen for `sdiv` removes one branch but keeps the zero-check to differentiate the two kinds of traps. The codegen for `srem` removes one branch but keeps the -1 check since the semantics of `srem` mismatch with the semantics of `idiv` with a -1 divisor (specifically for INT_MIN). This is unlikely to have really all that much of a speedup but was something I noticed during #6008 which seemed like it'd be good to clean up. Plus Wasmtime's signal handling was already set up to catch `SIGFPE`, it was just never firing. * Remove the `avoid_div_traps` cranelift setting With no known users currently removing this should be possible and helps simplify the x64 backend. * x64: GC more support for avoid_div_traps Remove the `validate_sdiv_divisor*` pseudo-instructions and clean up some of the ISLE rules now that `div` is allowed to itself trap unconditionally. * x64: Store div trap code in instruction itself * Keep divisors in registers, not in memory Don't accidentally fold multiple traps together * Handle EXC_ARITHMETIC on macos * Update emit tests * Update winch and tests
This commit is contained in:
@@ -1185,3 +1185,97 @@ fn host_return_error_no_backtrace() -> Result<()> {
|
||||
assert!(f.call(&mut store, ()).is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_plus_load_reported_right() -> Result<()> {
|
||||
let engine = Engine::default();
|
||||
let mut store = Store::new(&engine, ());
|
||||
let module = Module::new(
|
||||
&engine,
|
||||
r#"
|
||||
(module
|
||||
(memory (export "memory") 1)
|
||||
(func (export "i32.div_s") (param i32 i32) (result i32)
|
||||
(i32.div_s (local.get 0) (i32.load (local.get 1))))
|
||||
(func (export "i32.div_u") (param i32 i32) (result i32)
|
||||
(i32.div_u (local.get 0) (i32.load (local.get 1))))
|
||||
(func (export "i32.rem_s") (param i32 i32) (result i32)
|
||||
(i32.rem_s (local.get 0) (i32.load (local.get 1))))
|
||||
(func (export "i32.rem_u") (param i32 i32) (result i32)
|
||||
(i32.rem_u (local.get 0) (i32.load (local.get 1))))
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
let instance = Instance::new(&mut store, &module, &[])?;
|
||||
let memory = instance.get_memory(&mut store, "memory").unwrap();
|
||||
let i32_div_s = instance.get_typed_func::<(i32, i32), i32>(&mut store, "i32.div_s")?;
|
||||
let i32_div_u = instance.get_typed_func::<(u32, u32), u32>(&mut store, "i32.div_u")?;
|
||||
let i32_rem_s = instance.get_typed_func::<(i32, i32), i32>(&mut store, "i32.rem_s")?;
|
||||
let i32_rem_u = instance.get_typed_func::<(u32, u32), u32>(&mut store, "i32.rem_u")?;
|
||||
|
||||
memory.write(&mut store, 0, &1i32.to_le_bytes()).unwrap();
|
||||
memory.write(&mut store, 4, &0i32.to_le_bytes()).unwrap();
|
||||
memory.write(&mut store, 8, &(-1i32).to_le_bytes()).unwrap();
|
||||
|
||||
assert_eq!(i32_div_s.call(&mut store, (100, 0))?, 100);
|
||||
assert_eq!(i32_div_u.call(&mut store, (101, 0))?, 101);
|
||||
assert_eq!(i32_rem_s.call(&mut store, (102, 0))?, 0);
|
||||
assert_eq!(i32_rem_u.call(&mut store, (103, 0))?, 0);
|
||||
|
||||
assert_trap(
|
||||
i32_div_s.call(&mut store, (100, 4)),
|
||||
Trap::IntegerDivisionByZero,
|
||||
);
|
||||
assert_trap(
|
||||
i32_div_u.call(&mut store, (100, 4)),
|
||||
Trap::IntegerDivisionByZero,
|
||||
);
|
||||
assert_trap(
|
||||
i32_rem_s.call(&mut store, (100, 4)),
|
||||
Trap::IntegerDivisionByZero,
|
||||
);
|
||||
assert_trap(
|
||||
i32_rem_u.call(&mut store, (100, 4)),
|
||||
Trap::IntegerDivisionByZero,
|
||||
);
|
||||
|
||||
assert_trap(
|
||||
i32_div_s.call(&mut store, (i32::MIN, 8)),
|
||||
Trap::IntegerOverflow,
|
||||
);
|
||||
assert_eq!(i32_rem_s.call(&mut store, (i32::MIN, 8))?, 0);
|
||||
|
||||
assert_trap(
|
||||
i32_div_s.call(&mut store, (100, 100_000)),
|
||||
Trap::MemoryOutOfBounds,
|
||||
);
|
||||
assert_trap(
|
||||
i32_div_u.call(&mut store, (100, 100_000)),
|
||||
Trap::MemoryOutOfBounds,
|
||||
);
|
||||
assert_trap(
|
||||
i32_rem_s.call(&mut store, (100, 100_000)),
|
||||
Trap::MemoryOutOfBounds,
|
||||
);
|
||||
assert_trap(
|
||||
i32_rem_u.call(&mut store, (100, 100_000)),
|
||||
Trap::MemoryOutOfBounds,
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
|
||||
#[track_caller]
|
||||
fn assert_trap<T>(result: Result<T>, expected: Trap) {
|
||||
match result {
|
||||
Ok(_) => panic!("expected failure"),
|
||||
Err(e) => {
|
||||
if let Some(code) = e.downcast_ref::<Trap>() {
|
||||
if *code == expected {
|
||||
return;
|
||||
}
|
||||
}
|
||||
panic!("unexpected error {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user