#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 && offsetof(Environment,mxcsr)==28); using UpdateEnvironment=int (APS5_VABI*)(const Environment*); UpdateEnvironment updateEnvironment; void Require(bool condition,const char* message) { if(!condition) { std::fprintf(stderr,"feupdateenv: %s\n",message); std::exit(1); } } Environment Read() { Environment result; asm volatile("fnstenv %0\n\tfldcw %0" : "=m"(result.x87)); result.mxcsr=_mm_getcsr(); return result; } void Write(const Environment& value) { asm volatile("fldenv %0" : : "m"(value.x87) : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)"); _mm_setcsr(value.mxcsr); } bool Same(const Environment& a,const Environment& b){return a.x87==b.x87 && a.mxcsr==b.mxcsr;} void Matrix() { const auto original=Read(); auto clean=original; clean.x87[0]|=0x3f; clean.x87[1]&=~0xffu; clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7); unsigned cases=0; for(unsigned x87=0;x87<64;++x87) for(unsigned sse=0;sse<64;++sse) for(unsigned pattern=0;pattern<4;++pattern) for(unsigned rounding=0;rounding<16;++rounding) { auto saved=clean; constexpr unsigned precisions[]{0u,0x200u,0x300u,0u}; saved.x87[0]=(saved.x87[0]&~0x300u)|precisions[pattern]; saved.x87[0]=(saved.x87[0]&~0xc00u)|((rounding&3)<<10); saved.mxcsr=(saved.mxcsr&~0x6000u)|((rounding>>2)<<13); const unsigned savedX87=pattern==0?0:pattern==1?0x15:pattern==2?0x2a:0x3f; const unsigned savedSse=pattern==0?0x3f:pattern==1?0x2a:pattern==2?0x15:0; saved.x87[1]|=savedX87; saved.mxcsr|=savedSse; Write(saved); saved=Read(); auto pending=clean; pending.x87[1]|=x87; pending.mxcsr|=sse; Write(pending); const auto input=saved; *__error_nid_postfix()=123; const int result=updateEnvironment(&saved); const auto after=Read(); const int error=*__error_nid_postfix(); Write(clean); auto expected=input; expected.x87[1]|=x87|sse; expected.mxcsr|=x87|sse; Require(result==0 && error==123,"return/errno"); Require(after.x87==expected.x87 && after.mxcsr==expected.mxcsr,"restore saved controls and raise old union in both units"); Require(std::memcmp(&input,&saved,sizeof(input))==0,"input unchanged"); ++cases; } Write(original); std::printf("feupdateenv: %u flag/rounding combinations passed\n",cases); } void TagsAndPointers() { const auto entry=Read(); auto clean=entry; clean.x87[0]|=0x3f; clean.x87[1]&=~0xffu; clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7); Write(clean); 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=updateEnvironment(&desired); const auto after=Read(); Write(original); Require(result==0 && Same(after,desired),"nonempty x87 tag/TOP and instruction pointer restored"); GuestFloatingPointEnvironment guestSaved; Require(fegetenv_nid_postfix(&guestSaved)==0,"save using existing guest fegetenv"); asm volatile("fninit" : : : "st","st(1)","st(2)","st(3)","st(4)","st(5)","st(6)","st(7)"); Environment saved; std::memcpy(&saved, &guestSaved, sizeof(saved)); Require(updateEnvironment(&saved)==0,"restore guest fegetenv output"); const auto restored=Read(); Write(original); Require(Same(saved,restored),"guest fegetenv round trip"); Write(entry); } void BoundsAndErrors() { const auto entry=Read(); auto clean=entry; clean.x87[0]|=0x3f; clean.x87[1]&=~0xffu; clean.mxcsr=(clean.mxcsr&~0x3fu)|(0x3f<<7); Write(clean); 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=updateEnvironment(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 { updateEnvironment(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< state; asm volatile("fxsave %0" : "=m"(state)); const auto supported=state[7]?state[7]:0xffbfu; for(unsigned mode:{0u,0x40u,0x8000u,0x8040u}) { if(mode&~supported) continue; auto desired=clean; desired.mxcsr=(desired.mxcsr&~0x8040u)|mode; Require(updateEnvironment(&desired)==0,"supported DAZ/FTZ return"); const auto actual=Read(); Write(clean); Require(Same(actual,desired),"supported DAZ/FTZ restored"); } std::barrier ready(2); auto worker=[&](unsigned rounding,unsigned flags) { const auto entry=Read(); auto desired=clean; desired.x87[0]=(desired.x87[0]&~0xc00u)|(rounding<<10); desired.mxcsr=(desired.mxcsr&~0x6000u)|(rounding<<13); auto pending=clean; pending.mxcsr|=flags; ready.arrive_and_wait(); Write(pending); *__error_nid_postfix()=static_cast(flags); const int result=updateEnvironment(&desired); const int error=*__error_nid_postfix(); const auto after=Read(); ready.arrive_and_wait(); Write(entry); desired.x87[1]|=flags; desired.mxcsr|=flags; Require(result==0 && error==static_cast(flags),"thread return/errno"); Require(Same(after,desired),"thread environment and flag union"); }; std::thread a(worker,1,4),b(worker,2,8);a.join();b.join(); Write(original); } volatile std::sig_atomic_t entered=0; #ifdef _WIN32 unsigned expectedFlag; DWORD expectedException; LONG CALLBACK Trap(EXCEPTION_POINTERS* exception) { const auto& state=exception->ContextRecord->FltSave; const auto active=state.StatusWord&~state.ControlWord&0x3f; const auto code=exception->ExceptionRecord->ExceptionCode; const bool denormal=expectedFlag==2 && code==EXCEPTION_FLT_INVALID_OPERATION; if(entered && active==expectedFlag && (code==expectedException || denormal)) std::_Exit(0); return EXCEPTION_CONTINUE_SEARCH; } #else void Trap(int){std::_Exit(entered?0:1);} #endif void Pending(unsigned bit,unsigned source) { Require(bit<6 && source<3,"trap arguments"); #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}; expectedFlag=1u<(GetProcAddress(library,"feupdateenv_nid_postfix")); #else const auto library=dlopen(argv[1],RTLD_NOW); Require(library!=nullptr,"load libc"); updateEnvironment=reinterpret_cast(dlsym(library,"feupdateenv_nid_postfix")); #endif Require(updateEnvironment!=nullptr,"export is missing"); if(argc==4) Pending(static_cast(std::atoi(argv[2])),static_cast(std::atoi(argv[3]))); Matrix(); TagsAndPointers(); BoundsAndErrors(); MasksAndThreads(); #ifdef _WIN32 Require(FreeLibrary(library)!=0,"release library lookup handle"); #else Require(dlclose(library)==0,"release library lookup handle"); #endif }