All files / transpiler/output/codegen/helpers FloatBitHelper.ts

100% Statements 21/21
100% Branches 16/16
100% Functions 2/2
100% Lines 21/21

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                                                                                                        849x                                           12x 12x 5x     7x 7x   7x 7x 12x 12x     12x 12x 5x       7x   7x 12x         12x   12x   5x             2x 2x                    
/**
 * FloatBitHelper - Generates float bit write operations using shadow variables
 *
 * Issue #644: Extracted from CodeGenerator to reduce file size.
 *
 * Floats don't support direct bit access in C, so we use a shadow integer
 * variable and memcpy to read/write bit values. The pattern:
 *   1. Declare shadow variable if needed
 *   2. memcpy float → shadow (if not already current)
 *   3. Modify shadow bits
 *   4. memcpy shadow → float
 */
 
import TTypeInfo from "../types/TTypeInfo.js";
import TIncludeHeader from "../generators/TIncludeHeader.js";
 
/**
 * State tracking for float bit shadows.
 */
interface IFloatBitState {
  /** Shadow variables already declared in current scope */
  floatBitShadows: Set<string>;
  /** Shadows that currently hold the float's value (skip redundant memcpy read) */
  floatShadowCurrent: Set<string>;
}
 
/**
 * Dependencies required for float bit write generation.
 */
interface IFloatBitHelperDeps {
  /** Whether we're generating C++ code */
  cppMode: boolean;
  /** Float bit shadow tracking state */
  state: IFloatBitState;
  /** Generate a bit mask expression */
  generateBitMask: (width: string, is64Bit?: boolean) => string;
  /** Fold boolean expressions to 0/1 integer */
  foldBooleanToInt: (expr: string) => string;
  /** Request an include header */
  requireInclude: (header: TIncludeHeader) => void;
}
 
/**
 * Generates float bit write operations using shadow variables and memcpy.
 *
 * For single bit: width is null, uses bitIndex only
 * For bit range: width is provided, uses bitIndex as start position
 */
class FloatBitHelper {
  private readonly deps: IFloatBitHelperDeps;
 
  constructor(deps: IFloatBitHelperDeps) {
    this.deps = deps;
  }
 
  /**
   * Generate float bit write using shadow variable + memcpy.
   * Returns null if typeInfo is not a float type.
   *
   * @param name - Variable name being written
   * @param typeInfo - Type information for the variable
   * @param bitIndex - Bit index expression (start position)
   * @param width - Bit width expression (null for single bit)
   * @param value - Value to write
   * @returns Generated C code, or null if not a float type
   */
  generateFloatBitWrite(
    name: string,
    typeInfo: TTypeInfo,
    bitIndex: string,
    width: string | null,
    value: string,
  ): string | null {
    const isFloatType =
      typeInfo.baseType === "f32" || typeInfo.baseType === "f64";
    if (!isFloatType) {
      return null;
    }
 
    this.deps.requireInclude("string"); // For memcpy
    this.deps.requireInclude("float_static_assert"); // For size verification
 
    const isF64 = typeInfo.baseType === "f64";
    const shadowType = isF64 ? "uint64_t" : "uint32_t";
    const shadowName = `__bits_${name}`;
    const maskSuffix = isF64 ? "ULL" : "U";
 
    // Check if shadow variable needs declaration
    const needsDeclaration = !this.deps.state.floatBitShadows.has(shadowName);
    if (needsDeclaration) {
      this.deps.state.floatBitShadows.add(shadowName);
    }
 
    // Check if shadow already has current value (skip redundant memcpy read)
    const shadowIsCurrent = this.deps.state.floatShadowCurrent.has(shadowName);
 
    const decl = needsDeclaration ? `${shadowType} ${shadowName}; ` : "";
    const readMemcpy = shadowIsCurrent
      ? ""
      : `memcpy(&${shadowName}, &${name}, sizeof(${name})); `;
 
    // Mark shadow as current after this write
    this.deps.state.floatShadowCurrent.add(shadowName);
 
    if (width === null) {
      // Single bit assignment: floatVar[3] <- true
      return (
        `${decl}${readMemcpy}` +
        `${shadowName} = (${shadowName} & ~(1${maskSuffix} << ${bitIndex})) | ((${shadowType})${this.deps.foldBooleanToInt(value)} << ${bitIndex}); ` +
        `memcpy(&${name}, &${shadowName}, sizeof(${name}));`
      );
    } else {
      // Bit range assignment: floatVar[0, 8] <- b0
      const mask = this.deps.generateBitMask(width, isF64);
      return (
        `${decl}${readMemcpy}` +
        `${shadowName} = (${shadowName} & ~(${mask} << ${bitIndex})) | (((${shadowType})${value} & ${mask}) << ${bitIndex}); ` +
        `memcpy(&${name}, &${shadowName}, sizeof(${name}));`
      );
    }
  }
}
 
export default FloatBitHelper;