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:
@@ -3503,13 +3503,20 @@
|
||||
|
||||
;; Rules for `udiv` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; NB: a `RegMem` divisor, while allowed in the instruction encoding, isn't
|
||||
;; used right now to prevent a possibly-trapping load getting folded into the
|
||||
;; `div` instruction. Ideally non-trapping loads would get folded, however, or
|
||||
;; alternatively Wasmtime/Cranelift would grow support for multiple traps on
|
||||
;; a single opcode and the signal kind would differentiate at runtime.
|
||||
|
||||
;; The inputs to the `div` instruction are different for 8-bit division so
|
||||
;; it needs a special case here since the instruction being crafted has a
|
||||
;; different shape.
|
||||
(rule 2 (lower (udiv a @ (value_type $I8) b))
|
||||
(x64_div8 (extend_to_gpr a $I32 (ExtendKind.Zero))
|
||||
(nonzero_divisor $I8 b)
|
||||
(DivSignedness.Unsigned)))
|
||||
(put_in_gpr b)
|
||||
(DivSignedness.Unsigned)
|
||||
(TrapCode.IntegerDivisionByZero)))
|
||||
|
||||
;; 16-to-64-bit division is all done with a similar instruction and the only
|
||||
;; tricky requirement here is that when div traps are disallowed the divisor
|
||||
@@ -3517,29 +3524,40 @@
|
||||
(rule 1 (lower (udiv a @ (value_type (fits_in_64 ty)) b))
|
||||
(x64_div_quotient a
|
||||
(imm $I64 0)
|
||||
(nonzero_divisor ty b)
|
||||
(put_in_gpr b)
|
||||
(raw_operand_size_of_type ty)
|
||||
(DivSignedness.Unsigned)))
|
||||
(DivSignedness.Unsigned)
|
||||
(TrapCode.IntegerDivisionByZero)))
|
||||
|
||||
;; Helper to place `Value` into a `Gpr` while possibly trapping if it's zero.
|
||||
;; Rules for `sdiv` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(rule 2 (lower (sdiv a @ (value_type $I8) b))
|
||||
(x64_div8 (x64_sign_extend_data a (OperandSize.Size8))
|
||||
(nonzero_sdiv_divisor $I8 b)
|
||||
(DivSignedness.Signed)
|
||||
(TrapCode.IntegerOverflow)))
|
||||
|
||||
(rule 1 (lower (sdiv a @ (value_type (fits_in_64 ty)) b))
|
||||
(let (
|
||||
(a Gpr a)
|
||||
(size OperandSize (raw_operand_size_of_type ty))
|
||||
)
|
||||
(x64_div_quotient a
|
||||
(x64_sign_extend_data a size)
|
||||
(nonzero_sdiv_divisor ty b)
|
||||
size
|
||||
(DivSignedness.Signed)
|
||||
(TrapCode.IntegerOverflow))))
|
||||
|
||||
;; Checks to make sure that the input `Value` is a non-zero value for `sdiv`.
|
||||
;;
|
||||
;; If the `avoid_div_traps=true` codegen setting is specified then the value
|
||||
;; is checked for zero and a trap happens before the value is returned as a
|
||||
;; register here.
|
||||
(decl nonzero_divisor (Type Value) Gpr)
|
||||
|
||||
;; As a special-case if the divisor is a constant number which is nonzero then
|
||||
;; no matter what there's no checks necessary.
|
||||
(rule 2 (nonzero_divisor ty (iconst (u64_from_imm64 (u64_nonzero n))))
|
||||
;; This is required to differentiate the divide-by-zero trap from the
|
||||
;; integer-overflow trap, the two trapping conditions of signed division.
|
||||
(decl nonzero_sdiv_divisor (Type Value) Reg)
|
||||
(rule 1 (nonzero_sdiv_divisor ty (iconst imm))
|
||||
(if-let n (safe_divisor_from_imm64 ty imm))
|
||||
(imm ty n))
|
||||
|
||||
;; No checks necessary when `avoid_div_traps=false`
|
||||
(rule 1 (nonzero_divisor ty val)
|
||||
(if-let $false (avoid_div_traps))
|
||||
val)
|
||||
|
||||
;; Base case traps if `val` is zero by using a `test` + `trap_if` combo
|
||||
(rule (nonzero_divisor ty val)
|
||||
(rule 0 (nonzero_sdiv_divisor ty val)
|
||||
(let (
|
||||
(val Reg val)
|
||||
(_ InstOutput (side_effect (with_flags_side_effect
|
||||
@@ -3548,64 +3566,26 @@
|
||||
)
|
||||
val))
|
||||
|
||||
;; Rules for `sdiv` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(rule 2 (lower (sdiv a @ (value_type $I8) b))
|
||||
(let (
|
||||
(a Gpr (x64_sign_extend_data a (OperandSize.Size8)))
|
||||
)
|
||||
(x64_div8 a (safe_sdiv_divisor $I8 a b) (DivSignedness.Signed))))
|
||||
|
||||
(rule 1 (lower (sdiv a @ (value_type (fits_in_64 ty)) b))
|
||||
(let (
|
||||
(a Gpr a)
|
||||
(size OperandSize (raw_operand_size_of_type ty))
|
||||
(b Gpr (safe_sdiv_divisor ty a b))
|
||||
)
|
||||
(x64_div_quotient a (x64_sign_extend_data a size) b size (DivSignedness.Signed))))
|
||||
|
||||
;; Similar to `nonzero_divisor` except this checks to make sure that the divisor
|
||||
;; provided as a `Value` is safe to divide into the dividend `Gpr` provided.
|
||||
(decl safe_sdiv_divisor (Type Gpr Value) Reg)
|
||||
|
||||
;; If the divisor is a constant that isn't 0 or -1, then it's always safe so
|
||||
;; materialize it into a register.
|
||||
(rule 3 (safe_sdiv_divisor ty a (iconst imm))
|
||||
(if-let n (safe_divisor_from_imm64 ty imm))
|
||||
(imm ty n))
|
||||
|
||||
;; With `avoid_div_traps=false` the divisor can be plumbed through.
|
||||
;;
|
||||
;; Note that CLIF semantics dictate that division-by-zero and INT_MIN/-1 both
|
||||
;; trap, but this matches the hardware semantics of `idiv` on x64 so they're
|
||||
;; fine to get plumbed through as-is.
|
||||
(rule 2 (safe_sdiv_divisor ty a b)
|
||||
(if-let $false (avoid_div_traps))
|
||||
b)
|
||||
|
||||
;; The base cases here rely on some pseudo-instructions to do the checks to
|
||||
;; jump around with labels and such.
|
||||
(rule 1 (safe_sdiv_divisor $I64 a b) (validate_sdiv_divisor64 a b))
|
||||
(rule 0 (safe_sdiv_divisor ty a b) (validate_sdiv_divisor (raw_operand_size_of_type ty) a b))
|
||||
|
||||
;; Rules for `urem` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; The remainder is in AH, so take the result of the division and right-shift
|
||||
;; by 8.
|
||||
(rule 2 (lower (urem a @ (value_type $I8) b))
|
||||
(let (
|
||||
(a Gpr (extend_to_gpr a $I32 (ExtendKind.Zero)))
|
||||
(b Gpr (nonzero_divisor $I8 b))
|
||||
(result Gpr (x64_div8 a b (DivSignedness.Unsigned)))
|
||||
(result Gpr (x64_div8 (extend_to_gpr a $I32 (ExtendKind.Zero))
|
||||
(put_in_gpr b) ;; see `udiv` for why not `gpr_mem`
|
||||
(DivSignedness.Unsigned)
|
||||
(TrapCode.IntegerDivisionByZero)))
|
||||
)
|
||||
(x64_shr $I64 result (Imm8Reg.Imm8 8))))
|
||||
|
||||
(rule 1 (lower (urem a @ (value_type (fits_in_64 ty)) b))
|
||||
(x64_div_remainder a
|
||||
(imm $I64 0)
|
||||
(nonzero_divisor ty b)
|
||||
(put_in_gpr b) ;; see `udiv` for why not `gpr_mem`
|
||||
(raw_operand_size_of_type ty)
|
||||
(DivSignedness.Unsigned)))
|
||||
(DivSignedness.Unsigned)
|
||||
(TrapCode.IntegerDivisionByZero)))
|
||||
|
||||
;; Rules for `srem` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -3618,7 +3598,7 @@
|
||||
(if-let n (safe_divisor_from_imm64 $I8 imm))
|
||||
(let (
|
||||
(a Gpr (x64_sign_extend_data a (OperandSize.Size8)))
|
||||
(result Gpr (x64_div8 a (imm $I8 n) (DivSignedness.Signed)))
|
||||
(result Gpr (x64_div8 a (imm $I8 n) (DivSignedness.Signed) (TrapCode.IntegerDivisionByZero)))
|
||||
)
|
||||
(x64_shr $I64 result (Imm8Reg.Imm8 8))))
|
||||
|
||||
@@ -3633,19 +3613,18 @@
|
||||
(x64_sign_extend_data a size)
|
||||
(imm ty n)
|
||||
size
|
||||
(DivSignedness.Signed))))
|
||||
(DivSignedness.Signed)
|
||||
(TrapCode.IntegerDivisionByZero))))
|
||||
|
||||
(rule 1 (lower (srem a @ (value_type $I8) b))
|
||||
(let (
|
||||
(a Gpr (x64_sign_extend_data a (OperandSize.Size8)))
|
||||
(b Gpr (nonzero_divisor $I8 b))
|
||||
)
|
||||
(x64_shr $I64 (x64_checked_srem_seq8 a b) (Imm8Reg.Imm8 8))))
|
||||
|
||||
(rule (lower (srem a @ (value_type ty) b))
|
||||
(let (
|
||||
(a Gpr a)
|
||||
(b Gpr (nonzero_divisor ty b))
|
||||
(size OperandSize (raw_operand_size_of_type ty))
|
||||
(hi Gpr (x64_sign_extend_data a size))
|
||||
(tmp ValueRegs (x64_checked_srem_seq size a hi b))
|
||||
|
||||
Reference in New Issue
Block a user