#include "NativePal.h"
// These public .NET PAL and GCToOSInterface signatures are derived from the
// MIT-licensed dotnet/runtime v10.0.12 Pal.h and gc/env/gcenv.os.h. Implementations
// below are XFESystem-specific; no Cosmos implementation or IL patcher is used.
extern "C" void* malloc(size_t);
extern "C" void free(void*);
extern "C" void* memcpy(void*, const void*, size_t);
extern "C" size_t strlen(const char*);
extern "C" int strcmp(const char*, const char*);
extern "C" int strcasecmp(const char*, const char*);
extern "C" int snprintf(char*,size_t,const char*,...);
extern "C" [[noreturn]] void abort();
static const XfeRuntimePal* services;
extern "C" int XfeRuntimePalInstalled() { return services != nullptr; }
extern "C" const XfeRuntimePal* XfeRuntimeGetPal() { return services; }
extern "C" int XfeRuntimeInstallPal(const XfeRuntimePal* p)
{
if (!p || p->version != 1 || p->structure_bytes != sizeof(XfeRuntimePal) ||
!p->reserve || !p->commit || !p->decommit || !p->release || !p->protect ||
!p->physical_bytes || !p->free_physical_bytes || !p->monotonic_nanoseconds ||
!p->current_thread_id || !p->current_stack_bounds || !p->start_thread ||
!p->sleep_milliseconds || !p->yield || !p->create_event || !p->close_event ||
!p->set_event || !p->reset_event || !p->wait_event || !p->wait_any ||
!p->attach_runtime_thread || !p->request_gc_hijack || !p->write || !p->fatal)
return 0;
if (services && services != p) return 0;
services = p;
return 1;
}
static const XfeRuntimePal& pal()
{
if (!services) abort();
return *services;
}
[[noreturn]] static void fail(const char* reason)
{
if (services) services->fatal(reason);
abort();
}
static void memory_failure(const char* operation,void* address,uint64_t bytes,uint32_t flags)
{
char buffer[160];
int n=snprintf(buffer,sizeof(buffer),"XFE_DOTNET_MEMORY_FAILURE %s address=%p bytes=%llu flags=%u\n",operation,address,(unsigned long long)bytes,flags);
if(n>0 && (size_t)n<sizeof(buffer))pal().write(buffer,n);
}
static uint32_t protection(uint32_t value)
{
switch (value) {
case 1: return 0; // PAGE_NOACCESS
case 2: return 1;
case 4: return 3;
case 0x10: return 4;
case 0x20: return 5;
case 0x40: return 7;
default: return UINT32_MAX;
}
}
struct FILETIME { uint32_t dwLowDateTime, dwHighDateTime; };
struct PAL_LIMITED_CONTEXT { uintptr_t IP,Rsp,Rbp,Rax,Rbx,Rdx,R12,R13,R14,R15; };
static_assert(sizeof(PAL_LIMITED_CONTEXT)==80);
class Thread {
public:
bool IsGCSpecial(); void* GetTransitionFrame(); bool IsHijacked();
void HijackReturnAddress(PAL_LIMITED_CONTEXT*,void(*)());
};
class RuntimeInstance { public: bool IsManaged(void*); };
RuntimeInstance* GetRuntimeInstance();
extern "C" void RhpGcProbeHijack();
typedef int32_t (*HardwareExceptionHandler)(uintptr_t, uintptr_t, PAL_LIMITED_CONTEXT*, uintptr_t*, uintptr_t*);
static HardwareExceptionHandler exception_handler;
uint32_t g_RhNumberOfProcessors = 1;
size_t g_pageSizeUnixInl = 4096;
thread_local intptr_t tls_thunkData;
extern "C" intptr_t RhGetCurrentThunkContext() { return tls_thunkData; }
// GC configuration is initialized by the official runtime; the kernel provides
// validated services before RhInitialize is entered.
class GCToOSInterface {
public:
static bool Initialize(); static void Shutdown();
static void* VirtualReserve(size_t,size_t,uint32_t,uint16_t);
static bool VirtualRelease(void*,size_t); static bool VirtualCommit(void*,size_t,uint16_t);
static bool VirtualDecommit(void*,size_t); static bool VirtualReset(void*,size_t,bool);
static void* VirtualReserveAndCommitLargePages(size_t,uint16_t);
static bool SupportsWriteWatch(); static void ResetWriteWatch(void*,size_t);
static bool GetWriteWatch(bool,void*,size_t,void**,uintptr_t*);
static void Sleep(uint32_t); static void YieldThread(uint32_t);
static uint32_t GetCurrentProcessorNumber(); static bool CanGetCurrentProcessorNumber();
static uint64_t GetCurrentThreadIdForLogging(); static uint32_t GetCurrentProcessId();
static bool SetCurrentThreadIdealAffinity(uint16_t,uint16_t); static bool SetThreadAffinity(uint16_t);
static bool BoostThreadPriority(); static bool CanEnableGCNumaAware(); static bool CanEnableGCCPUGroups();
static uint32_t GetTotalProcessorCount(); static uint32_t GetMaxProcessorCount();
static bool GetProcessorForHeap(uint16_t,uint16_t*,uint16_t*);
static size_t GetVirtualMemoryLimit(); static size_t GetVirtualMemoryMaxAddress();
static uint64_t GetPhysicalMemoryLimit(bool*);
static void GetMemoryStatus(uint64_t,uint32_t*,uint64_t*,uint64_t*);
static size_t GetCacheSizePerLogicalCpu(bool);
static int64_t QueryPerformanceCounter(); static int64_t QueryPerformanceFrequency();
static uint64_t GetLowPrecisionTimeStamp(); static void FlushProcessWriteBuffers();
static void DebugBreak();
static bool ParseGCHeapAffinitizeRangesEntry(const char**,size_t*,size_t*);
};
bool GCToOSInterface::Initialize() { bool ready=XfeRuntimePalInstalled() && pal().physical_bytes() >= 64*1024*1024 && pal().monotonic_nanoseconds() != 0;if(ready)pal().write("XFE_DOTNET_PAL_OK\n",18);return ready; }
void GCToOSInterface::Shutdown() {}
void* GCToOSInterface::VirtualReserve(size_t n,size_t alignment,uint32_t flags,uint16_t node)
{
if (!n || flags != 0 || (node != UINT16_MAX && node != 0)) { memory_failure("reserve contract",nullptr,n,flags);return nullptr; }
if (alignment < 4096) alignment = 4096;
if ((alignment & (alignment-1)) != 0) return nullptr;
void* result=pal().reserve(n,alignment);if(!result)memory_failure("reserve",nullptr,n,flags);return result;
}
bool GCToOSInterface::VirtualRelease(void* p,size_t n) { return pal().release(p,n) != 0; }
bool GCToOSInterface::VirtualCommit(void* p,size_t n,uint16_t node) { bool result=(node == UINT16_MAX || node == 0) && pal().commit(p,n) != 0;if(!result)memory_failure("commit",p,n,node);return result; }
bool GCToOSInterface::VirtualDecommit(void* p,size_t n) { return pal().decommit(p,n) != 0; }
bool GCToOSInterface::VirtualReset(void*,size_t,bool) { return false; } // Optional discard optimisation not provided.
void* GCToOSInterface::VirtualReserveAndCommitLargePages(size_t,uint16_t) { return nullptr; }
bool GCToOSInterface::SupportsWriteWatch() { return false; }
void GCToOSInterface::ResetWriteWatch(void*,size_t) { fail("GC write watch requested although unavailable"); }
bool GCToOSInterface::GetWriteWatch(bool,void*,size_t,void**,uintptr_t*) { return false; }
void GCToOSInterface::Sleep(uint32_t n) { pal().sleep_milliseconds(n); }
void GCToOSInterface::YieldThread(uint32_t) { pal().yield(); }
uint32_t GCToOSInterface::GetCurrentProcessorNumber() { return 0; }
bool GCToOSInterface::CanGetCurrentProcessorNumber() { return true; }
uint64_t GCToOSInterface::GetCurrentThreadIdForLogging() { return pal().current_thread_id(); }
uint32_t GCToOSInterface::GetCurrentProcessId() { return 1; }
bool GCToOSInterface::SetCurrentThreadIdealAffinity(uint16_t from,uint16_t to) { return from == 0 && to == 0; }
bool GCToOSInterface::SetThreadAffinity(uint16_t n) { return n == 0; }
bool GCToOSInterface::BoostThreadPriority() { return false; }
bool GCToOSInterface::CanEnableGCNumaAware() { return false; }
bool GCToOSInterface::CanEnableGCCPUGroups() { return false; }
uint32_t GCToOSInterface::GetTotalProcessorCount() { return 1; }
uint32_t GCToOSInterface::GetMaxProcessorCount() { return 1; }
bool GCToOSInterface::GetProcessorForHeap(uint16_t n,uint16_t* cpu,uint16_t* node) { if(n != 0)return false;if(cpu)*cpu=0;if(node)*node=UINT16_MAX;return true; }
size_t GCToOSInterface::GetVirtualMemoryLimit() { return 1ULL << 46; }
size_t GCToOSInterface::GetVirtualMemoryMaxAddress() { return (1ULL << 47)-1; }
uint64_t GCToOSInterface::GetPhysicalMemoryLimit(bool* restricted) { if(restricted)*restricted=false;return pal().physical_bytes(); }
void GCToOSInterface::GetMemoryStatus(uint64_t limit,uint32_t* load,uint64_t* available,uint64_t* pagefile)
{
uint64_t total=pal().physical_bytes(),remaining=pal().free_physical_bytes();
if(limit && limit<total)total=limit;
if(remaining>total)remaining=total;
if(load)*load=total?(uint32_t)((total-remaining)*100/total):100;
if(available)*available=remaining;
if(pagefile)*pagefile=remaining;
}
size_t GCToOSInterface::GetCacheSizePerLogicalCpu(bool) { return 0; }
int64_t GCToOSInterface::QueryPerformanceCounter() { return (int64_t)pal().monotonic_nanoseconds(); }
int64_t GCToOSInterface::QueryPerformanceFrequency() { return 1000000000; }
uint64_t GCToOSInterface::GetLowPrecisionTimeStamp() { return pal().monotonic_nanoseconds()/1000000; }
void GCToOSInterface::FlushProcessWriteBuffers() { __atomic_thread_fence(__ATOMIC_SEQ_CST); }
void GCToOSInterface::DebugBreak() { fail("GC debug break"); }
bool GCToOSInterface::ParseGCHeapAffinitizeRangesEntry(const char** text,size_t* first,size_t* last)
{
if(!text || !*text || !first || !last)return false;
const char* p=*text;
while(*p==' ' || *p=='\t')p++;
if(*p!='0')return false;p++;
if(*p=='-'){p++;if(*p!='0')return false;p++;}
if(*p!=0 && *p!=',' && *p!=' ' && *p!='\t')return false;
*first=*last=0;*text=p;return true;
}
class GCConfig { public: static void Initialize(); };
class GCEvent {
void* implementation;
public:
GCEvent(); void CloseEvent(); void Set(); void Reset();
uint32_t Wait(uint32_t,bool);
bool CreateManualEventNoThrow(bool); bool CreateAutoEventNoThrow(bool);
bool CreateOSManualEventNoThrow(bool); bool CreateOSAutoEventNoThrow(bool);
};
GCEvent::GCEvent():implementation(nullptr) {}
void GCEvent::CloseEvent() { if(implementation && !pal().close_event(implementation))fail("GC event close refused");implementation=nullptr; }
void GCEvent::Set() { if(!implementation || !pal().set_event(implementation))fail("GC event set refused"); }
void GCEvent::Reset() { if(!implementation || !pal().reset_event(implementation))fail("GC event reset refused"); }
uint32_t GCEvent::Wait(uint32_t timeout,bool) { return implementation?pal().wait_event(implementation,timeout):UINT32_MAX; }
bool GCEvent::CreateManualEventNoThrow(bool initial) { if(implementation)return false;implementation=pal().create_event(1,initial);return implementation!=nullptr; }
bool GCEvent::CreateAutoEventNoThrow(bool initial) { if(implementation)return false;implementation=pal().create_event(0,initial);return implementation!=nullptr; }
bool GCEvent::CreateOSManualEventNoThrow(bool initial) { return CreateManualEventNoThrow(initial); }
bool GCEvent::CreateOSAutoEventNoThrow(bool initial) { return CreateAutoEventNoThrow(initial); }
bool PalInit() { GCConfig::Initialize(); return GCToOSInterface::Initialize(); }
void RhFailFast() { fail("Microsoft NativeAOT fail fast"); }
int32_t PalGetProcessCpuCount() { return 1; }
uint32_t PalGetOsPageSize() { return 4096; }
void* PalVirtualAlloc(uintptr_t n,uint32_t prot)
{
uint32_t bits=protection(prot);if(bits==UINT32_MAX || !n)return nullptr;
void* p=pal().reserve(n,4096);if(!p)return nullptr;
if(!pal().commit(p,n) || !pal().protect(p,n,bits)){pal().release(p,n);return nullptr;}return p;
}
void PalVirtualFree(void* p,uintptr_t n) { if(!pal().release(p,n))fail("NativeAOT virtual release refused"); }
uint32_t PalVirtualProtect(void* p,uintptr_t n,uint32_t prot)
{
uint32_t bits=protection(prot);uintptr_t start=(uintptr_t)p & ~(uintptr_t)4095;
uintptr_t offset=(uintptr_t)p-start;
if(!n || bits==UINT32_MAX || n>UINTPTR_MAX-offset || n+offset>UINTPTR_MAX-4095)return 0;
uintptr_t bytes=(n+offset+4095)&~(uintptr_t)4095;
uint32_t result=pal().protect((void*)start,bytes,bits)!=0;
if(!result)memory_failure("protect",(void*)start,bytes,bits);
return result;
}
void PalFlushInstructionCache(void*,size_t) { __atomic_thread_fence(__ATOMIC_SEQ_CST); }
void PalFlushProcessWriteBuffers() { __atomic_thread_fence(__ATOMIC_SEQ_CST); }
void PalSleep(uint32_t n) { pal().sleep_milliseconds(n); }
uint32_t PalSwitchToThread() { pal().yield();return 0; }
uint32_t PalAreShadowStacksEnabled() { return 0; }
bool PalGetMaximumStackBounds(void** low,void** high) { return pal().current_stack_bounds(low,high)!=0; }
uint64_t PalGetCurrentOSThreadId() { return pal().current_thread_id(); }
uint32_t PalGetCurrentProcessId() { return 1; }
void PalAttachThread(void* thread) { pal().attach_runtime_thread(thread); }
bool PalSetCurrentThreadName(const char*) { return false; } // Optional diagnostic name.
bool PalStartBackgroundGCThread(XfeRuntimeThreadEntry entry,void* data) { return pal().start_thread(entry,data,8*1024*1024)!=0; }
bool PalStartFinalizerThread(XfeRuntimeThreadEntry entry,void* data) { return pal().start_thread(entry,data,8*1024*1024)!=0; }
bool PalStartEventPipeHelperThread(XfeRuntimeThreadEntry entry,void* data) { return pal().start_thread(entry,data,8*1024*1024)!=0; }
void PalHijack(Thread* thread) { if(pal().request_gc_hijack(thread)<0)fail("GC thread hijack target invalid"); }
extern "C" int XfeRuntimeTryHijack(void* runtime_thread,const XfeRuntimeLimitedContext* saved)
{
if(!runtime_thread || !saved || !saved->ip || !saved->rsp)return -1;
auto thread=(Thread*)runtime_thread;
if(thread->IsGCSpecial() || thread->GetTransitionFrame()!=nullptr)return 1;
if(!GetRuntimeInstance()->IsManaged((void*)saved->ip))return 0;
PAL_LIMITED_CONTEXT context={saved->ip,saved->rsp,saved->rbp,saved->rax,saved->rbx,saved->rdx,saved->r12,saved->r13,saved->r14,saved->r15};
thread->HijackReturnAddress(&context,&RhpGcProbeHijack);
return thread->IsHijacked()?1:0;
}
void RuntimeThreadShutdown(void*);
extern "C" void* RhpGetCurrentThread();
extern "C" int XfeRuntimeDetachThread(void* thread) { if(!thread || RhpGetCurrentThread()!=thread)return 0;RuntimeThreadShutdown(thread);return 1; }
void (*PalGetHijackTarget(void (*target)()))() { return target; }
void* PalCreateEventW(void*,uint32_t manual,uint32_t state,const char16_t*) { return pal().create_event(manual!=0,state!=0); }
uint32_t PalCloseHandle(void* e) { return pal().close_event(e)!=0; }
uint32_t PalResetEvent(void* e) { return pal().reset_event(e)!=0; }
uint32_t PalSetEvent(void* e) { return pal().set_event(e)!=0; }
uint32_t PalWaitForSingleObjectEx(void* e,uint32_t ms,uint32_t alertable) { if(alertable)return UINT32_MAX;return pal().wait_event(e,ms); }
uint32_t PalCompatibleWaitAny(uint32_t alertable,uint32_t ms,uint32_t count,void** handles,uint32_t reentrant) { if(alertable||reentrant)return UINT32_MAX;return pal().wait_any(handles,count,ms); }
void* PalCreateLowMemoryResourceNotification() { return nullptr; }
void PalSetHardwareExceptionHandler(HardwareExceptionHandler handler) { exception_handler=handler; }
extern "C" int XfeRuntimeDispatchHardwareFault(XfeRuntimeInterruptFrame* frame,uint64_t address,void* fxsave)
{
static_assert(sizeof(XfeRuntimeInterruptFrame)==176);
if(!exception_handler || !frame || !fxsave || (frame->vector!=0 && frame->vector!=14))return 0;
uint32_t code=frame->vector==14?0xc0000005U:0xc0000094U;
if(frame->vector==0 && !XfeRuntimeClassifyDivideFault(frame,&code))return 0;
PAL_LIMITED_CONTEXT context={frame->rip,frame->rsp,frame->rbp,frame->rax,frame->rbx,frame->rdx,frame->r12,frame->r13,frame->r14,frame->r15};
uintptr_t arg0=frame->rdi,arg1=frame->rsi;
// The official runtime recognises registered managed code and its known
// helper fault sites. It decides whether/how to redirect to RhpThrowHwEx.
if(exception_handler(code,address,&context,&arg0,&arg1)!=-1)return 0;
frame->rip=context.IP;frame->rsp=context.Rsp;frame->rbp=context.Rbp;
frame->rax=context.Rax;frame->rbx=context.Rbx;frame->rdx=context.Rdx;
frame->r12=context.R12;frame->r13=context.R13;frame->r14=context.R14;frame->r15=context.R15;
frame->rdi=arg0;frame->rsi=arg1;
// PAL_LIMITED_CONTEXT has no SIMD fields. The original FXSAVE64 region is
// retained intact, and the real interrupt exit restores it before IRETQ.
char text[96];int n=snprintf(text,sizeof(text),"XFE_DOTNET_HARDWARE_EH vector=%llu code=%x\n",(unsigned long long)frame->vector,code);
if(n>0 && (size_t)n<sizeof(text))pal().write(text,n);
return 1;
}
void PalPrintFatalError(const char* text) { pal().write(text,strlen(text)); }
// No crash-dump device or writer is registered. A fail-fast diagnostic still
// prints its actual reason and halts; this optional hook produces no dump.
void PalCreateCrashDumpIfEnabled(void*) {}
char* PalCopyTCharAsChar(const char* text) { size_t n=strlen(text)+1;auto p=(char*)malloc(n);if(p)memcpy(p,text,n);return p; }
uint32_t PalGetEnvironmentVariable(const char*,char*,uint32_t) { return 0; }
void PalGetSystemTimeAsFileTime(FILETIME* out) { if(!pal().utc_filetime)fail("Wall clock is unavailable");uint64_t value=pal().utc_filetime();if(!value)fail("RTC_UNAVAILABLE");out->dwLowDateTime=(uint32_t)value;out->dwHighDateTime=(uint32_t)(value>>32); }
extern "C" char __xfe_kernel_start, __xfe_kernel_end;
void* PalGetModuleHandleFromPointer(void* pointer) { uintptr_t p=(uintptr_t)pointer;return p>=(uintptr_t)&__xfe_kernel_start && p<(uintptr_t)&__xfe_kernel_end?&__xfe_kernel_start:nullptr; }
int32_t PalGetModuleFileName(const char** result,void* module) { if(!result || module!=&__xfe_kernel_start)return 0;static const char name[]="XFESystem.Kernel.elf";*result=name;return sizeof(name)-1; }
void* PalLoadLibrary(const char*) { return nullptr; }
void* PalGetProcAddress(void*,const char*) { return nullptr; }
int32_t _stricmp(const char* a,const char* b) { return strcasecmp(a,b); }
#include "NativePal.h"
// These public .NET PAL and GCToOSInterface signatures are derived from the
// MIT-licensed dotnet/runtime v10.0.12 Pal.h and gc/env/gcenv.os.h. Implementations
// below are XFESystem-specific; no Cosmos implementation or IL patcher is used.
extern "C" void* malloc(size_t);
extern "C" void free(void*);
extern "C" void* memcpy(void*, const void*, size_t);
extern "C" size_t strlen(const char*);
extern "C" int strcmp(const char*, const char*);
extern "C" int strcasecmp(const char*, const char*);
extern "C" int snprintf(char*,size_t,const char*,...);
extern "C" [[noreturn]] void abort();
static const XfeRuntimePal* services;
extern "C" int XfeRuntimePalInstalled() { return services != nullptr; }
extern "C" const XfeRuntimePal* XfeRuntimeGetPal() { return services; }
extern "C" int XfeRuntimeInstallPal(const XfeRuntimePal* p)
{
if (!p || p->version != 1 || p->structure_bytes != sizeof(XfeRuntimePal) ||
!p->reserve || !p->commit || !p->decommit || !p->release || !p->protect ||
!p->physical_bytes || !p->free_physical_bytes || !p->monotonic_nanoseconds ||
!p->current_thread_id || !p->current_stack_bounds || !p->start_thread ||
!p->sleep_milliseconds || !p->yield || !p->create_event || !p->close_event ||
!p->set_event || !p->reset_event || !p->wait_event || !p->wait_any ||
!p->attach_runtime_thread || !p->request_gc_hijack || !p->write || !p->fatal)
return 0;
if (services && services != p) return 0;
services = p;
return 1;
}
static const XfeRuntimePal& pal()
{
if (!services) abort();
return *services;
}
[[noreturn]] static void fail(const char* reason)
{
if (services) services->fatal(reason);
abort();
}
static void memory_failure(const char* operation,void* address,uint64_t bytes,uint32_t flags)
{
char buffer[160];
int n=snprintf(buffer,sizeof(buffer),"XFE_DOTNET_MEMORY_FAILURE %s address=%p bytes=%llu flags=%u\n",operation,address,(unsigned long long)bytes,flags);
if(n>0 && (size_t)n<sizeof(buffer))pal().write(buffer,n);
}
static uint32_t protection(uint32_t value)
{
switch (value) {
case 1: return 0; // PAGE_NOACCESS
case 2: return 1;
case 4: return 3;
case 0x10: return 4;
case 0x20: return 5;
case 0x40: return 7;
default: return UINT32_MAX;
}
}
struct FILETIME { uint32_t dwLowDateTime, dwHighDateTime; };
struct PAL_LIMITED_CONTEXT { uintptr_t IP,Rsp,Rbp,Rax,Rbx,Rdx,R12,R13,R14,R15; };
static_assert(sizeof(PAL_LIMITED_CONTEXT)==80);
class Thread {
public:
bool IsGCSpecial(); void* GetTransitionFrame(); bool IsHijacked();
void HijackReturnAddress(PAL_LIMITED_CONTEXT*,void(*)());
};
class RuntimeInstance { public: bool IsManaged(void*); };
RuntimeInstance* GetRuntimeInstance();
extern "C" void RhpGcProbeHijack();
typedef int32_t (*HardwareExceptionHandler)(uintptr_t, uintptr_t, PAL_LIMITED_CONTEXT*, uintptr_t*, uintptr_t*);
static HardwareExceptionHandler exception_handler;
uint32_t g_RhNumberOfProcessors = 1;
size_t g_pageSizeUnixInl = 4096;
thread_local intptr_t tls_thunkData;
extern "C" intptr_t RhGetCurrentThunkContext() { return tls_thunkData; }
// GC configuration is initialized by the official runtime; the kernel provides
// validated services before RhInitialize is entered.
class GCToOSInterface {
public:
static bool Initialize(); static void Shutdown();
static void* VirtualReserve(size_t,size_t,uint32_t,uint16_t);
static bool VirtualRelease(void*,size_t); static bool VirtualCommit(void*,size_t,uint16_t);
static bool VirtualDecommit(void*,size_t); static bool VirtualReset(void*,size_t,bool);
static void* VirtualReserveAndCommitLargePages(size_t,uint16_t);
static bool SupportsWriteWatch(); static void ResetWriteWatch(void*,size_t);
static bool GetWriteWatch(bool,void*,size_t,void**,uintptr_t*);
static void Sleep(uint32_t); static void YieldThread(uint32_t);
static uint32_t GetCurrentProcessorNumber(); static bool CanGetCurrentProcessorNumber();
static uint64_t GetCurrentThreadIdForLogging(); static uint32_t GetCurrentProcessId();
static bool SetCurrentThreadIdealAffinity(uint16_t,uint16_t); static bool SetThreadAffinity(uint16_t);
static bool BoostThreadPriority(); static bool CanEnableGCNumaAware(); static bool CanEnableGCCPUGroups();
static uint32_t GetTotalProcessorCount(); static uint32_t GetMaxProcessorCount();
static bool GetProcessorForHeap(uint16_t,uint16_t*,uint16_t*);
static size_t GetVirtualMemoryLimit(); static size_t GetVirtualMemoryMaxAddress();
static uint64_t GetPhysicalMemoryLimit(bool*);
static void GetMemoryStatus(uint64_t,uint32_t*,uint64_t*,uint64_t*);
static size_t GetCacheSizePerLogicalCpu(bool);
static int64_t QueryPerformanceCounter(); static int64_t QueryPerformanceFrequency();
static uint64_t GetLowPrecisionTimeStamp(); static void FlushProcessWriteBuffers();
static void DebugBreak();
static bool ParseGCHeapAffinitizeRangesEntry(const char**,size_t*,size_t*);
};
bool GCToOSInterface::Initialize() { bool ready=XfeRuntimePalInstalled() && pal().physical_bytes() >= 64*1024*1024 && pal().monotonic_nanoseconds() != 0;if(ready)pal().write("XFE_DOTNET_PAL_OK\n",18);return ready; }
void GCToOSInterface::Shutdown() {}
void* GCToOSInterface::VirtualReserve(size_t n,size_t alignment,uint32_t flags,uint16_t node)
{
if (!n || flags != 0 || (node != UINT16_MAX && node != 0)) { memory_failure("reserve contract",nullptr,n,flags);return nullptr; }
if (alignment < 4096) alignment = 4096;
if ((alignment & (alignment-1)) != 0) return nullptr;
void* result=pal().reserve(n,alignment);if(!result)memory_failure("reserve",nullptr,n,flags);return result;
}
bool GCToOSInterface::VirtualRelease(void* p,size_t n) { return pal().release(p,n) != 0; }
bool GCToOSInterface::VirtualCommit(void* p,size_t n,uint16_t node) { bool result=(node == UINT16_MAX || node == 0) && pal().commit(p,n) != 0;if(!result)memory_failure("commit",p,n,node);return result; }
bool GCToOSInterface::VirtualDecommit(void* p,size_t n) { return pal().decommit(p,n) != 0; }
bool GCToOSInterface::VirtualReset(void*,size_t,bool) { return false; } // Optional discard optimisation not provided.
void* GCToOSInterface::VirtualReserveAndCommitLargePages(size_t,uint16_t) { return nullptr; }
bool GCToOSInterface::SupportsWriteWatch() { return false; }
void GCToOSInterface::ResetWriteWatch(void*,size_t) { fail("GC write watch requested although unavailable"); }
bool GCToOSInterface::GetWriteWatch(bool,void*,size_t,void**,uintptr_t*) { return false; }
void GCToOSInterface::Sleep(uint32_t n) { pal().sleep_milliseconds(n); }
void GCToOSInterface::YieldThread(uint32_t) { pal().yield(); }
uint32_t GCToOSInterface::GetCurrentProcessorNumber() { return 0; }
bool GCToOSInterface::CanGetCurrentProcessorNumber() { return true; }
uint64_t GCToOSInterface::GetCurrentThreadIdForLogging() { return pal().current_thread_id(); }
uint32_t GCToOSInterface::GetCurrentProcessId() { return 1; }
bool GCToOSInterface::SetCurrentThreadIdealAffinity(uint16_t from,uint16_t to) { return from == 0 && to == 0; }
bool GCToOSInterface::SetThreadAffinity(uint16_t n) { return n == 0; }
bool GCToOSInterface::BoostThreadPriority() { return false; }
bool GCToOSInterface::CanEnableGCNumaAware() { return false; }
bool GCToOSInterface::CanEnableGCCPUGroups() { return false; }
uint32_t GCToOSInterface::GetTotalProcessorCount() { return 1; }
uint32_t GCToOSInterface::GetMaxProcessorCount() { return 1; }
bool GCToOSInterface::GetProcessorForHeap(uint16_t n,uint16_t* cpu,uint16_t* node) { if(n != 0)return false;if(cpu)*cpu=0;if(node)*node=UINT16_MAX;return true; }
size_t GCToOSInterface::GetVirtualMemoryLimit() { return 1ULL << 46; }
size_t GCToOSInterface::GetVirtualMemoryMaxAddress() { return (1ULL << 47)-1; }
uint64_t GCToOSInterface::GetPhysicalMemoryLimit(bool* restricted) { if(restricted)*restricted=false;return pal().physical_bytes(); }
void GCToOSInterface::GetMemoryStatus(uint64_t limit,uint32_t* load,uint64_t* available,uint64_t* pagefile)
{
uint64_t total=pal().physical_bytes(),remaining=pal().free_physical_bytes();
if(limit && limit<total)total=limit;
if(remaining>total)remaining=total;
if(load)*load=total?(uint32_t)((total-remaining)*100/total):100;
if(available)*available=remaining;
if(pagefile)*pagefile=remaining;
}
size_t GCToOSInterface::GetCacheSizePerLogicalCpu(bool) { return 0; }
int64_t GCToOSInterface::QueryPerformanceCounter() { return (int64_t)pal().monotonic_nanoseconds(); }
int64_t GCToOSInterface::QueryPerformanceFrequency() { return 1000000000; }
uint64_t GCToOSInterface::GetLowPrecisionTimeStamp() { return pal().monotonic_nanoseconds()/1000000; }
void GCToOSInterface::FlushProcessWriteBuffers() { __atomic_thread_fence(__ATOMIC_SEQ_CST); }
void GCToOSInterface::DebugBreak() { fail("GC debug break"); }
bool GCToOSInterface::ParseGCHeapAffinitizeRangesEntry(const char** text,size_t* first,size_t* last)
{
if(!text || !*text || !first || !last)return false;
const char* p=*text;
while(*p==' ' || *p=='\t')p++;
if(*p!='0')return false;p++;
if(*p=='-'){p++;if(*p!='0')return false;p++;}
if(*p!=0 && *p!=',' && *p!=' ' && *p!='\t')return false;
*first=*last=0;*text=p;return true;
}
class GCConfig { public: static void Initialize(); };
class GCEvent {
void* implementation;
public:
GCEvent(); void CloseEvent(); void Set(); void Reset();
uint32_t Wait(uint32_t,bool);
bool CreateManualEventNoThrow(bool); bool CreateAutoEventNoThrow(bool);
bool CreateOSManualEventNoThrow(bool); bool CreateOSAutoEventNoThrow(bool);
};
GCEvent::GCEvent():implementation(nullptr) {}
void GCEvent::CloseEvent() { if(implementation && !pal().close_event(implementation))fail("GC event close refused");implementation=nullptr; }
void GCEvent::Set() { if(!implementation || !pal().set_event(implementation))fail("GC event set refused"); }
void GCEvent::Reset() { if(!implementation || !pal().reset_event(implementation))fail("GC event reset refused"); }
uint32_t GCEvent::Wait(uint32_t timeout,bool) { return implementation?pal().wait_event(implementation,timeout):UINT32_MAX; }
bool GCEvent::CreateManualEventNoThrow(bool initial) { if(implementation)return false;implementation=pal().create_event(1,initial);return implementation!=nullptr; }
bool GCEvent::CreateAutoEventNoThrow(bool initial) { if(implementation)return false;implementation=pal().create_event(0,initial);return implementation!=nullptr; }
bool GCEvent::CreateOSManualEventNoThrow(bool initial) { return CreateManualEventNoThrow(initial); }
bool GCEvent::CreateOSAutoEventNoThrow(bool initial) { return CreateAutoEventNoThrow(initial); }
bool PalInit() { GCConfig::Initialize(); return GCToOSInterface::Initialize(); }
void RhFailFast() { fail("Microsoft NativeAOT fail fast"); }
int32_t PalGetProcessCpuCount() { return 1; }
uint32_t PalGetOsPageSize() { return 4096; }
void* PalVirtualAlloc(uintptr_t n,uint32_t prot)
{
uint32_t bits=protection(prot);if(bits==UINT32_MAX || !n)return nullptr;
void* p=pal().reserve(n,4096);if(!p)return nullptr;
if(!pal().commit(p,n) || !pal().protect(p,n,bits)){pal().release(p,n);return nullptr;}return p;
}
void PalVirtualFree(void* p,uintptr_t n) { if(!pal().release(p,n))fail("NativeAOT virtual release refused"); }
uint32_t PalVirtualProtect(void* p,uintptr_t n,uint32_t prot)
{
uint32_t bits=protection(prot);uintptr_t start=(uintptr_t)p & ~(uintptr_t)4095;
uintptr_t offset=(uintptr_t)p-start;
if(!n || bits==UINT32_MAX || n>UINTPTR_MAX-offset || n+offset>UINTPTR_MAX-4095)return 0;
uintptr_t bytes=(n+offset+4095)&~(uintptr_t)4095;
uint32_t result=pal().protect((void*)start,bytes,bits)!=0;
if(!result)memory_failure("protect",(void*)start,bytes,bits);
return result;
}
void PalFlushInstructionCache(void*,size_t) { __atomic_thread_fence(__ATOMIC_SEQ_CST); }
void PalFlushProcessWriteBuffers() { __atomic_thread_fence(__ATOMIC_SEQ_CST); }
void PalSleep(uint32_t n) { pal().sleep_milliseconds(n); }
uint32_t PalSwitchToThread() { pal().yield();return 0; }
uint32_t PalAreShadowStacksEnabled() { return 0; }
bool PalGetMaximumStackBounds(void** low,void** high) { return pal().current_stack_bounds(low,high)!=0; }
uint64_t PalGetCurrentOSThreadId() { return pal().current_thread_id(); }
uint32_t PalGetCurrentProcessId() { return 1; }
void PalAttachThread(void* thread) { pal().attach_runtime_thread(thread); }
bool PalSetCurrentThreadName(const char*) { return false; } // Optional diagnostic name.
bool PalStartBackgroundGCThread(XfeRuntimeThreadEntry entry,void* data) { return pal().start_thread(entry,data,8*1024*1024)!=0; }
bool PalStartFinalizerThread(XfeRuntimeThreadEntry entry,void* data) { return pal().start_thread(entry,data,8*1024*1024)!=0; }
bool PalStartEventPipeHelperThread(XfeRuntimeThreadEntry entry,void* data) { return pal().start_thread(entry,data,8*1024*1024)!=0; }
void PalHijack(Thread* thread) { if(pal().request_gc_hijack(thread)<0)fail("GC thread hijack target invalid"); }
extern "C" int XfeRuntimeTryHijack(void* runtime_thread,const XfeRuntimeLimitedContext* saved)
{
if(!runtime_thread || !saved || !saved->ip || !saved->rsp)return -1;
auto thread=(Thread*)runtime_thread;
if(thread->IsGCSpecial() || thread->GetTransitionFrame()!=nullptr)return 1;
if(!GetRuntimeInstance()->IsManaged((void*)saved->ip))return 0;
PAL_LIMITED_CONTEXT context={saved->ip,saved->rsp,saved->rbp,saved->rax,saved->rbx,saved->rdx,saved->r12,saved->r13,saved->r14,saved->r15};
thread->HijackReturnAddress(&context,&RhpGcProbeHijack);
return thread->IsHijacked()?1:0;
}
void RuntimeThreadShutdown(void*);
extern "C" void* RhpGetCurrentThread();
extern "C" int XfeRuntimeDetachThread(void* thread) { if(!thread || RhpGetCurrentThread()!=thread)return 0;RuntimeThreadShutdown(thread);return 1; }
void (*PalGetHijackTarget(void (*target)()))() { return target; }
void* PalCreateEventW(void*,uint32_t manual,uint32_t state,const char16_t*) { return pal().create_event(manual!=0,state!=0); }
uint32_t PalCloseHandle(void* e) { return pal().close_event(e)!=0; }
uint32_t PalResetEvent(void* e) { return pal().reset_event(e)!=0; }
uint32_t PalSetEvent(void* e) { return pal().set_event(e)!=0; }
uint32_t PalWaitForSingleObjectEx(void* e,uint32_t ms,uint32_t alertable) { if(alertable)return UINT32_MAX;return pal().wait_event(e,ms); }
uint32_t PalCompatibleWaitAny(uint32_t alertable,uint32_t ms,uint32_t count,void** handles,uint32_t reentrant) { if(alertable||reentrant)return UINT32_MAX;return pal().wait_any(handles,count,ms); }
void* PalCreateLowMemoryResourceNotification() { return nullptr; }
void PalSetHardwareExceptionHandler(HardwareExceptionHandler handler) { exception_handler=handler; }
extern "C" int XfeRuntimeDispatchHardwareFault(XfeRuntimeInterruptFrame* frame,uint64_t address,void* fxsave)
{
static_assert(sizeof(XfeRuntimeInterruptFrame)==176);
if(!exception_handler || !frame || !fxsave || (frame->vector!=0 && frame->vector!=14))return 0;
uint32_t code=frame->vector==14?0xc0000005U:0xc0000094U;
if(frame->vector==0 && !XfeRuntimeClassifyDivideFault(frame,&code))return 0;
PAL_LIMITED_CONTEXT context={frame->rip,frame->rsp,frame->rbp,frame->rax,frame->rbx,frame->rdx,frame->r12,frame->r13,frame->r14,frame->r15};
uintptr_t arg0=frame->rdi,arg1=frame->rsi;
// The official runtime recognises registered managed code and its known
// helper fault sites. It decides whether/how to redirect to RhpThrowHwEx.
if(exception_handler(code,address,&context,&arg0,&arg1)!=-1)return 0;
frame->rip=context.IP;frame->rsp=context.Rsp;frame->rbp=context.Rbp;
frame->rax=context.Rax;frame->rbx=context.Rbx;frame->rdx=context.Rdx;
frame->r12=context.R12;frame->r13=context.R13;frame->r14=context.R14;frame->r15=context.R15;
frame->rdi=arg0;frame->rsi=arg1;
// PAL_LIMITED_CONTEXT has no SIMD fields. The original FXSAVE64 region is
// retained intact, and the real interrupt exit restores it before IRETQ.
char text[96];int n=snprintf(text,sizeof(text),"XFE_DOTNET_HARDWARE_EH vector=%llu code=%x\n",(unsigned long long)frame->vector,code);
if(n>0 && (size_t)n<sizeof(text))pal().write(text,n);
return 1;
}
void PalPrintFatalError(const char* text) { pal().write(text,strlen(text)); }
// No crash-dump device or writer is registered. A fail-fast diagnostic still
// prints its actual reason and halts; this optional hook produces no dump.
void PalCreateCrashDumpIfEnabled(void*) {}
char* PalCopyTCharAsChar(const char* text) { size_t n=strlen(text)+1;auto p=(char*)malloc(n);if(p)memcpy(p,text,n);return p; }
uint32_t PalGetEnvironmentVariable(const char*,char*,uint32_t) { return 0; }
void PalGetSystemTimeAsFileTime(FILETIME* out) { if(!pal().utc_filetime)fail("Wall clock is unavailable");uint64_t value=pal().utc_filetime();if(!value)fail("RTC_UNAVAILABLE");out->dwLowDateTime=(uint32_t)value;out->dwHighDateTime=(uint32_t)(value>>32); }
extern "C" char __xfe_kernel_start, __xfe_kernel_end;
void* PalGetModuleHandleFromPointer(void* pointer) { uintptr_t p=(uintptr_t)pointer;return p>=(uintptr_t)&__xfe_kernel_start && p<(uintptr_t)&__xfe_kernel_end?&__xfe_kernel_start:nullptr; }
int32_t PalGetModuleFileName(const char** result,void* module) { if(!result || module!=&__xfe_kernel_start)return 0;static const char name[]="XFESystem.Kernel.elf";*result=name;return sizeof(name)-1; }
void* PalLoadLibrary(const char*) { return nullptr; }
void* PalGetProcAddress(void*,const char*) { return nullptr; }
int32_t _stricmp(const char* a,const char* b) { return strcasecmp(a,b); }