From 73cc433bdd5f5468580bf04564b184a35d1ff039 Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Fri, 17 Mar 2023 18:54:29 +0000 Subject: [PATCH] cranelift: simplify `icmp` against UMAX/SMIN/SMAX (#6037) * cranelift: simplify `icmp` against UMAX/SMIN/SMAX * Add tests for icmp against numeric limits --- cranelift/codegen/src/isle_prelude.rs | 30 ++++ cranelift/codegen/src/opts/algebraic.isle | 101 +++++++++-- cranelift/codegen/src/prelude.isle | 17 ++ .../filetests/filetests/egraph/algebraic.clif | 166 ++++++++++++++---- 4 files changed, 271 insertions(+), 43 deletions(-) diff --git a/cranelift/codegen/src/isle_prelude.rs b/cranelift/codegen/src/isle_prelude.rs index cf0caac323..d557d53f93 100644 --- a/cranelift/codegen/src/isle_prelude.rs +++ b/cranelift/codegen/src/isle_prelude.rs @@ -217,6 +217,36 @@ macro_rules! isle_common_prelude_methods { u64::MAX >> shift } + #[inline] + fn ty_umin(&mut self, _ty: Type) -> u64 { + 0 + } + + #[inline] + fn ty_umax(&mut self, ty: Type) -> u64 { + self.ty_mask(ty) + } + + #[inline] + fn ty_smin(&mut self, ty: Type) -> u64 { + let ty_bits = ty.bits(); + debug_assert_ne!(ty_bits, 0); + let shift = 64_u64 + .checked_sub(ty_bits.into()) + .expect("unimplemented for > 64 bits"); + (i64::MIN as u64) >> shift + } + + #[inline] + fn ty_smax(&mut self, ty: Type) -> u64 { + let ty_bits = ty.bits(); + debug_assert_ne!(ty_bits, 0); + let shift = 64_u64 + .checked_sub(ty_bits.into()) + .expect("unimplemented for > 64 bits"); + (i64::MAX as u64) >> shift + } + fn fits_in_16(&mut self, ty: Type) -> Option { if ty.bits() <= 16 && !ty.is_dynamic_vector() { Some(ty) diff --git a/cranelift/codegen/src/opts/algebraic.isle b/cranelift/codegen/src/opts/algebraic.isle index 3efa05dfe3..ede83f7c0f 100644 --- a/cranelift/codegen/src/opts/algebraic.isle +++ b/cranelift/codegen/src/opts/algebraic.isle @@ -377,20 +377,97 @@ (iconst _ (u64_from_imm64 1)))) extend) -;; `x < 0` is always false for unsigned integers, and `x >= 0` is always true -;; for unsigned integers, along with their reversals. +;; ult(x, 0) == false. (rule (simplify - (icmp (fits_in_64 (ty_int ty)) - (IntCC.UnsignedLessThan) - _ - (iconst _ (u64_from_imm64 0)))) - (iconst ty (imm64 0))) + (icmp (fits_in_64 (ty_int bty)) (IntCC.UnsignedLessThan) x zero @ (iconst _ (u64_from_imm64 0)))) + (subsume (iconst bty (imm64 0)))) + +;; ule(x, 0) == eq(x, 0) (rule (simplify - (icmp (fits_in_64 (ty_int ty)) - (IntCC.UnsignedGreaterThanOrEqual) - _ - (iconst _ (u64_from_imm64 0)))) - (iconst ty (imm64 1))) + (icmp (fits_in_64 (ty_int bty)) (IntCC.UnsignedLessThanOrEqual) x zero @ (iconst _ (u64_from_imm64 0)))) + (icmp bty (IntCC.Equal) x zero)) + +;; ugt(x, 0) == ne(x, 0). +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.UnsignedGreaterThan) x zero @ (iconst _ (u64_from_imm64 0)))) + (icmp bty (IntCC.NotEqual) x zero)) + +;; uge(x, 0) == true. +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.UnsignedGreaterThanOrEqual) x zero @ (iconst _ (u64_from_imm64 0)))) + (subsume (iconst bty (imm64 1)))) + +;; ult(x, UMAX) == ne(x, UMAX). +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.UnsignedLessThan) x umax @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_umax cty))) + (icmp bty (IntCC.NotEqual) x umax)) + +;; ule(x, UMAX) == true. +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.UnsignedLessThanOrEqual) x umax @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_umax cty))) + (subsume (iconst bty (imm64 1)))) + +;; ugt(x, UMAX) == false. +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.UnsignedGreaterThan) x umax @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_umax cty))) + (subsume (iconst bty (imm64 0)))) + +;; uge(x, UMAX) == eq(x, UMAX). +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.UnsignedGreaterThanOrEqual) x umax @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_umax cty))) + (icmp bty (IntCC.Equal) x umax)) + +;; slt(x, SMIN) == false. +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.SignedLessThan) x smin @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_smin cty))) + (subsume (iconst bty (imm64 0)))) + +;; sle(x, SMIN) == eq(x, SMIN). +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.SignedLessThanOrEqual) x smin @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_smin cty))) + (icmp bty (IntCC.Equal) x smin)) + +;; sgt(x, SMIN) == ne(x, SMIN). +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.SignedGreaterThan) x smin @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_smin cty))) + (icmp bty (IntCC.NotEqual) x smin)) + +;; sge(x, SMIN) == true. +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.SignedGreaterThanOrEqual) x smin @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_smin cty))) + (subsume (iconst bty (imm64 1)))) + +;; slt(x, SMAX) == ne(x, SMAX). +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.SignedLessThan) x smax @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_smax cty))) + (icmp bty (IntCC.NotEqual) x smax)) + +;; sle(x, SMAX) == true. +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.SignedLessThanOrEqual) x smax @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_smax cty))) + (subsume (iconst bty (imm64 1)))) + +;; sgt(x, SMAX) == false. +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.SignedGreaterThan) x smax @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_smax cty))) + (subsume (iconst bty (imm64 0)))) + +;; sge(x, SMAX) == eq(x, SMAX). +(rule (simplify + (icmp (fits_in_64 (ty_int bty)) (IntCC.SignedGreaterThanOrEqual) x smax @ (iconst cty (u64_from_imm64 y)))) + (if-let $true (u64_eq y (ty_smax cty))) + (icmp bty (IntCC.Equal) x smax)) ;; 32-bit integers zero-extended to 64-bit integers are never negative (rule (simplify diff --git a/cranelift/codegen/src/prelude.isle b/cranelift/codegen/src/prelude.isle index ef2741570a..5461109fdd 100644 --- a/cranelift/codegen/src/prelude.isle +++ b/cranelift/codegen/src/prelude.isle @@ -208,6 +208,23 @@ (extern const $I32X4XN Type) +;; Get the unsigned minimum value for a given type. +;; This always zero, but is included for completeness. +(decl pure ty_umin (Type) u64) +(extern constructor ty_umin ty_umin) + +;; Get the unsigned maximum value for a given type. +(decl pure ty_umax (Type) u64) +(extern constructor ty_umax ty_umax) + +;; Get the signed minimum value for a given type. +(decl pure ty_smin (Type) u64) +(extern constructor ty_smin ty_smin) + +;; Get the signed maximum value for a given type. +(decl pure ty_smax (Type) u64) +(extern constructor ty_smax ty_smax) + ;; Get the bit width of a given type. (decl pure ty_bits (Type) u8) (extern constructor ty_bits ty_bits) diff --git a/cranelift/filetests/filetests/egraph/algebraic.clif b/cranelift/filetests/filetests/egraph/algebraic.clif index faae822feb..73c39950de 100644 --- a/cranelift/filetests/filetests/egraph/algebraic.clif +++ b/cranelift/filetests/filetests/egraph/algebraic.clif @@ -418,45 +418,149 @@ block0(v1: i64, v2: i64): ; check: v4 = uextend.i64 v3 ; check: return v4 -function %ult_zero_always_false(i64) -> i8 { -block0(v1: i64): - v2 = iconst.i64 0 - v3 = icmp ult v1, v2 - return v3 +function %icmp_ult_0(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0 + v2 = icmp ult v0, v1 + return v2 + ; check: v3 = iconst.i8 0 + ; check: return v3 } -; check: v4 = iconst.i8 0 -; check: return v4 - -function %ugt_zero_always_false(i64) -> i8 { -block0(v1: i64): - v2 = iconst.i64 0 - v3 = icmp ugt v2, v1 - return v3 +function %icmp_ule_0(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0 + v2 = icmp ule v0, v1 + return v2 + ; check: v3 = icmp eq v0, v1 + ; check: return v3 } -; check: v5 = iconst.i8 0 -; check: return v5 - -function %uge_zero_always_false(i64) -> i8 { -block0(v1: i64): - v2 = iconst.i64 0 - v3 = icmp uge v1, v2 - return v3 +function %icmp_ugt_0(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0 + v2 = icmp ugt v0, v1 + return v2 + ; check: v3 = icmp ne v0, v1 + ; check: return v3 } -; check: v4 = iconst.i8 1 -; check: return v4 - -function %ule_zero_always_false(i64) -> i8 { -block0(v1: i64): - v2 = iconst.i64 0 - v3 = icmp ule v2, v1 - return v3 +function %icmp_uge_0(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0 + v2 = icmp uge v0, v1 + return v2 + ; check: v3 = iconst.i8 1 + ; check: return v3 } -; check: v5 = iconst.i8 1 -; check: return v5 +function %icmp_ult_umax(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0xffff_ffff + v2 = icmp ult v0, v1 + return v2 + ; check: v3 = icmp ne v0, v1 + ; check: return v3 +} + +function %icmp_ule_umax(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0xffff_ffff + v2 = icmp ule v0, v1 + return v2 + ; check: v3 = iconst.i8 1 + ; check: return v3 +} + +function %icmp_ugt_umax(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0xffff_ffff + v2 = icmp ugt v0, v1 + return v2 + ; check: v3 = iconst.i8 0 + ; check: return v3 +} + +function %icmp_uge_umax(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0xffff_ffff + v2 = icmp uge v0, v1 + return v2 + ; check: v3 = icmp eq v0, v1 + ; check: return v3 +} + +function %icmp_slt_smin(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0x8000_0000 + v2 = icmp slt v0, v1 + return v2 + ; check: v3 = iconst.i8 0 + ; check: return v3 +} + +function %icmp_sle_smin(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0x8000_0000 + v2 = icmp sle v0, v1 + return v2 + ; check: v3 = icmp eq v0, v1 + ; check: return v3 +} + +function %icmp_sgt_smin(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0x8000_0000 + v2 = icmp sgt v0, v1 + return v2 + ; check: v3 = icmp ne v0, v1 + ; check: return v3 +} + +function %icmp_sge_smin(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0x8000_0000 + v2 = icmp sge v0, v1 + return v2 + ; check: v3 = iconst.i8 1 + ; check: return v3 +} + +function %icmp_slt_smax(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0x7FFF_FFFF + v2 = icmp slt v0, v1 + return v2 + ; check: v3 = icmp ne v0, v1 + ; check: return v3 +} + +function %icmp_sle_smax(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0x7FFF_FFFF + v2 = icmp sle v0, v1 + return v2 + ; check: v3 = iconst.i8 1 + ; check: return v3 +} + +function %icmp_sgt_smax(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0x7FFF_FFFF + v2 = icmp sgt v0, v1 + return v2 + ; check: v3 = iconst.i8 0 + ; check: return v3 +} + +function %icmp_sge_smax(i32) -> i8 { +block0(v0: i32): + v1 = iconst.i32 0x7FFF_FFFF + v2 = icmp sge v0, v1 + return v2 + ; check: v3 = icmp eq v0, v1 + ; check: return v3 +} function %extend_always_above_zero(i32) -> i8 { block0(v1: i32):