#include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #include #include #endif struct Environment { std::array x87; std::uint32_t mxcsr; }; static_assert(sizeof(Environment)==32 && alignof(Environment)==4 && offsetof(Environment,mxcsr)==28); using SetEnvironment=int(APS5_VABI*)(const Environment*); SetEnvironment setEnvironment; static_assert(sizeof(Environment)==sizeof(GuestFloatingPointEnvironment) && offsetof(Environment,mxcsr)==offsetof(GuestFloatingPointEnvironment,mxcsr)); void Require(bool condition,const char* message) { if(!condition) { std::fprintf(stderr,"fesetenv: %s\n",message); std::abort(); } } Environment Read() { Environment state; asm volatile("fnstenv %0\n\tfldenv %0" : "=m"(state.x87)); state.mxcsr=_mm_getcsr(); return state; } void Write(const Environment& state) { asm volatile("fldenv %0" : : "m"(state.x87) : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)"); _mm_setcsr(state.mxcsr); } bool Same(const Environment& a,const Environment& b) { return a.x87==b.x87 && a.mxcsr==b.mxcsr; } void StateMatrix() { const auto original=Read(); auto clean=original; clean.x87[0]|=0x3f; clean.x87[1]&=~0xffu; clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7); for(unsigned precision:{0u,0x200u,0x300u}) for(unsigned rounding=0;rounding<16;++rounding) for(unsigned x87=0;x87<64;++x87) for(unsigned sse=0;sse<64;++sse) { auto desired=clean; desired.x87[0]=(desired.x87[0]&~0xf00u)|precision|((rounding&3)<<10); desired.x87[1]=(desired.x87[1]&~0x3fu)|x87; desired.mxcsr=(desired.mxcsr&~0x603fu)|((rounding>>2)<<13)|sse; Write(desired); desired=Read(); Write(clean); const auto input=desired; *__error_nid_postfix()=123; const int result=setEnvironment(&desired); const auto after=Read(); Write(clean); Require(result==0 && *__error_nid_postfix()==123,"matrix return and errno"); Require(Same(after,input),"x87 controls/status/tags/pointers and independent MXCSR restored"); Require(std::memcmp(&desired,&input,sizeof(input))==0,"input unchanged"); } for(unsigned x87Mask=0;x87Mask<64;++x87Mask) for(unsigned sseMask=0;sseMask<64;++sseMask) { auto desired=clean; desired.x87[0]=(desired.x87[0]&~0x3fu)|x87Mask; desired.mxcsr=(desired.mxcsr&~(0x3fu<<7))|(sseMask<<7); Require(setEnvironment(&desired)==0,"independent exception-mask return"); const auto after=Read(); Write(clean); Require(Same(after,desired),"independent x87/SSE exception masks restored"); } alignas(16) std::array extended{}; asm volatile("fxsave %0" : "=m"(extended)); std::uint32_t supported; std::memcpy(&supported,extended.data()+28,sizeof(supported)); if(supported==0) supported=0xffbf; for(unsigned mode:{0u,0x40u,0x8000u,0x8040u}) { if((mode&~supported)!=0) continue; auto desired=clean; desired.mxcsr=(desired.mxcsr&~0x8040u)|mode; Require(setEnvironment(&desired)==0,"subnormal control return"); const auto after=Read(); Write(clean); Require(Same(after,desired),"supported DAZ/FTZ restored"); } Write(original); } void TagsAndPointers() { const auto original=Read(); asm volatile("fninit\n\tfld1\n\tfldpi" : : : "st","st(1)"); const auto desired=Read(); asm volatile("fninit" : : : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)"); const int result=setEnvironment(&desired); const auto after=Read(); Write(original); Require(result==0 && Same(after,desired),"nonempty x87 tag/TOP and instruction pointer restored"); Environment saved; Require(fegetenv_nid_postfix(reinterpret_cast(&saved))==0,"save using existing guest fegetenv"); asm volatile("fninit" : : : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)"); Require(setEnvironment(&saved)==0,"restore guest fegetenv output"); const auto restored=Read(); Write(original); Require(Same(saved,restored),"guest fegetenv round trip"); } void BoundsAndErrors() { const auto original=Read(); #ifdef _WIN32 SYSTEM_INFO info{}; GetSystemInfo(&info); const auto page=static_cast(info.dwPageSize); auto* memory=static_cast(VirtualAlloc(nullptr,2*page,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE)); Require(memory!=nullptr,"allocate guard pages"); DWORD previous; Require(VirtualProtect(memory+page,page,PAGE_NOACCESS,&previous)!=0,"protect trailing page"); #else const auto page=static_cast(sysconf(_SC_PAGESIZE)); auto* memory=static_cast(mmap(nullptr,2*page,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0)); Require(memory!=MAP_FAILED,"allocate guard pages"); Require(mprotect(memory+page,page,PROT_NONE)==0,"protect trailing page"); #endif auto* end=reinterpret_cast(memory+page-sizeof(Environment)); std::memcpy(end,&original,sizeof(original)); #ifdef _WIN32 Require(VirtualProtect(memory,page,PAGE_READONLY,&previous)!=0,"input read-only"); #else Require(mprotect(memory,page,PROT_READ)==0,"input read-only"); #endif const int result=setEnvironment(end); const auto after=Read(); Require(result==0 && Same(after,original),"exact32-byte read at guard-page boundary"); #ifdef _WIN32 Require(VirtualFree(memory,0,MEM_RELEASE)!=0,"release guard pages"); #else Require(munmap(memory,2*page)==0,"release guard pages"); #endif bool rejected=false; *__error_nid_postfix()=123; try { setEnvironment(nullptr); } catch(const std::invalid_argument&) { rejected=true; } Require(rejected && Same(Read(),original) && *__error_nid_postfix()==123,"null rejected without state or errno change"); alignas(16) std::array extended; asm volatile("fxsave %0" : "=m"(extended)); const auto supported=extended[7] ? extended[7] : 0xffbfu; for(unsigned bit=0;bit<32;++bit) { const auto reserved=1u<(flags); const int result=setEnvironment(&desired); const int error=*__error_nid_postfix(); ready.arrive_and_wait(); const auto after=Read(); Write(original); Require(result==0 && Same(after,desired),"thread-local environment"); Require(error==static_cast(flags),"thread-local errno"); }; std::thread a(worker,1,4),b(worker,2,8); a.join(); b.join(); } volatile std::sig_atomic_t returned=0; #ifdef _WIN32 DWORD expectedException; unsigned expectedFlag; LONG CALLBACK Trap(EXCEPTION_POINTERS* exception) { const auto code=exception->ExceptionRecord->ExceptionCode; const auto& state=exception->ContextRecord->FltSave; const auto active=state.StatusWord & ~state.ControlWord & 0x3f; const bool denormalCode=expectedFlag==2 && code==EXCEPTION_FLT_INVALID_OPERATION; if(returned && (code==expectedException || denormalCode) && active==expectedFlag) std::_Exit(0); return EXCEPTION_CONTINUE_SEARCH; } #else void Trap(int) { std::_Exit(returned ? 0 : 1); } #endif void Pending(unsigned bit) { Require(bit<6,"pending exception bit"); #ifdef _WIN32 constexpr DWORD codes[]{EXCEPTION_FLT_INVALID_OPERATION,EXCEPTION_FLT_DENORMAL_OPERAND,EXCEPTION_FLT_DIVIDE_BY_ZERO,EXCEPTION_FLT_OVERFLOW,EXCEPTION_FLT_UNDERFLOW,EXCEPTION_FLT_INEXACT_RESULT}; expectedException=codes[bit]; expectedFlag=1u<(GetProcAddress(library,"fesetenv_nid_postfix")); #else const auto library=dlopen(argv[1],RTLD_NOW); Require(library!=nullptr,"load libc"); setEnvironment=reinterpret_cast(dlsym(library,"fesetenv_nid_postfix")); #endif Require(setEnvironment!=nullptr,"fesetenv export is missing"); if(argc==3) Pending(static_cast(std::atoi(argv[2]))); StateMatrix(); TagsAndPointers(); BoundsAndErrors(); Threads(); #ifdef _WIN32 Require(FreeLibrary(library)!=0,"release lookup handle"); #else Require(dlclose(library)==0,"release lookup handle"); #endif std::puts("fesetenv checks passed:196608 state combinations,4096 mask pairs, bounds/errors/tags/threads"); }