9#if defined(_M_X64) || defined(__x86_64__)
29 std::uint64_t control, status;
64 std::thread::id owner_;
83 [[nodiscard]]
bool controls_match() const noexcept;
93 std::thread::id owner_;
113#if defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm64__)
120#if defined(_M_X64) || defined(__x86_64__)
121 auto word = _mm_getcsr();
122 return {word & ~63u, word & 63u};
123#elif defined(__aarch64__) || defined(__arm64__)
125 __asm__
volatile(
"mrs %0, fpcr" :
"=r"(state.control) : :
"memory");
126 __asm__
volatile(
"mrs %0, fpsr" :
"=r"(state.status) : :
"memory");
132 namespace detail::fp32_environment {
133 [[noreturn]]
void failure(
char const * reason,
bool invalid =
false) {
134#if defined(__cpp_exceptions) || defined(_CPPUNWIND)
135 if (invalid)
throw std::invalid_argument(reason);
136 throw std::runtime_error(reason);
139 std::fputs(reason, stderr);
140 std::fputc(
'\n', stderr);
144 void write_native_fp_state(native_fp_state state)
noexcept {
145#if defined(_M_X64) || defined(__x86_64__)
146 _mm_setcsr(
static_cast<unsigned int>(state.control | state.status));
147#elif defined(__aarch64__) || defined(__arm64__)
148 __asm__
volatile(
"msr fpcr, %0\n\tisb" : :
"r"(state.control) :
"memory");
149 __asm__
volatile(
"msr fpsr, %0" : :
"r"(state.status) :
"memory");
154 native_fp_state requested_state(native_fp_state saved,
native_fp32_mode mode) {
156 failure(
"Native FP32 environment unavailable; use software arithmetic policy");
157 if (mode != native_fp32_mode::gradual && mode != native_fp32_mode::flush)
158 failure(
"Invalid native FP32 mode",
true);
159#if defined(_M_X64) || defined(__x86_64__)
161 auto control = (saved.control & ~std::uint64_t(0xe040)) | 0x1f80u;
162 if (mode == native_fp32_mode::flush) control |= 0x8040u;
164#elif defined(__aarch64__) || defined(__arm64__)
167 constexpr std::uint64_t clear = 7u | (0x1full << 8) | (1ull << 15) |
168 (3ull << 22) | (1ull << 24);
169 auto control = saved.control & ~clear;
170 if (mode == native_fp32_mode::flush) control |= 1ull << 24;
179 (mode != native_fp32_mode::gradual && mode != native_fp32_mode::flush))
return false;
181 detail::fp32_environment::write_native_fp_state(requested);
185 : previous_(
read_native_fp_state()), requested_(detail::fp32_environment::requested_state(previous_, mode)),
186 owner_(std::this_thread::get_id()) {
187 detail::fp32_environment::write_native_fp_state(requested_);
189 detail::fp32_environment::write_native_fp_state(previous_);
190 detail::fp32_environment::failure(
"Native FP32 environment did not accept requested controls");
194 assert(owner_ == std::this_thread::get_id());
195 detail::fp32_environment::write_native_fp_state(previous_);
198 return owner_ == std::this_thread::get_id() &&
read_native_fp_state().control == requested_.control;
202 assert(region.owner_ == owner_);
203 detail::fp32_environment::write_native_fp_state(region.previous_);
206 assert(owner_ == std::this_thread::get_id());
207 detail::fp32_environment::write_native_fp_state(previous_);
217export namespace ftz {
221 none, environment_unavailable, normal_arithmetic, core_contract, hardware_ftz
231 bool normal_rne =
false, fused_fma =
false, separate_rounding =
false;
232 bool signed_input_ftz =
false, signed_output_ftz =
false, core_exact =
false;
233 bool explicit_core_exact =
false, controls_stable =
false;
236 [[nodiscard]]
bool admitted() const noexcept {
return failure == ftz32_cpu_failure::none; }
239 return normal_rne && fused_fma && separate_rounding && explicit_core_exact && controls_stable;
244export namespace ftz::detail {
245 struct ftz32_cpu_witness {
unsigned int operation, a, b, c, expected; };
246 enum class ftz32_cpu_path { raw, compiled_core, explicit_core };
247 using ftz32_cpu_evaluator =
unsigned int (*)(ftz32_cpu_witness, ftz32_cpu_path);
248 [[nodiscard]] ftz32_cpu_admission ftz32_cpu_probe(ftz32_cpu_evaluator evaluate,
bool hardware_ftz);
250namespace ftz::detail {
254 inline constexpr std::array ftz32_normal_witnesses {
255 ftz32_cpu_witness{0,0x3f800000u,0x33800000u,0u,0x3f800000u},
256 ftz32_cpu_witness{0,0xbf800000u,0xb3800000u,0u,0xbf800000u},
257 ftz32_cpu_witness{0,0x3f800000u,0x34400000u,0u,0x3f800002u},
258 ftz32_cpu_witness{0,0xbf800000u,0xb4400000u,0u,0xbf800002u},
259 ftz32_cpu_witness{1,0x3f800001u,0x3f800001u,0u,0x3f800002u},
260 ftz32_cpu_witness{1,0xbf800001u,0x3f800001u,0u,0xbf800002u}
262 inline constexpr std::array ftz32_fusion_witnesses {
263 ftz32_cpu_witness{2,0x3f800001u,0x3f7ffffeu,0xbf800000u,0xa8800000u},
264 ftz32_cpu_witness{2,0xbf800001u,0x3f7ffffeu,0x3f800000u,0x28800000u},
265 ftz32_cpu_witness{3,0x3f800001u,0x3f7ffffeu,0xbf800000u,0u}
267 inline constexpr std::array ftz32_flush_witnesses {
268 ftz32_cpu_witness{1,1u,0x7e800000u,0u,0u},
269 ftz32_cpu_witness{1,0x80000001u,0x7e800000u,0u,0x80000000u},
270 ftz32_cpu_witness{2,1u,0x7e800000u,0u,0u},
271 ftz32_cpu_witness{2,0x80000001u,0x7e800000u,0x80000000u,0x80000000u},
272 ftz32_cpu_witness{1,0x00800000u,0x3f000000u,0u,0u},
273 ftz32_cpu_witness{1,0x80800000u,0x3f000000u,0u,0x80000000u},
274 ftz32_cpu_witness{2,0x00800000u,0x3f000000u,0u,0u},
275 ftz32_cpu_witness{2,0x80800000u,0x3f000000u,0x80000000u,0x80000000u},
276 ftz32_cpu_witness{0,0x00800000u,0x80800001u,0u,0x80000000u},
277 ftz32_cpu_witness{0,0x80800000u,0x00800001u,0u,0u}
279 inline constexpr std::array ftz32_core_witnesses {
280 ftz32_cpu_witness{1,0x00800000u,0x3f7fffffu,0u,0x00800000u},
281 ftz32_cpu_witness{1,0x80800000u,0x3f7fffffu,0u,0x80800000u},
282 ftz32_cpu_witness{1,0x20000001u,0x1ffffffeu,0u,0x00800000u},
283 ftz32_cpu_witness{1,0xa0000001u,0x1ffffffeu,0u,0x80800000u},
284 ftz32_cpu_witness{2,0x00800000u,0x3f7fffffu,0u,0x00800000u},
285 ftz32_cpu_witness{2,0x80800000u,0x3f7fffffu,0x80000000u,0x80800000u},
286 ftz32_cpu_witness{2,0x20000001u,0x1ffffffeu,0u,0x00800000u},
287 ftz32_cpu_witness{2,0xa0000001u,0x1ffffffeu,0x80000000u,0x80800000u},
288 ftz32_cpu_witness{1,0x00800000u,0x3f7ffffeu,0u,0u},
289 ftz32_cpu_witness{1,0x80800000u,0x3f7ffffeu,0u,0x80000000u},
290 ftz32_cpu_witness{0,0x00800000u,0x80800001u,0u,0x80000000u},
291 ftz32_cpu_witness{0,0x80800000u,0x00800001u,0u,0u},
292 ftz32_cpu_witness{3,0x00800000u,0x3f800000u,0x80800001u,0x80000000u},
293 ftz32_cpu_witness{3,0x80800000u,0x3f800000u,0x00800001u,0u},
294 ftz32_cpu_witness{4,1u,0u,0u,0u},
295 ftz32_cpu_witness{4,0x807fffffu,0u,0u,0x80000000u},
296 ftz32_cpu_witness{4,0xff800001u,0u,0u,0x7fc00000u},
297 ftz32_cpu_witness{4,0x00800000u,0u,0u,0x00800000u}
299 struct ftz32_cpu_observation {
300 bool available =
false, exception_masks =
false, rounding_controls =
false;
301 ftz::native_fp_state before{}, after{};
302 std::array<
unsigned int, ftz32_normal_witnesses.size()> normal{};
303 std::array<
unsigned int, ftz32_fusion_witnesses.size()> fusion{};
304 std::array<
unsigned int, ftz32_flush_witnesses.size()> flush{};
305 std::array<
unsigned int, ftz32_core_witnesses.size()> core{}, explicit_core{};
308 template<std::
size_t N>
inline bool ftz32_cpu_matches(
309 std::array<unsigned int,N>
const & actual, std::array<ftz32_cpu_witness,N>
const & rows,
310 std::size_t first = 0, std::size_t end = N)
noexcept {
311 for (
auto i = first; i < end; ++i) {
312 unsigned int expected = rows[i].expected;
315 if (actual[i] != expected && !((actual[i] & 0x7fffffffu) > 0x7f800000u && (expected & 0x7fffffffu) > 0x7f800000u))
return false;
319 ftz32_cpu_admission ftz32_cpu_classify(ftz32_cpu_observation
const & o,
bool hardware_ftz)
noexcept {
320 ftz32_cpu_admission r;
321 r.before=o.before; r.after=o.after;
322 if (!o.available)
return r;
323 r.controls_stable=o.before.control==o.after.control;
324 r.normal_rne=o.exception_masks && o.rounding_controls && ftz32_cpu_matches(o.normal,ftz32_normal_witnesses);
325 r.fused_fma=ftz32_cpu_matches(o.fusion,ftz32_fusion_witnesses,0,2);
326 r.separate_rounding=ftz32_cpu_matches(o.fusion,ftz32_fusion_witnesses,2,3);
327 r.signed_input_ftz=ftz32_cpu_matches(o.flush,ftz32_flush_witnesses,0,4);
328 r.signed_output_ftz=ftz32_cpu_matches(o.flush,ftz32_flush_witnesses,4);
329 r.core_exact=ftz32_cpu_matches(o.core,ftz32_core_witnesses);
330 r.explicit_core_exact=ftz32_cpu_matches(o.explicit_core,ftz32_core_witnesses);
331 bool hardware_missing =
false;
333 hardware_missing = !r.signed_input_ftz || !r.signed_output_ftz;
334 if (!r.normal_rne || !r.fused_fma || !r.separate_rounding || !r.controls_stable)
335 r.failure=ftz32_cpu_failure::normal_arithmetic;
336 else if (hardware_missing)
337 r.failure=ftz32_cpu_failure::hardware_ftz;
338 else if (!r.core_exact) r.failure=ftz32_cpu_failure::core_contract;
339 else r.failure=ftz32_cpu_failure::none;
342 ftz32_cpu_observation ftz32_cpu_observe(ftz32_cpu_evaluator evaluate) {
343 ftz32_cpu_observation o;
346#if defined(_M_X64) || defined(__x86_64__)
347 o.exception_masks=(o.before.control & 0x1f80u)==0x1f80u;
348 o.rounding_controls=(o.before.control & 0x6000u)==0u;
349#elif defined(__aarch64__) || defined(__arm64__)
350 o.exception_masks=(o.before.control & ((0x1full<<8)|(1ull<<15)))==0u;
351 o.rounding_controls=(o.before.control & ((3ull<<22)|7u))==0u;
355 if (o.available && o.exception_masks) {
356 for (std::size_t i=0;i<o.normal.size();++i) o.normal[i]=evaluate(ftz32_normal_witnesses[i],ftz32_cpu_path::raw);
357 for (std::size_t i=0;i<o.fusion.size();++i) o.fusion[i]=evaluate(ftz32_fusion_witnesses[i],ftz32_cpu_path::raw);
358 for (std::size_t i=0;i<o.flush.size();++i) o.flush[i]=evaluate(ftz32_flush_witnesses[i],ftz32_cpu_path::raw);
359 for (std::size_t i=0;i<o.core.size();++i) {
360 o.core[i]=evaluate(ftz32_core_witnesses[i],ftz32_cpu_path::compiled_core);
361 o.explicit_core[i]=evaluate(ftz32_core_witnesses[i],ftz32_cpu_path::explicit_core);
367 ftz32_cpu_admission ftz32_cpu_probe(ftz32_cpu_evaluator evaluate,
bool hardware_ftz) {
368 return ftz32_cpu_classify(ftz32_cpu_observe(evaluate), hardware_ftz);
bool native_fp32_environment_available() noexcept
Reports whether this build can access native CPU FP controls; this is not numerical admission.
ftz32_cpu_failure
Identifies the first rejected admission requirement.
bool set_native_fp32_mode(native_fp32_mode mode) noexcept
Initializes an application-owned numerical thread, without restoration or admission.
native_fp32_mode
Selects gradual denormals or signed native input/output flushing.
native_fp_state read_native_fp_state() noexcept
Reads this thread's controls and status without changing them; unsupported hosts return zero fields.
Results of finite startup witnesses for this scalar compilation and calling thread....
bool admitted() const noexcept
Reports whether every requirement of the requested policy passed.
bool explicit_compatible() const noexcept
Reports explicit-policy compatibility without requiring hardware signed flushing.
external_scope(external_scope &&)=delete
Disallows transfer of a guard whose restoration belongs to its constructing thread.
external_scope & operator=(external_scope &&)=delete
Disallows transfer of a guard whose restoration belongs to its constructing thread.
~external_scope() noexcept
Restores the numerical state captured by this external-call guard.
external_scope(external_scope const &)=delete
Disallows transfer of a guard whose restoration belongs to its constructing thread.
external_scope(native_fp32_scope const ®ion) noexcept
Saves the current state, then restores region.previous() on the same thread.
external_scope & operator=(external_scope const &)=delete
Disallows transfer of a guard whose restoration belongs to its constructing thread.
native_fp32_scope & operator=(native_fp32_scope const &)=delete
Disallows transfer of a guard whose restoration belongs to its constructing thread.
native_fp_state requested() const noexcept
Returns the controls and initially clear status requested by construction.
native_fp32_scope(native_fp32_mode mode)
Saves the current thread state and establishes the requested numerical region.
native_fp32_scope & operator=(native_fp32_scope &&)=delete
Disallows transfer of a guard whose restoration belongs to its constructing thread.
~native_fp32_scope() noexcept
Restores the complete saved state on the constructing thread.
native_fp32_scope(native_fp32_scope const &)=delete
Disallows transfer of a guard whose restoration belongs to its constructing thread.
native_fp32_scope(native_fp32_scope &&)=delete
Disallows transfer of a guard whose restoration belongs to its constructing thread.
native_fp_state previous() const noexcept
Returns the state captured before this numerical region.
bool controls_match() const noexcept
Checks thread identity and controls; accrued exception status does not cause failure.
Opaque native control/status snapshot, suitable for exact restoration checks.
friend bool operator==(native_fp_state const &, native_fp_state const &)=default
Compares both control and status words exactly.