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 | 18x 18x 10x 18x 18x 5x 5x 5x 13x 13x 13x 36x 36x 36x 36x 36x 36x 36x 4x 4x 32x 32x 52x 52x 52x 52x | /**
* Assignment Handler Utilities
*
* Common utilities shared across assignment handlers to reduce duplication.
* Issue #707: Extracted from RegisterHandlers.ts and AccessPatternHandlers.ts.
*/
import IRegisterNameResult from "./IRegisterNameResult";
import QualifiedCName from "../../../../../utils/QualifiedCName";
import invariant from "../../../../../utils/invariant";
import QualifiedNameGenerator from "../../../../../utils/QualifiedNameGenerator";
import BitUtils from "../../../../../utils/BitUtils";
import type IAssignmentContext from "../../../../2-Plan/types/IAssignmentContext";
/**
* Validate that compound assignment operators are not used with bit field access.
*
* @param isCompound - Whether this is a compound assignment
* @param cnextOp - The C-Next operator being used
* @throws Error if compound operator is used with bit fields
*/
// #1322: `validateNoCompoundForBitAccess` is gone. Compound assignment on a
// bit index, bit range, slice or string is E0857 in pass 2.1 -- one decision
// where this was six throws with four message variants, and where this very
// helper was defined a second time, verbatim, in `BitAccessHandlers`.
/**
* A write-1 register bit is never assigned a zero here.
*
* #1322: E0872 rejects it in pass 2.1 (ADR-004), by VALUE -- `0x0` and a
* zero-valued const included, which the text comparison below let through and
* turned into a SET of the bit the author meant to clear. The generated form
* for a single bit is `REG = (1U << bit)`, so a zero reaching this point would
* be emitted as a set; the assertion holds the emission to the rule.
*
* @param value - The generated value being assigned
* @param targetName - The full register name, for the assertion's text
* @param bitIndex - The bit index expression, for the assertion's text
* @param isSingleBit - True for single bit access, false for bit range
*/
function validateWriteOnlyValue(
value: string,
targetName: string,
bitIndex: string,
isSingleBit: boolean,
): void {
const zero = isSingleBit ? value === "false" || value === "0" : value === "0";
invariant(
!zero,
`a write-1 register bit takes a non-zero value -- E0872 rejects ` +
`'${value}' on ${targetName}[${bitIndex}] in pass 2.1, before this runs`,
);
}
/**
* Build a scoped register name from scope and identifier parts.
*
* @param scopeName - The scope name prefix
* @param parts - The identifier parts (register name, member name)
* @returns The full scoped register name (e.g., "Scope_Register_Member")
*/
function buildScopedRegisterName(
declaringScopePath: string,
parts: readonly string[],
): string {
// #1285: the accumulator loop was a hand-rolled join -- each turn fed the
// PREVIOUS result back in as if it were a scope, which is why `forMember` had to
// accept an arbitrary string. The scope qualifies the head; the remaining parts
// are register/member components joined textually.
return QualifiedCName.fromParts([
QualifiedNameGenerator.forMember(declaringScopePath, parts[0]),
...parts.slice(1),
]);
}
/**
* Build register name with automatic scope detection.
*
* @param identifiers - The identifier chain
* @param isKnownScope - Function to check if an identifier is a known scope
* @returns Object with fullName, regName, and isScoped flag
*/
function buildRegisterNameWithScopeDetection(
identifiers: readonly string[],
isKnownScope: (name: string) => boolean,
): IRegisterNameResult {
const leadingId = identifiers[0];
if (isKnownScope(leadingId) && identifiers.length >= 3) {
// Scoped: Scope.Register.Member
const regName = QualifiedCName.fromParts([leadingId, identifiers[1]]);
const fullName = QualifiedCName.fromParts([regName, identifiers[2]]);
return { fullName, regName, isScoped: true };
} else {
// Non-scoped: Register.Member
const regName = leadingId;
const fullName = QualifiedCName.fromParts([leadingId, identifiers[1]]);
return { fullName, regName, isScoped: false };
}
}
/**
* The one bit write: the target without its final subscript, rendered by
* the target renderer, and that subscript's bit or bit range (#1668 review).
*
* Five handlers and the member-chain one each rebuilt the base from the
* source spelling, so a local renamed `f__gs` was written as the global
* `gs`, and each picked the mask's width from a type NAME only `u64`/`i64`
* matched: a header's `uint64_t` or a `u64` struct field got `1U << 40`,
* undefined behavior. The width is the typer's now, for the value the
* subscript indexes, whatever its spelling -- and the MISRA C:2012 Rule
* 10.3 narrowing cast comes with it for every form, not only two.
* `BitUtils` takes it as the C type the value is stored in, which is what a
* bitmap, a register member and a float's bits give it too.
*
* #1760 review: the value's category decides the write, once, for every
* target. A float's bits go through a union, an element's and a field's too
* (they fell through to a plain subscript store, and a variable was the only
* float this reached). An integer the target gives no width, such as a
* header's `int_fast16_t`, is written in `uintmax_t` by its own type name
* (owner ruling), where a 32-bit mask cleared the upper half of a `long`.
*/
function writeBits(ctx: IAssignmentContext): string {
const last = ctx.postfixOps.at(-1);
invariant(
last?.kind === "subscript",
"a bit write's target ends in a subscript: the classifier routed it here",
);
// Source order: the base's own subscripts, then the bit's
const base = ctx.renderBitTarget();
const [start, widthText] = last.renderIndexes();
// Every writer takes the width with its fold (#1096): the float branch
// passed it unfolded, and masked a runtime `1U << 32` at full width
const width =
widthText === undefined
? undefined
: { text: widthText, folded: last.foldWidth() };
const value = ctx.target.last?.before ?? null;
if (value?.category === "floating") {
invariant(value.typeName !== null, "the typer names a float's type");
return ctx.state.requireGenerator().generateFloatBitWrite({
target: base,
floatType: value.typeName,
bitIndex: start,
width: width ?? null,
value: ctx.generatedValue,
isVariable: ctx.postfixOps.length === 1,
});
}
const storage = BitUtils.storageOf(value);
return width === undefined
? BitUtils.singleBitWrite(base, start, ctx.generatedValue, storage)
: BitUtils.multiBitWrite(base, start, width, ctx.generatedValue, storage);
}
/**
* Assignment Handler Utilities
*/
class AssignmentHandlerUtils {
static readonly writeBits = writeBits;
static readonly validateWriteOnlyValue = validateWriteOnlyValue;
static readonly buildScopedRegisterName = buildScopedRegisterName;
static readonly buildRegisterNameWithScopeDetection =
buildRegisterNameWithScopeDetection;
}
export default AssignmentHandlerUtils;
|