All files / utils BitUtils.ts

100% Statements 51/51
97.56% Branches 40/41
100% Functions 16/16
100% Lines 46/46

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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264                    56x     56x                                                                       63x 63x   9x 63x                 81x                       75x 19x 11x                         110x 110x 101x   9x                     112x 112x                                       55x 55x 55x 55x                                             40x 40x 40x 40x                                       14x 14x                                           7x 7x 7x         47x               47x 47x             178x 173x 173x                 95x 95x 57x   38x                 116x 113x 113x 116x         294x         286x 286x          
import CompositeType from "./CompositeType";
import CExpression from "./CExpression";
import CNEXT_TO_C_TYPE_MAP from "./constants/TypeMappings";
import type IBitWidth from "../types/IBitWidth";
import type IOperandType from "../types/IOperandType";
 
/**
 * The storage's width, from its fixed-width C type: `uint32_t`, `int16_t`, ...
 * A type this does not name (`bool`, or none known) has no width here.
 */
const FIXED_WIDTH = /^u?int(8|16|32|64)_t$/;
 
/** A bit width written as a constant: `24`, or `24U` once suffixed */
const CONSTANT_WIDTH = /^(\d+)U?$/;
 
/**
 * Bit manipulation utilities for C code generation: the one rule for
 * writing a bit or a bit range of an integer, whatever names it -- a
 * variable, a bitmap field, a register member, or a float's bits.
 *
 * Every write takes its storage's C type, because two decisions depend on
 * it (#1668):
 * - an operand shifted into storage wider than 16 bits is cast to the
 *   storage's own unsigned width first. C promises `unsigned int` only 16
 *   bits, and where it is 16 bits (AVR) `1U << 16` is undefined and
 *   `~(1U << 3)` is a 16-bit mask that clears bits 16-31 of the storage it
 *   is ANDed with -- a silent miscompile that compiles cleanly. A
 *   fixed-width type is exact on every target, so the output is too;
 * - storage narrower than 32 bits, or signed, takes the MISRA C:2012 Rule
 *   10.3 cast back to its type: the operators promote, and the result is
 *   unsigned (#1760 second review: an `i32` or `i64` target took none).
 *
 * Any other named storage is an integer whose width the target does not fix
 * (`int_fast16_t`, which is `long` on a 64-bit host): the typer gives it no
 * width, and only such a type is passed here by name. Its write is worked in
 * `uintmax_t` and cast back to the storage type (owner ruling, #1760 review),
 * which is correct at any width and the same on every target. A 32-bit `1U`
 * mask there cleared the upper half of a 64-bit `long`.
 */
class BitUtils {
  /**
   * The C type an integer's bits are read or written in: a known width's
   * fixed-width type, or, for an integer the target gives no width, the type
   * its header spelled, which is worked in `uintmax_t` (see above). Undefined
   * for anything else. #1760 review: a read sized its mask from the ROOT's
   * declared type while a write asked this of the value ranged, so
   * `this.v[0, n]` on a u64 had a 32-bit mask.
   */
  static storageOf(value: IOperandType | null): string | undefined {
    const type = CompositeType.integerOf([value]);
    if (type !== null) return CNEXT_TO_C_TYPE_MAP[type];
    const isInteger =
      value?.category === "signed" || value?.category === "unsigned";
    return isInteger ? (value.cType ?? undefined) : undefined;
  }
 
  /**
   * A `[start, width]` width as C: its folded value, `NU`, when it folds, so
   * the mask is a literal (Issue #1094, #1096: `(1U << WIDTH) - 1U` is
   * undefined at full width); its rendered text otherwise
   */
  static widthText(width: IBitWidth): string {
    return width.folded === undefined ? width.text : `${width.folded}U`;
  }
 
  /**
   * Convert a boolean expression to an unsigned integer (0U or 1U).
   * Handles literal "true"/"false" and generates ternary for expressions.
   * Uses unsigned literals for MISRA C:2012 Rule 10.1 compliance.
   *
   * @param expr - The expression to convert
   * @returns C code string representing the unsigned integer value
   */
  static boolToInt(expr: string): string {
    if (expr === "true") return "1U";
    if (expr === "false") return "0U";
    return `(${expr} ? 1U : 0U)`;
  }
 
  /**
   * The mask of `width` ones. A constant width is written as its value, a
   * hex literal C sizes to fit on every target; only a width known at run
   * time is computed, in the storage's width.
   *
   * @param width - The bit width (number, or the generated C for it)
   * @param storage - The C type of the value masked, when known
   * @returns C code string for the mask
   */
  static generateMask(width: string | number, storage?: string): string {
    const constant = CONSTANT_WIDTH.exec(String(width));
    if (constant) {
      return BitUtils.maskHex(Number(constant[1]));
    }
    return `((${BitUtils.widen("1U", storage)} << ${width}) - 1U)`;
  }
 
  /**
   * The hex literal of `width` ones, e.g. 4 -> `0xFU`, 64 ->
   * `0xFFFFFFFFFFFFFFFFU`. The `U` suffix is MISRA C:2012 Rule 7.2's.
   *
   * @param width - The bit width, 0 to 64
   * @returns Hex mask string
   */
  static maskHex(width: number): string {
    const ones = (1n << BigInt(width)) - 1n;
    return `0x${ones.toString(16).toUpperCase()}U`;
  }
 
  /**
   * Generate read-modify-write code for single bit assignment.
   * Pattern: target = (target & ~(1 << offset)) | (value << offset)
   * Converts boolean values via boolToInt.
   *
   * @param target - The variable to modify
   * @param offset - Bit position (0-indexed)
   * @param value - Value to write (will be converted via boolToInt)
   * @param storage - The target's C type, when known
   * @returns C code string for the assignment
   */
  static singleBitWrite(
    target: string,
    offset: string | number,
    value: string,
    storage?: string,
  ): string {
    const one = BitUtils.widen("1U", storage);
    const bit = BitUtils.widen(BitUtils.boolToInt(value), storage);
    const rhs = `(${target} & ~(${one} << ${offset})) | (${bit} << ${offset})`;
    return BitUtils.assign(target, rhs, storage);
  }
 
  /**
   * Generate read-modify-write code for multi-bit assignment.
   * Pattern: target = (target & ~(mask << offset)) | ((value & mask) << offset)
   *
   * @param target - The variable to modify
   * @param offset - Starting bit position (0-indexed)
   * @param width - Number of bits to write: a constant, or the C for it with
   *   its fold, so every writer folds (#1096)
   * @param value - Value to write, masked as one operand (#1760 second review:
   *   `a | b & mask` wrote bits outside the range)
   * @param storage - The target's C type, when known
   * @returns C code string for the assignment
   */
  static multiBitWrite(
    target: string,
    offset: string | number,
    width: number | IBitWidth,
    value: string,
    storage?: string,
  ): string {
    const mask = BitUtils.shiftedMask(BitUtils.widthOf(width), storage);
    const masked = `(${CExpression.operand(value)} & ${mask})`;
    const rhs = `(${target} & ~(${mask} << ${offset})) | (${masked} << ${offset})`;
    return BitUtils.assign(target, rhs, storage);
  }
 
  /**
   * Generate write-only register code for single bit assignment.
   * No read-modify-write, just shifts the value into position.
   * Pattern: target = (value << offset)
   *
   * @param target - The register to write
   * @param offset - Bit position (0-indexed)
   * @param value - Value to write (will be converted via boolToInt)
   * @param storage - The target's C type, when known
   * @returns C code string for the assignment
   */
  static writeOnlySingleBit(
    target: string,
    offset: string | number,
    value: string,
    storage?: string,
  ): string {
    const bit = BitUtils.widen(BitUtils.boolToInt(value), storage);
    return `${target} = ${BitUtils.narrowCast(storage)}(${bit} << ${offset});`;
  }
 
  /**
   * Generate write-only register code for multi-bit assignment.
   * No read-modify-write, just shifts the masked value into position.
   * Pattern: target = ((value & mask) << offset)
   *
   * @param target - The register to write
   * @param offset - Starting bit position (0-indexed)
   * @param width - Number of bits to write (see `multiBitWrite`)
   * @param value - Value to write, masked as one operand
   * @param storage - The target's C type, when known
   * @returns C code string for the assignment
   */
  static writeOnlyMultiBit(
    target: string,
    offset: string | number,
    width: number | IBitWidth,
    value: string,
    storage?: string,
  ): string {
    const mask = BitUtils.shiftedMask(BitUtils.widthOf(width), storage);
    const cast = BitUtils.narrowCast(storage);
    return `${target} = ${cast}((${CExpression.operand(value)} & ${mask}) << ${offset});`;
  }
 
  /** A writer's width as C: a constant as it is, a written one folded */
  private static widthOf(width: number | IBitWidth): string | number {
    return typeof width === "number" ? width : BitUtils.widthText(width);
  }
 
  /** A mask about to be shifted into `storage`: a computed one already is */
  private static shiftedMask(
    width: string | number,
    storage: string | undefined,
  ): string {
    const mask = BitUtils.generateMask(width, storage);
    return CONSTANT_WIDTH.test(String(width))
      ? BitUtils.widen(mask, storage)
      : mask;
  }
 
  /** An operand shifted into `storage`, in the storage's width (see above) */
  private static widen(operand: string, storage: string | undefined): string {
    if (BitUtils.isUnfixed(storage)) return `(uintmax_t)${operand}`;
    const bits = BitUtils.bitsOf(storage);
    return bits > 16 ? `(uint${bits}_t)${operand}` : operand;
  }
 
  /** A read-modify-write's assignment, cast back as `narrowCast` says */
  private static assign(
    target: string,
    rhs: string,
    storage: string | undefined,
  ): string {
    const cast = BitUtils.narrowCast(storage);
    if (cast === "") {
      return `${target} = ${rhs};`;
    }
    return `${target} = ${cast}(${rhs});`;
  }
 
  /**
   * The Rule 10.3 cast back to storage narrower than 32 bits, to signed
   * storage, or to storage of unfixed width, which is worked in `uintmax_t`;
   * none otherwise
   */
  private static narrowCast(storage: string | undefined): string {
    if (BitUtils.isUnfixed(storage)) return `(${storage})`;
    const bits = BitUtils.bitsOf(storage);
    const signed = storage?.startsWith("int") ?? false;
    return bits > 0 && (bits < 32 || signed) ? `(${storage})` : "";
  }
 
  /** Storage named by a type whose width the target does not fix */
  private static isUnfixed(storage: string | undefined): boolean {
    return storage !== undefined && !FIXED_WIDTH.test(storage);
  }
 
  /** The storage's width in bits, or 0 when its type is not fixed-width */
  private static bitsOf(storage: string | undefined): number {
    const match = FIXED_WIDTH.exec(storage ?? "");
    return match ? Number(match[1]) : 0;
  }
}
 
export default BitUtils;