[PATCH] D50310: Improve the legalisation lowering of UMULO

Simonas Kazlauskas via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 8 13:42:42 PDT 2018


nagisa added a comment.

In https://reviews.llvm.org/D50310#1192833, @efriedma wrote:

> Please choose a different name for the tests; "muloti2.ll" isn't usefully indicating what the files actually test.


Does something like `umulo-legalisation-lowering.ll` sound good?

In https://reviews.llvm.org/D50310#1192833, @efriedma wrote:

> > Correctness of the algorithm was verified by exhaustively checking the output of this algorithm for overflowing multiplication of 16 bit integers against an obviously correct widening multiplication.
>
> What exactly did you verify?  Looking again, I'm pretty sure your example IR doesn't compute the correct result: you never compute the product %LHS.HI * %RHS.HI.


The computation of `%LHS.HI * %RHS.HI` is only necessary to compute the overflow bit. If multiplication was used, the check would look like this:

  %product = { iNh, i1 } @umul.with.overflow.iNh(iNh %LHS.HI, iNh %RHS.HI)
  %0 = product.0 != 0 || product.1 ; equivalent of the current %0

The `%0 = %LHS.HI != 0 && %RHS.HI != 0` is an optimisation that calculates the same information without doing the multiply. Computing the product may be more efficient for some specific bitwidths on some targets, but I found the `%LHS.HI != 0 && %RHS.HI != 0` variant to be more palatable in the general case.

---

The following Rust code is what I used to check the correctness of current algorithm exhaustively in a reasonable time-frame. I’m willing to port this test to C if there’s a demand for it.

  type Half = u8;
  type Full = u16;
  type Double = u32;
  
  const HALF_BITS: u32 = 8;
  const FULL_BITS: u32 = 16;
  
  pub fn obviously_correct(l: Full, r: Full) -> (Full, bool) {
      // Do a widening multiplication and check the high half to see if the multiplication
      // overflowed. Also correctly handles result wrapping in case of overflow.
      let doublewide = (l as Double).wrapping_mul(r as Double);
      (doublewide as Full, (doublewide >> FULL_BITS) != 0)
  }
  
  pub fn actual_implementation(l: Full, r: Full) -> (Full, bool) {
      let (lhs_lo, rhs_lo) = (l as Half, r as Half);
      let (lhs_hi, rhs_hi) = ((l >> HALF_BITS) as Half, (r >> HALF_BITS) as Half);
  
      let overflow0 = lhs_hi != 0 && rhs_hi != 0;
      let (r1, overflow1) = lhs_hi.overflowing_mul(rhs_lo);
      let (r2, overflow2) = rhs_hi.overflowing_mul(lhs_lo);
      let r3 = (lhs_lo as Full).wrapping_mul(rhs_lo as Full);
      let r4 = ((r1 as Full) << HALF_BITS).wrapping_add((r2 as Full) << HALF_BITS);
      let (r5, overflow5) = r4.overflowing_add(r3);
      (r5, overflow0 || overflow1 || overflow2 || overflow5)
  }
  
  pub fn main() {
      for lhs in Full::min_value()..=Full::max_value() {
          for rhs in Full::min_value()..=Full::max_value() {
              assert_eq!(obviously_correct(lhs, rhs), actual_implementation(lhs, rhs),
                         "results did not match for lhs={}, rhs={}", lhs, rhs);
          }
      }
  }


https://reviews.llvm.org/D50310





More information about the llvm-commits mailing list