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

100% Statements 22/22
100% Branches 18/18
100% Functions 2/2
100% Lines 22/22

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                                                                          14x 7x                                                                 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 union-based type punning
 *
 * Issue #644: Extracted from CodeGenerator to reduce file size.
 * Issue #857: Changed from memcpy to union for MISRA C:2012 Rule 21.15 compliance.
 *
 * Floats don't support direct bit access in C, so we use a union for type punning.
 * The pattern:
 *   1. Declare union variable if needed: union { float f; uint32_t u; } __bits_name;
 *   2. Copy float to union: __bits_name.f = floatVar;
 *   3. Modify bits via union member: __bits_name.u = ...
 *   4. Copy back: floatVar = __bits_name.f;
 *
 * This approach is MISRA-compliant because union type punning is well-defined in C99+.
 *
 * Migrated to use CodeGenState instead of constructor DI.
 */
 
import TTypeInfo from "../types/TTypeInfo.js";
import TIncludeHeader from "../generators/TIncludeHeader.js";
import CodeGenState from "../../../state/CodeGenState.js";
 
/**
 * Callback types for code generation operations.
 */
interface IFloatBitCallbacks {
  /** 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;
}
 
/**
 * Get the C float type name for a C-Next float type.
 */
const getFloatTypeName = (baseType: string): string => {
  return baseType === "f64" ? "double" : "float";
};
 
/**
 * Generates float bit write operations using union-based type punning.
 *
 * For single bit: width is null, uses bitIndex only
 * For bit range: width is provided, uses bitIndex as start position
 */
class FloatBitHelper {
  /**
   * Generate float bit write using union-based type punning.
   * Returns null if typeInfo is not a float type.
   *
   * Uses union { float f; uint32_t u; } for MISRA 21.15 compliance instead of memcpy.
   *
   * @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
   * @param callbacks - Code generation callbacks
   * @returns Generated C code, or null if not a float type
   */
  static generateFloatBitWrite(
    name: string,
    typeInfo: TTypeInfo,
    bitIndex: string,
    width: string | null,
    value: string,
    callbacks: IFloatBitCallbacks,
  ): string | null {
    const isFloatType =
      typeInfo.baseType === "f32" || typeInfo.baseType === "f64";
    if (!isFloatType) {
      return null;
    }
 
    callbacks.requireInclude("float_static_assert"); // For size verification
 
    const isF64 = typeInfo.baseType === "f64";
    const floatType = getFloatTypeName(typeInfo.baseType);
    const intType = isF64 ? "uint64_t" : "uint32_t";
    const shadowName = `__bits_${name}`;
    const maskSuffix = isF64 ? "ULL" : "U";
 
    // Check if shadow variable needs declaration
    const needsDeclaration = !CodeGenState.floatBitShadows.has(shadowName);
    if (needsDeclaration) {
      CodeGenState.floatBitShadows.add(shadowName);
    }
 
    // Check if shadow already has current value (skip redundant read)
    const shadowIsCurrent = CodeGenState.floatShadowCurrent.has(shadowName);
 
    // Union declaration: union { float f; uint32_t u; } __bits_name;
    const decl = needsDeclaration
      ? `union { ${floatType} f; ${intType} u; } ${shadowName};\n`
      : "";
    // Read from float into union: __bits_name.f = floatVar;
    const readUnion = shadowIsCurrent ? "" : `${shadowName}.f = ${name};\n`;
 
    // Mark shadow as current after this write
    CodeGenState.floatShadowCurrent.add(shadowName);
 
    if (width === null) {
      // Single bit assignment: floatVar[3] <- true
      return (
        `${decl}${readUnion}` +
        `${shadowName}.u = (${shadowName}.u & ~(1${maskSuffix} << ${bitIndex})) | ((${intType})${callbacks.foldBooleanToInt(value)} << ${bitIndex});\n` +
        `${name} = ${shadowName}.f;`
      );
    } else {
      // Bit range assignment: floatVar[0, 8] <- b0
      const mask = callbacks.generateBitMask(width, isF64);
      return (
        `${decl}${readUnion}` +
        `${shadowName}.u = (${shadowName}.u & ~(${mask} << ${bitIndex})) | (((${intType})${value} & ${mask}) << ${bitIndex});\n` +
        `${name} = ${shadowName}.f;`
      );
    }
  }
}
 
export default FloatBitHelper;