[PATCH] D52286: [Intrinsic] Signed Saturation Intirnsic

Bevin Hansson via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Mon Oct 8 02:03:53 PDT 2018


ebevhan added a comment.

I've been experimenting a bit with early expansion of our sat intrinsic to see how the code generation is affected by it. In general, it doesn't actually seem like there's much of an effect. In fact, neither our benchmarks nor the user code I'm looking at seem to be terribly affected by expanding the intrinsic. This is probably due to most of the instances of saturation being 'locked down' by surrounding intrinsics.

However, it's definitely possible to construct quite simple cases that are worsened by expansion. Here's an example. Obviously I'm the only one who can compile this, but I think the idea gets across:

  __fixed ac(__accum a, unsigned int n) {
    a = a < 0.0a ? 0.0a : a;
    __accum s = 0.0a;
    for (unsigned i = 0; i < n; i++)
      s += (__sat __fixed)a;
    return (__sat __fixed)s;
  }

If we expand the saturation intrinsic (used for the casts from `__accum` to `__sat __fixed`) right after IR emission, our final optimized IR becomes:

  define i16 @ac(i24 %a, i16 %n) #0 {
  entry:
    %0 = icmp sgt i24 %a, 0
    %cond = select i1 %0, i24 %a, i24 0
    %cmp19 = icmp eq i16 %n, 0
    br i1 %cmp19, label %.thread13, label %for.cond.cleanup
  
  for.cond.cleanup:                                 ; preds = %entry
    %1 = icmp slt i24 %cond, 32767
    %2 = select i1 %1, i24 %cond, i24 32767
    %3 = and i24 %2, 65535
    %4 = add i16 %n, -1
    %5 = zext i16 %4 to i24
    %6 = add nuw nsw i24 %5, 1
    %7 = mul i24 %6, %3
    %8 = icmp sgt i24 %7, -32768
    br i1 %8, label %9, label %.thread13
  
  ; <label>:9:                                      ; preds = %for.cond.cleanup
    %10 = icmp slt i24 %7, 32767
    %extract.t15 = trunc i24 %7 to i16
    br i1 %10, label %.thread13, label %11
  
  .thread13:                                        ; preds = %9, %for.cond.cleanup, %entry
    %.off014 = phi i16 [ %extract.t15, %9 ], [ 0, %entry ], [ -32768, %for.cond.cleanup ]
    br label %11
  
  ; <label>:11:                                     ; preds = %.thread13, %9
    %.off0 = phi i16 [ %.off014, %.thread13 ], [ 32767, %9 ]
    ret i16 %.off0
  }

For our target, this doesn't select any saturation instructions, and consists of 19 static cycles.

If we keep the saturation intrinsic instead:

  define i16 @ac(i24 %a, i16 %n) {
  entry:
    %cmp17 = icmp eq i16 %n, 0
    br i1 %cmp17, label %for.cond.cleanup, label %for.body.lr.ph
  
  for.body.lr.ph:                                   ; preds = %entry
    %0 = icmp sgt i24 %a, 0
    %cond = select i1 %0, i24 %a, i24 0
    %1 = tail call i24 @llvm.sat.i24(i24 %cond, i32 16)
    %2 = add i16 %n, -1
    %3 = zext i16 %2 to i24
    %4 = shl i24 %1, 8
    %5 = ashr exact i24 %4, 8
    %6 = add nuw nsw i24 %3, 1
    %7 = mul i24 %6, %5
    br label %for.cond.cleanup
  
  for.cond.cleanup:                                 ; preds = %for.body.lr.ph, %entry
    %s.0.lcssa = phi i24 [ 0, %entry ], [ %7, %for.body.lr.ph ]
    %8 = tail call i24 @llvm.sat.i24(i24 %s.0.lcssa, i32 16)
    %resize3 = trunc i24 %8 to i16
    ret i16 %resize3
  }

This builds to 13 cycles.

I suppose you could consider this case to be a bit contrived, since the loop is eliminated, but it's still the case that the optimizer mangled the original saturation operations. Essentially anything that results in the optimizer moving the max and min selects away from each other will accomplish this.


Repository:
  rL LLVM

https://reviews.llvm.org/D52286





More information about the llvm-commits mailing list