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
//! The double→integer casts the three calc dialects perform, in one place.
//!
//! Every integer operation in an EPICS calc engine starts by casting a `double`
//! stack cell to an integer, and the three dialects do **not** use the same
//! cast. Keeping one module as the owner is what stops a cast from being copied
//! between dialects, which is how the port ended up running base's `d2i` inside
//! sCalc and aCalc, neither of which has that macro at all:
//!
//! | dialect | bit / shift ops | MODULO / NINT |
//! |---|---|---|
//! | base `calcPerform.c` | `d2i` / `d2ui` (:324-325, :329-366) | `d2i` (MODULO :176-190, NINT :313-317) since `669a25697` / PR #925 |
//! | sCalc `sCalcPerform.c` | plain `(long)` (:578-631, :725) | `c_long` — `(long)` (MODULO :1121, NINT :730) |
//! | aCalc `aCalcPerform.c` | plain `(int)` (:907, :1355-1357, :1424-1427) | MODULO `c_int` — `(int)` (:661, :685, :711); NINT `c_long` — `(long)` (:839, :1096) |
//!
//! MODULO and NINT share the single `nint`/`imod` owners, but each engine
//! passes its OWN dialect's narrowing — one logic, several narrowings. The C
//! engines have genuinely different integer widths, so the port mirrors each
//! rather than forcing one; sCalc's `(long)` even loses less than base's `d2i`
//! (3e9 survives 64-bit intact). Note aCalc is internally inconsistent: its NINT
//! is `(long)` (64-bit) but its MODULO is `(int)` (32-bit), so the array engine
//! passes `c_long` to `nint` and `c_int` to `imod`. The zero-divisor
//! disposition and the `-1 → 0` guard are uniform (CBUG-A2/A1).
//!
//! `d2i`/`d2ui` are *not* general truncating casts: they route a
//! non-negative double through `epicsUInt32` first, so `3e9` becomes
//! `-1294967296` (bit pattern preserved) instead of overflowing. That
//! reinterpretation is exactly what base wants for a bitwise operand and
//! exactly what C's plain cast does *not* do.
/// C `d2i` (`calcPerform.c:324`):
/// `((x)<0 ? (epicsInt32)(x) : (epicsInt32)(epicsUInt32)(x))`.
///
/// A non-negative double goes through `epicsUInt32`, so the full 32-bit
/// pattern survives and bit 31 lands as the sign bit — `d2i(3e9)` is
/// `-1294967296`, not an overflow. Base uses this for BIT_OR/AND/XOR/NOT, the
/// shifts, and — since `669a25697` / PR #925 — NINT and MODULO too.
pub
/// C `d2ui` (`calcPerform.c:325`):
/// `((x)<0 ? (epicsUInt32)(epicsInt32)(x) : (epicsUInt32)(x))`.
/// Base's logical right shift (`>>>`, RIGHT_SHIFT_LOGIC) is its only user.
pub
/// C's plain `(int)` / `(epicsInt32)` cast of a double.
///
/// In range it truncates toward zero. Out of range (and for NaN/±Inf) the C
/// standard calls it **undefined**, so a compiled C IOC is not single-valued:
/// x86-64's `cvttsd2si` stores the "integer indefinite" `INT32_MIN`, while
/// aarch64's `fcvtzs` saturates to `INT32_MAX` and sends NaN to 0. Since UB is
/// not a contract, the port takes the clean value and **saturates** — see
/// [`crate::types::c_cast`] for the decision (CBUG-E2) and the disassembly.
///
/// Either way it differs from [`d2i`]'s wrap, which is the bug this module's
/// split exists to prevent: `d2i` routes a non-negative double through
/// `epicsUInt32` first, and that reinterpretation *is* well defined for
/// anything inside `u32`.
///
/// The cast itself is not calc's — it is the same bare C cast `dbConvert.c`
/// performs on a `DBF_DOUBLE` → `DBF_LONG` put — so it is owned by
/// [`crate::types::c_cast`] and merely named for calc here.
pub
/// C `myNINT` — `sCalcPerform.c:40` and `aCalcPerform.c:50`, byte-identical:
///
/// ```c
/// #define myNINT(a) ((int)((a) >= 0 ? (a)+0.5 : (a)-0.5))
/// ```
///
/// Round half away from zero, **and then cast to `int`** — the narrowing is
/// INSIDE the macro, which is the whole point of it living here. A caller that
/// takes `myNINT`'s value into a `long` gets an already-narrowed `int`
/// sign-extended, not a fresh 64-bit conversion of the double.
///
/// The port used to have two copies of this and neither narrowed like C: sCalc's
/// returned a `double` (so each of its call sites invented its own narrowing —
/// `as i64` wrapped, `as i32` saturated, and the two disagreed *bitwise* on the
/// same input), and aCalc's used Rust's `as i32`, which saturates where C's
/// `cvttsd2si` yields the indefinite value. One function, one narrowing.
///
/// Compiled (`gcc -O0`, x86-64, runtime operand — a *constant* operand is folded
/// by gcc to `INT32_MAX` instead, which is why this must be probed at runtime):
/// `myNINT(3e9)` = `myNINT(-3e9)` = `myNINT(1e18)` = `myNINT(NaN)` =
/// `-2147483648`; `myNINT(2.5)` = 3; `myNINT(-2.5)` = -3.
pub
/// C's plain `(long)` cast of a double on LP64 (what sCalc's operators use).
/// Same story as [`c_int`], one width up: x86-64 `cvttsd2si` with a 64-bit
/// destination yields `INT64_MIN` for NaN and for anything out of range.
pub
/// `NINT` — round half away from zero, then narrow with the caller's dialect
/// cast (`narrow`). One body, per-dialect narrowing: the numeric engine passes
/// [`d2i`] (base's fixed `calcPerform.c:313-317`, `669a25697`/PR #925), and both
/// sCalc (`sCalcPerform.c:730`) and aCalc (`aCalcPerform.c:839,1096`) pass
/// [`c_long`] — BOTH synApps engines narrow NINT with `(long)`, 64-bit — each
/// mirroring THAT dialect's own C.
///
/// C shape, identical in all three but for the cast:
///
/// ```c
/// top = top >= 0 ? top + 0.5 : top - 0.5;
/// *ptop = <narrow>(top);
/// ```
///
/// So `NINT(3e9)` is `d2i(3e9) = -1294967296` in base (the u32 reinterpretation),
/// but `(long)3e9 = 3000000000` in both sCalc and aCalc (64-bit, no loss).
/// Inside each width's range the three agree and are bit-identical to their C.
pub
/// `MODULO` — narrow both operands with the caller's dialect cast (`narrow`),
/// then take the integer remainder. Returns `None` when the divisor narrows to
/// zero. One body, three narrowings — see [`nint`] for the per-dialect casts.
///
/// C shape, identical in all three engines but for the cast width (base's fixed
/// `calcPerform.c:176-190`; sCalc `sCalcPerform.c:558-563`/`:1102-1110`; aCalc
/// `aCalcPerform.c:645-703`):
///
/// ```c
/// itop = <narrow>(top); /* divisor */
/// if (itop == 0) *ptop = epicsNAN; /* caller's zero rule */
/// else if (itop == -1) *ptop = 0; /* x % -1 == 0, dodges INT_MIN % -1 */
/// else *ptop = <narrow>(*ptop) % itop;
/// ```
///
/// `None` signals the divisor narrowed to 0 so the caller applies its engine's
/// zero-divisor rule (base `NaN`, sCalc error, aCalc `myMAXFLOAT`) — exactly the
/// three arms C's own `case MODULO` splits into. A `-1` divisor returns `0`,
/// both mathematically correct (`x % -1 == 0` for every integer) and the value
/// all three C engines now define (calc PR #38 + base `fd64a84aa`) to avoid the
/// undefined `INT_MIN % -1` that x86 `idiv` traps as SIGFPE.
pub
/// `(epicsInt32)x` where the value is already known to be in `epicsUInt32`
/// range — the tail of `d2i`/`d2ui`, i.e. a modular reduction, NOT a C cast.
/// Private on purpose: an operator that wants a C cast wants [`c_int`].