All files / TRANSPILE/3-Render/codegen/helpers SaturatingCast.ts

93.33% Statements 14/15
87.5% Branches 7/8
100% Functions 2/2
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69                                                                11x 11x 11x   11x 11x     11x 11x   11x 11x 11x       11x                 46x 46x       46x          
/**
 * ADR-024 / Issue #632: the one shape of a saturating float-to-integer cast.
 *
 * C leaves an out-of-range float-to-int conversion undefined, and C-Next's
 * default is to saturate, so the cast is a bounded ternary. Written once
 * because it is emitted in two places (#1668): inline, and as the body of the
 * single-evaluation helper a side-effecting operand is routed through. Two
 * copies of the ternary would be free to disagree about a bound.
 *
 * MISRA C:2012 Rule 10.3: the limit macros have type `int`, so each is cast
 * to the target type before use (the naive form assigns an `int` expression
 * to a narrower essential type).
 */
import TYPE_LIMITS from "../types/TYPE_LIMITS";
import CNEXT_TO_C_TYPE_MAP from "../../../../utils/constants/TypeMappings";
import invariant from "../../../../utils/invariant";
 
class SaturatingCast {
  /**
   * The bounded ternary saturating `operand` (a float expression, evaluated
   * up to three times) into `targetTypeName`, or null when the target has no
   * known limits (#644: the caller casts plainly).
   *
   * @param cast the mode's cast spelling, `(T)x` or `static_cast<T>(x)`
   */
  static expression(
    operand: string,
    sourceType: string,
    targetTypeName: string,
    targetCType: string,
    cast: (type: string, expr: string) => string,
  ): string | null {
    const maxValue = TYPE_LIMITS.TYPE_MAX[targetTypeName];
    const minValue = TYPE_LIMITS.TYPE_MIN[targetTypeName];
    Iif (!maxValue) return null;
 
    const floatSuffix = sourceType === "f32" ? "f" : "";
    const floatCType = SaturatingCast.floatCType(sourceType);
    // For unsigned types, minValue is "0", for signed a macro like INT8_MIN
    const minComparison =
      minValue === "0" ? `0.0${floatSuffix}` : `((${floatCType})${minValue})`;
    const maxComparison = `((${floatCType})${maxValue})`;
 
    const finalCast = cast(targetCType, `(${operand})`);
    const castMax = cast(targetCType, maxValue);
    const castMin = cast(targetCType, minValue);
    // `>=`, not `>` (#1760 review): `(float)UINT32_MAX` rounds up to 2^32,
    // and under `>` that value reached the raw cast, which is undefined. At an
    // exactly representable maximum both branches give MAX.
    return `((${operand}) >= ${maxComparison} ? ${castMax} : (${operand}) < ${minComparison} ? ${castMin} : ${finalCast})`;
  }
 
  /**
   * The C type of a float source, from the one type map (#1760 second
   * review: this and the float-bits union each spelled it, with opposite
   * defaults for anything but `f32` and `f64`)
   */
  static floatCType(sourceType: string): string {
    const cType = CNEXT_TO_C_TYPE_MAP[sourceType];
    invariant(
      cType === "float" || cType === "double",
      `a saturating cast's source is a float ('${sourceType}')`,
    );
    return cType;
  }
}
 
export default SaturatingCast;