-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathsoft_float.h
More file actions
281 lines (235 loc) · 8.78 KB
/
Copy pathsoft_float.h
File metadata and controls
281 lines (235 loc) · 8.78 KB
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#pragma once
#include "util/types.h"
/*
* DuetOS — Soft-float runtime (IEEE 754 binary32).
*
* The kernel is compiled with `-mno-sse -mno-sse2` and does not
* link any compiler-rt soft-float helpers, so `float` cannot be
* used in kernel code at all (no scalar SSE, no x87 — the
* scheduler doesn't save the FPU state for the kernel ring). Any
* subsystem that genuinely needs binary32 arithmetic — most
* notably the SPIR-V interpreter that drives the Vulkan ICD's
* programmable rasterizer — has to do the math in pure integer
* code.
*
* This TU implements that. The interface is `Sf32` — an opaque
* struct wrapping a `u32` IEEE 754 bit pattern — plus the small
* set of arithmetic / comparison / conversion entry points the
* shader interpreter needs:
*
* - construction: from sign+exp+mantissa, from u32 bits,
* from i32 / u32 / canonical 0.0 / 1.0 / -1.0 helpers
* - arithmetic: Add, Sub, Mul, Div, Neg, Abs
* - extras for shaders: Sqrt, FMin, FMax, FClamp, FMix, Step
* - comparison: LessThan, GreaterThan, Equal, LessOrEqual,
* GreaterOrEqual, NotEqual (IEEE ordered semantics — NaN
* compares unordered with everything)
* - conversion: ToI32 (truncation, IEEE saturation on overflow),
* ToU32, FromI32, FromU32
*
* Implementation strategy: full-precision integer arithmetic on
* sign / exponent / mantissa fields, with denormals flushed to
* zero on output (the shader workloads don't care about denormal
* preservation and FTZ keeps the code compact). NaN payload is
* not preserved — every NaN result encodes as a canonical
* quiet-NaN bit pattern. Rounding: round to nearest, ties to
* even (the IEEE default).
*
* This is deliberately scoped to what shaders need. It is NOT a
* conformance-grade soft-float library — no denormal arithmetic,
* no signed zero discrimination in comparisons (per IEEE +0 ==
* -0), no FP exception flags (no inexact / overflow / underflow
* sticky bits). If a future caller needs those, they bolt on
* here.
*
* Context: kernel. Header-only types + a flat C++23 namespace.
*/
namespace duetos::core
{
/// IEEE 754 binary32 bit-pattern wrapper.
///
/// Storage is the raw 32-bit encoding: sign in bit 31, exponent
/// in bits 30..23 (biased by 127, with 0 = subnormal/zero and
/// 255 = infinity/NaN), mantissa in bits 22..0 (with an implicit
/// leading 1 for normals).
struct Sf32
{
u32 bits;
};
// --------------------------------------------------------------
// Construction helpers
// --------------------------------------------------------------
/// Encode a single-precision float from its IEEE 754 bit pattern.
/// Useful when the caller already has the encoding (e.g. embedded
/// constants from a SPIR-V OpConstant payload, host-side test
/// vectors). Bit pattern is taken as-is — no normalisation.
constexpr Sf32 Sf32FromBits(u32 bits)
{
return Sf32{bits};
}
/// Convert back to the raw 32-bit encoding.
constexpr u32 Sf32ToBits(Sf32 x)
{
return x.bits;
}
/// Canonical positive zero (0x00000000).
constexpr Sf32 Sf32Zero()
{
return Sf32{0u};
}
/// Canonical 1.0 (0x3F800000).
constexpr Sf32 Sf32One()
{
return Sf32{0x3F800000u};
}
/// Canonical -1.0 (0xBF800000).
constexpr Sf32 Sf32NegOne()
{
return Sf32{0xBF800000u};
}
/// Canonical +infinity (0x7F800000).
constexpr Sf32 Sf32Inf()
{
return Sf32{0x7F800000u};
}
/// Canonical quiet NaN (0x7FC00000 — sign 0, exp all-1s,
/// mantissa MSB set per IEEE 754 quiet-NaN convention).
constexpr Sf32 Sf32QNaN()
{
return Sf32{0x7FC00000u};
}
// --------------------------------------------------------------
// Predicates (introspection without arithmetic)
// --------------------------------------------------------------
constexpr bool Sf32IsZero(Sf32 x)
{
return (x.bits & 0x7FFFFFFFu) == 0u;
}
constexpr bool Sf32IsNaN(Sf32 x)
{
return (x.bits & 0x7F800000u) == 0x7F800000u && (x.bits & 0x007FFFFFu) != 0u;
}
constexpr bool Sf32IsInf(Sf32 x)
{
return (x.bits & 0x7FFFFFFFu) == 0x7F800000u;
}
constexpr bool Sf32IsNegative(Sf32 x)
{
return (x.bits & 0x80000000u) != 0u;
}
// --------------------------------------------------------------
// Arithmetic
// --------------------------------------------------------------
Sf32 Sf32Add(Sf32 a, Sf32 b);
Sf32 Sf32Sub(Sf32 a, Sf32 b);
Sf32 Sf32Mul(Sf32 a, Sf32 b);
Sf32 Sf32Div(Sf32 a, Sf32 b);
/// Negate: flip the sign bit. Works for +0 / -0, NaN preserved.
constexpr Sf32 Sf32Neg(Sf32 x)
{
return Sf32{x.bits ^ 0x80000000u};
}
/// Absolute value: clear the sign bit.
constexpr Sf32 Sf32Abs(Sf32 x)
{
return Sf32{x.bits & 0x7FFFFFFFu};
}
/// Square root (Newton-Raphson, ~24-bit accurate). Returns NaN
/// for negative inputs, +0 for +/-0, +inf for +inf.
Sf32 Sf32Sqrt(Sf32 x);
/// Sine via a 7th-order minimax polynomial after range reduction
/// to [-pi, pi]. Accurate to ~5e-4 over the full real line —
/// good enough for shader use (lighting, animation, procedural
/// patterns) without paying for a full Cephes-grade
/// implementation. NaN in -> NaN out. Inf in -> NaN out (the
/// reduction would produce a non-finite phase).
Sf32 Sf32Sin(Sf32 x);
/// Cosine = Sin(x + pi/2). Same accuracy + special-case
/// handling as Sf32Sin.
Sf32 Sf32Cos(Sf32 x);
/// exp(x) via 2^(x/ln2) and a degree-5 polynomial on the
/// fractional part. NaN-in / NaN-out; saturates to +inf above
/// ~88.7 and to +0 below ~-87.3 (the IEEE 754 binary32 dynamic
/// range).
Sf32 Sf32Exp(Sf32 x);
/// Natural log via a degree-5 polynomial after range reduction
/// to [1, 2). Returns NaN for x <= 0, -inf for x = +0.
Sf32 Sf32Log(Sf32 x);
/// Power: x^y = exp(y * log(x)). For x < 0 or non-integer y
/// this returns NaN (matching IEEE pow semantics for the common
/// shader case where pow is used on already-positive bases).
Sf32 Sf32Pow(Sf32 x, Sf32 y);
/// Floor: largest integer <= x, returned as Sf32. NaN -> NaN.
Sf32 Sf32Floor(Sf32 x);
/// Ceil: smallest integer >= x. NaN -> NaN.
Sf32 Sf32Ceil(Sf32 x);
/// Round to nearest integer, ties to even (banker's rounding).
Sf32 Sf32Round(Sf32 x);
/// Fractional part: x - floor(x). Always in [0, 1). NaN -> NaN.
Sf32 Sf32Fract(Sf32 x);
// --------------------------------------------------------------
// GLSL.std.450 helpers used by shaders
// --------------------------------------------------------------
Sf32 Sf32Min(Sf32 a, Sf32 b);
Sf32 Sf32Max(Sf32 a, Sf32 b);
/// `Sf32Clamp(x, lo, hi) = Sf32Min(Sf32Max(x, lo), hi)`. NaN
/// propagation follows the same chain as the underlying
/// min/max.
inline Sf32 Sf32Clamp(Sf32 x, Sf32 lo, Sf32 hi)
{
return Sf32Min(Sf32Max(x, lo), hi);
}
/// Linear blend: `a*(1-t) + b*t`. Standard GLSL `mix`.
Sf32 Sf32Mix(Sf32 a, Sf32 b, Sf32 t);
/// GLSL step: 0.0 if x < edge, 1.0 otherwise. NaN inputs yield
/// 0.0 (consistent with GLSL implementation-defined behaviour
/// when the comparison is unordered).
Sf32 Sf32Step(Sf32 edge, Sf32 x);
// --------------------------------------------------------------
// Comparison (IEEE ordered — NaN compares unordered)
// --------------------------------------------------------------
bool Sf32LessThan(Sf32 a, Sf32 b);
bool Sf32GreaterThan(Sf32 a, Sf32 b);
bool Sf32Equal(Sf32 a, Sf32 b);
inline bool Sf32LessOrEqual(Sf32 a, Sf32 b)
{
return Sf32LessThan(a, b) || Sf32Equal(a, b);
}
inline bool Sf32GreaterOrEqual(Sf32 a, Sf32 b)
{
return Sf32GreaterThan(a, b) || Sf32Equal(a, b);
}
inline bool Sf32NotEqual(Sf32 a, Sf32 b)
{
// IEEE NotEqual is the *unordered* form: true if a != b OR
// either is NaN. Shaders use OpFOrdNotEqual (ordered) and
// OpFUnordNotEqual (unordered) separately; the interpreter
// calls one of these from the helper.
return !Sf32Equal(a, b);
}
// --------------------------------------------------------------
// Conversions
// --------------------------------------------------------------
/// Truncate toward zero. Saturates: NaN -> 0, |x| > INT32_MAX
/// pins to INT32_MAX or INT32_MIN per IEEE saturation.
i32 Sf32ToI32(Sf32 x);
/// Same shape as ToI32 but for unsigned. NaN -> 0, negative -> 0,
/// overflow -> UINT32_MAX.
u32 Sf32ToU32(Sf32 x);
/// Convert from a signed 32-bit integer. Always exact for values
/// representable as binary32; values whose magnitude exceeds 2^24
/// round to nearest.
Sf32 Sf32FromI32(i32 x);
/// Convert from an unsigned 32-bit integer. Same rounding rules
/// as the signed version.
Sf32 Sf32FromU32(u32 x);
// --------------------------------------------------------------
// Boot self-test (panic on regression).
// --------------------------------------------------------------
/// Walks a curated set of arithmetic / comparison / conversion
/// vectors. Panics if any case diverges from the expected
/// result. Wired into `boot_bringup.cpp` behind
/// `DUETOS_BOOT_SELFTEST` so it runs once at boot and produces
/// the structural sentinel `[util/soft_float] self-test PASS`.
void Sf32SelfTest();
} // namespace duetos::core