From 66cad978c39ef68bc39b9e2daf2c2df1cfaff3f2 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 31 Aug 2026 18:59:32 -0700 Subject: [PATCH] FEXCore: Removes syscall optimization The JIT was doing a bunch of additional work where it was saving and restoring registers and then juggling the arguments back in to a stack frame. All of this is nonsensical without the optimization where we could call syscalls inline without a stack frame. Instead remove this optimization entirely and behave like a "generic" syscall path always. The Linux syscall handler now pulls the arguments out of the CPU context directly and stores the result back in to RAX directly as well. This has knock-on effects where technically syscalls are going to be slightly faster because no stack frame setup for the arguments, but additionally we are going to be able to have syscalls be proper serialization points where we can interrupt the syscall and long-jump out without problems. Bumps the DiskCache version again because it causes codegen to change. --- FEXCore/Source/Interface/Core/Frontend.cpp | 4 +- FEXCore/Source/Interface/Core/Frontend.h | 4 - .../Source/Interface/Core/JIT/BranchOps.cpp | 25 +---- .../Interface/Core/OpcodeDispatcher.cpp | 48 +------- FEXCore/Source/Interface/IR/IR.json | 5 +- FEXCore/include/FEXCore/Core/DiskCache.h | 2 +- FEXCore/include/FEXCore/HLE/SyscallHandler.h | 31 +---- Source/Tools/CodeSizeValidation/Main.cpp | 8 +- Source/Tools/CommonTools/DummyHandlers.h | 3 +- Source/Tools/FEXOfflineCompiler/Main.cpp | 15 +-- .../LinuxSyscalls/Seccomp/SeccompEmulator.cpp | 8 +- .../LinuxSyscalls/Seccomp/SeccompEmulator.h | 24 ++-- .../LinuxEmulation/LinuxSyscalls/Syscalls.cpp | 106 +++++++++++++----- .../LinuxEmulation/LinuxSyscalls/Syscalls.h | 33 +++++- .../LinuxSyscalls/x32/Syscalls.cpp | 1 - .../LinuxSyscalls/x64/Syscalls.cpp | 2 - Source/Windows/ARM64EC/Module.S | 4 - Source/Windows/ARM64EC/Module.cpp | 6 +- Source/Windows/WOW64/Module.cpp | 30 +++-- 19 files changed, 158 insertions(+), 201 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Frontend.cpp b/FEXCore/Source/Interface/Core/Frontend.cpp index 3ea384c44..ff5288392 100644 --- a/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/FEXCore/Source/Interface/Core/Frontend.cpp @@ -69,7 +69,6 @@ static uint32_t MapVEXToReg(uint8_t vvvv, bool HasXMM) { Decoder::Decoder(FEXCore::Core::InternalThreadState* Thread) : Thread {Thread} , CTX {static_cast(Thread->CTX)} - , OSABI {CTX->SyscallHandler ? CTX->SyscallHandler->GetOSABI() : FEXCore::HLE::SyscallOSABI::OS_UNKNOWN} , PoolObject {CTX->FrontendAllocator, sizeof(FEXCore::X86Tables::DecodedInst) * DefaultDecodedBufferSize} { FEX_CONFIG_OPT(ReducedPrecision, X87REDUCEDPRECISION); @@ -1358,7 +1357,8 @@ const Decoder::DecodeStream Decoder::AdjustAddrForSpecialRegion(const uint8_t* _ constexpr uint64_t VSyscall_Base = 0xFFFF'FFFF'FF60'0000ULL; constexpr uint64_t VSyscall_End = VSyscall_Base + 0x1000; - if (OSABI == FEXCore::HLE::SyscallOSABI::OS_LINUX64 && RIP >= VSyscall_Base && RIP < VSyscall_End) { + if (BlockInfo.Is64BitMode && CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Linux && RIP >= VSyscall_Base && + RIP < VSyscall_End) { // VSyscall // This doesn't exist on AArch64 and on x86_64 hosts this is emulated with faults to a region mapped with --xp permissions // Offset 0: vgettimeofday diff --git a/FEXCore/Source/Interface/Core/Frontend.h b/FEXCore/Source/Interface/Core/Frontend.h index f668d2b9c..e98f864b3 100644 --- a/FEXCore/Source/Interface/Core/Frontend.h +++ b/FEXCore/Source/Interface/Core/Frontend.h @@ -19,9 +19,6 @@ namespace FEXCore::Context { class ContextImpl; } -namespace FEXCore::HLE { -enum class SyscallOSABI; -} namespace FEXCore::Frontend { class Decoder final { @@ -89,7 +86,6 @@ private: FEXCore::Core::InternalThreadState* Thread; FEXCore::Context::ContextImpl* CTX; - const FEXCore::HLE::SyscallOSABI OSABI {}; FEX_CONFIG_OPT(EnableCodeCacheValidation, ENABLECODECACHEVALIDATION); diff --git a/FEXCore/Source/Interface/Core/JIT/BranchOps.cpp b/FEXCore/Source/Interface/Core/JIT/BranchOps.cpp index d555e0648..2f67eed3e 100644 --- a/FEXCore/Source/Interface/Core/JIT/BranchOps.cpp +++ b/FEXCore/Source/Interface/Core/JIT/BranchOps.cpp @@ -277,11 +277,9 @@ DEF_OP(CondJump) { } DEF_OP(Syscall) { - auto Op = IROp->C(); // Arguments are passed as follows: // X0: SyscallHandler // X1: ThreadState - // X2: Pointer to SyscallArguments PushDynamicRegs(TMP1); @@ -300,31 +298,18 @@ DEF_OP(Syscall) { LoadConstant(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::r0, GPRSpillMask & 0xFFFF); str(ARMEmitter::XReg::x0, STATE, offsetof(FEXCore::Core::CpuStateFrame, InSyscallInfo)); - uint64_t SPOffset = AlignUp(FEXCore::HLE::SyscallArguments::MAX_ARGS * 8, 16); - sub(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::rsp, ARMEmitter::Reg::rsp, SPOffset); - for (uint32_t i = 0; i < FEXCore::HLE::SyscallArguments::MAX_ARGS; ++i) { - if (Op->Header.Args[i].IsInvalid()) { - continue; - } - str(GetReg(Op->Header.Args[i]).X(), ARMEmitter::Reg::rsp, i * 8); - } - ldr(ARMEmitter::XReg::x0, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.SyscallHandlerObj)); ldr(ARMEmitter::XReg::x3, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.SyscallHandlerFunc)); mov(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::r1, STATE.R()); - // SP supporting move - add(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::r2, ARMEmitter::Reg::rsp, 0); if (!CTX->Config.DisableVixlIndirectCalls) [[unlikely]] { GenerateIndirectRuntimeCall(ARMEmitter::Reg::r3); } else { blr(ARMEmitter::Reg::r3); } - add(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::rsp, ARMEmitter::Reg::rsp, SPOffset); - - // Result is now in x0 // Fix the stack and any values that were stepped on + // Syscall result is in any static register that the frontend desired. FillStaticRegs({ .OptionalReg = ARMEmitter::Reg::r1, .OptionalReg2 = ARMEmitter::Reg::r2, @@ -337,14 +322,6 @@ DEF_OP(Syscall) { str(ARMEmitter::XReg::zr, STATE, offsetof(FEXCore::Core::CpuStateFrame, InSyscallInfo)); PopDynamicRegs(); - - const auto OSABI = CTX->SyscallHandler->GetOSABI(); - - if (OSABI != FEXCore::HLE::SyscallOSABI::OS_GENERIC) { - // Move result to its destination register. - // Only if `NORETURNEDRESULT` wasn't set, otherwise we might overwrite the CPUState refilled with `FillStaticRegs` - mov(ARMEmitter::Size::i64Bit, GetReg(Node), ARMEmitter::Reg::r0); - } } DEF_OP(Thunk) { diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp index 20de85532..d2552f433 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp @@ -36,35 +36,6 @@ using X86Tables::OpToIndex; #define OpcodeArgs [[maybe_unused]] FEXCore::X86Tables::DecodedOp Op void OpDispatchBuilder::SyscallOp(OpcodeArgs, bool IsSyscallInst) { - constexpr size_t SyscallArgs = 7; - using SyscallArray = std::array; - - size_t NumArguments {}; - const SyscallArray* GPRIndexes {}; - static constexpr SyscallArray GPRIndexes_64 = { - FEXCore::X86State::REG_RAX, FEXCore::X86State::REG_RDI, FEXCore::X86State::REG_RSI, FEXCore::X86State::REG_RDX, - FEXCore::X86State::REG_R10, FEXCore::X86State::REG_R8, FEXCore::X86State::REG_R9, - }; - static constexpr SyscallArray GPRIndexes_32 = { - FEXCore::X86State::REG_RAX, FEXCore::X86State::REG_RBX, FEXCore::X86State::REG_RCX, FEXCore::X86State::REG_RDX, - FEXCore::X86State::REG_RSI, FEXCore::X86State::REG_RDI, FEXCore::X86State::REG_RBP, - }; - - const auto OSABI = CTX->SyscallHandler->GetOSABI(); - if (OSABI == FEXCore::HLE::SyscallOSABI::OS_LINUX64) { - NumArguments = GPRIndexes_64.size(); - GPRIndexes = &GPRIndexes_64; - } else if (OSABI == FEXCore::HLE::SyscallOSABI::OS_LINUX32) { - NumArguments = GPRIndexes_32.size(); - GPRIndexes = &GPRIndexes_32; - } else if (OSABI == FEXCore::HLE::SyscallOSABI::OS_GENERIC) { - // All registers will be spilled before the syscall and filled afterwards so no JIT-side argument handling is necessary. - NumArguments = 0; - GPRIndexes = nullptr; - } else { - ERROR_AND_DIE_FMT("Unhandled OSABI syscall"); - } - // Calculate flags early. CalculateDeferredFlags(); @@ -72,13 +43,6 @@ void OpDispatchBuilder::SyscallOp(OpcodeArgs, bool IsSyscallInst) { auto NewRIP = GetRelocatedPC(Op, -Op->InstSize); _StoreContextGPR(GPRSize, NewRIP, offsetof(FEXCore::Core::CPUState, rip)); - Ref Arguments[SyscallArgs] { - InvalidNode, InvalidNode, InvalidNode, InvalidNode, InvalidNode, InvalidNode, InvalidNode, - }; - for (size_t i = 0; i < NumArguments; ++i) { - Arguments[i] = LoadGPRRegister(GPRIndexes->at(i)); - } - if (IsSyscallInst) { // If this is the `Syscall` instruction rather than `int 0x80` then we need to do some additional work. // RCX = RIP after this instruction @@ -94,12 +58,7 @@ void OpDispatchBuilder::SyscallOp(OpcodeArgs, bool IsSyscallInst) { } FlushRegisterCache(); - auto SyscallOp = _Syscall(Arguments[0], Arguments[1], Arguments[2], Arguments[3], Arguments[4], Arguments[5], Arguments[6]); - - // Generic ABI doesn't store result in RAX. - if (OSABI != FEXCore::HLE::SyscallOSABI::OS_GENERIC) { - StoreGPRRegister(X86State::REG_RAX, SyscallOp); - } + _Syscall(); if (Op->TableInfo->Flags & X86Tables::InstFlags::FLAGS_BLOCK_END) { // RIP could have been updated after coming back from the Syscall. @@ -5051,11 +5010,6 @@ void OpDispatchBuilder::RDTSCPOp(OpcodeArgs) { } void OpDispatchBuilder::RDPIDOp(OpcodeArgs) { - if (CTX->HostFeatures.HostType != FEXCore::HostFeatures::HostTypeEnum::Linux && !CTX->HostFeatures.SupportsCPUIndexInTPIDRRO) { - // RDTSCP is unsupported on Win32 platforms if TPIDRRO isn't supported. - UnimplementedOp(Op); - return; - } StoreResultGPR(Op, _ProcessorID()); } diff --git a/FEXCore/Source/Interface/IR/IR.json b/FEXCore/Source/Interface/IR/IR.json index 5d46bdf57..2fc6a6960 100644 --- a/FEXCore/Source/Interface/IR/IR.json +++ b/FEXCore/Source/Interface/IR/IR.json @@ -326,11 +326,10 @@ "CallbackReturn": { "HasSideEffects": true }, - "GPR = Syscall GPR:$SyscallID, GPR:$Arg0, GPR:$Arg1, GPR:$Arg2, GPR:$Arg3, GPR:$Arg4, GPR:$Arg5": { + "Syscall": { "HasSideEffects": true, "Desc": ["Dispatches a guest syscall through to the SyscallHandler class" - ], - "DestSize": "OpSize::i64Bit" + ] }, "Thunk GPR:$ArgPtr, SHA256Sum:$ThunkNameHash": { diff --git a/FEXCore/include/FEXCore/Core/DiskCache.h b/FEXCore/include/FEXCore/Core/DiskCache.h index 760426c3b..ee21be3f6 100644 --- a/FEXCore/include/FEXCore/Core/DiskCache.h +++ b/FEXCore/include/FEXCore/Core/DiskCache.h @@ -205,7 +205,7 @@ namespace DiskCache { // TODO: This header is in global installed header path, but uses internal headers. // Migrate this once that is fixed. - static constexpr uint16_t FormatVersion = 4; + static constexpr uint16_t FormatVersion = 5; FEX_DEFAULT_VISIBILITY uint16_t GetFormatVersion(); } // namespace DiskCache diff --git a/FEXCore/include/FEXCore/HLE/SyscallHandler.h b/FEXCore/include/FEXCore/HLE/SyscallHandler.h index d1f4decd7..0ba0218e3 100644 --- a/FEXCore/include/FEXCore/HLE/SyscallHandler.h +++ b/FEXCore/include/FEXCore/HLE/SyscallHandler.h @@ -17,29 +17,6 @@ struct CpuStateFrame; } // namespace FEXCore::Core namespace FEXCore::HLE { -struct SyscallArguments { - static constexpr std::size_t MAX_ARGS = 7; - uint64_t Argument[MAX_ARGS]; -}; - -struct SyscallABI { - // Expectation is that the backend will be aware of how to modify the arguments based on numbering - // Only GPRs expected - uint8_t NumArgs; - // If the syscall has a return then it should be stored in the ABI specific syscall register - // Linux = RAX - bool HasReturn; - - int32_t HostSyscallNumber; -}; - -enum class SyscallOSABI { - OS_UNKNOWN, - OS_LINUX64, - OS_LINUX32, - OS_GENERIC, // No JIT-side argument handling, spill/fill all regs. -}; - struct ExecutableRangeInfo { uint64_t Base; uint64_t Size; @@ -53,11 +30,8 @@ class SyscallHandler { public: virtual ~SyscallHandler() = default; - virtual uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) = 0; + virtual void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) = 0; - SyscallOSABI GetOSABI() const { - return OSABI; - } virtual void MarkGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) {} virtual void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) {} virtual void MarkOvercommitRange(uint64_t Start, uint64_t Length) {} @@ -72,8 +46,5 @@ public: } virtual void SleepThread(FEXCore::Context::Context* CTX, FEXCore::Core::CpuStateFrame* Frame) {} - -protected: - SyscallOSABI OSABI; }; } // namespace FEXCore::HLE diff --git a/Source/Tools/CodeSizeValidation/Main.cpp b/Source/Tools/CodeSizeValidation/Main.cpp index 86de9e2d7..07436620a 100644 --- a/Source/Tools/CodeSizeValidation/Main.cpp +++ b/Source/Tools/CodeSizeValidation/Main.cpp @@ -479,13 +479,9 @@ private: class SimpleSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators { public: - SimpleSyscallHandler() { - // Just claim to be linux 64-bit for simplicity. - OSABI = FEXCore::HLE::SyscallOSABI::OS_LINUX64; - } - uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) override { + SimpleSyscallHandler() = default; + void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) override { // Don't do anything - return 0; } // These are no-ops implementations of the SyscallHandler API diff --git a/Source/Tools/CommonTools/DummyHandlers.h b/Source/Tools/CommonTools/DummyHandlers.h index 91666a525..3de15eb13 100644 --- a/Source/Tools/CommonTools/DummyHandlers.h +++ b/Source/Tools/CommonTools/DummyHandlers.h @@ -11,9 +11,8 @@ namespace FEX::DummyHandlers { class DummySyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators { public: - uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) override { + void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) override { // Don't do anything - return 0; } // These are no-ops implementations of the SyscallHandler API diff --git a/Source/Tools/FEXOfflineCompiler/Main.cpp b/Source/Tools/FEXOfflineCompiler/Main.cpp index 0c344dc91..e40ecdb64 100644 --- a/Source/Tools/FEXOfflineCompiler/Main.cpp +++ b/Source/Tools/FEXOfflineCompiler/Main.cpp @@ -65,14 +65,11 @@ class AOTSyscallHandler : public FEXCore::HLE::SyscallHandler { class AOTSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEX::HLE::SyscallMmapInterface { #endif public: - AOTSyscallHandler(FEXCore::Context::Context& CTX, FEXCore::HLE::SyscallOSABI SyscallOSABI) - : CTX(CTX) { - OSABI = SyscallOSABI; - } + AOTSyscallHandler(FEXCore::Context::Context& CTX) + : CTX(CTX) {} - uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) override { + void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) override { // Don't do anything - return 0; } FEXCore::Context::Context& CTX; @@ -427,8 +424,7 @@ static std::optional GenerateSingleCache(FEXCore::ExecutableFileInf #ifdef _WIN32 OvercommitTracker = std::make_unique(IsWine); - auto SyscallOSABI = FEXCore::HLE::SyscallOSABI::OS_GENERIC; - auto SyscallHandler = std::make_unique(*CTX, SyscallOSABI); + auto SyscallHandler = std::make_unique(*CTX); SyscallHandler->VAFileStart = TryMapImage(SyscallHandler->InvalidationTracker, SyscallHandler->ImageTracker, Binary.FileId, Binary).value_or(0); @@ -441,8 +437,7 @@ static std::optional GenerateSingleCache(FEXCore::ExecutableFileInf #else Loader.CalculateHWCaps(CTX.get()); - auto SyscallOSABI = Is64Bit ? FEXCore::HLE::SyscallOSABI::OS_LINUX64 : FEXCore::HLE::SyscallOSABI::OS_LINUX32; - auto SyscallHandler = std::make_unique(*CTX, SyscallOSABI); + auto SyscallHandler = std::make_unique(*CTX); // Populate relocations from ELF file { diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Seccomp/SeccompEmulator.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Seccomp/SeccompEmulator.cpp index 539ffa3c3..7b0e9f91c 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Seccomp/SeccompEmulator.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Seccomp/SeccompEmulator.cpp @@ -316,15 +316,13 @@ void SeccompEmulator::DeserializeFilters(FEXCore::Core::CpuStateFrame* Frame, in } SeccompEmulator::ExecuteFilterResult -SeccompEmulator::ExecuteFilter(FEXCore::Core::CpuStateFrame* Frame, uint64_t JITPC, FEXCore::HLE::SyscallArguments* Args) { - auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame); - - if (Thread->Filters.empty()) { +SeccompEmulator::ExecuteFilter(FEXCore::Core::CpuStateFrame* Frame, uint64_t JITPC, FEX::HLE::SyscallArguments* Args) { + if (!HasFilter(Frame)) { // Seccomp not installed. Allow it. return {false, 0}; } - // Reconstruct the RIP from the JITPC. + auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame); const uint64_t RIP = Thread->Thread->CTX->RestoreRIPFromHostPC(Frame->Thread, JITPC); const auto Arch = Is64BitMode() ? AUDIT_ARCH_X86_64 : AUDIT_ARCH_I386; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Seccomp/SeccompEmulator.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/Seccomp/SeccompEmulator.h index 0ea2af834..b10f664ed 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Seccomp/SeccompEmulator.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Seccomp/SeccompEmulator.h @@ -9,6 +9,7 @@ $end_info$ #include #include #include +#include "LinuxSyscalls/ThreadManager.h" #include #include @@ -20,23 +21,16 @@ struct sock_fprog; struct seccomp_data; struct seccomp_notif_sizes; -namespace FEXCore { - -namespace Core { - struct CpuStateFrame; -} - -namespace HLE { - struct SyscallArguments; -} - -} // namespace FEXCore +namespace FEXCore::Core { +struct CpuStateFrame; +} // namespace FEXCore::Core namespace FEX::HLE { class SignalDelegator; class SyscallHandler; struct ThreadStateObject; +struct SyscallArguments; using SeccompFilterFunc = uint64_t (*)(uint32_t Acc, uint32_t Index, uint32_t Tmp, uint32_t Tmp2, void* Data); struct SeccompFilterInfo final { @@ -65,7 +59,13 @@ public: bool EarlyReturn {}; uint64_t Result; }; - ExecuteFilterResult ExecuteFilter(FEXCore::Core::CpuStateFrame* Frame, uint64_t JITPC, FEXCore::HLE::SyscallArguments* Args); + ExecuteFilterResult ExecuteFilter(FEXCore::Core::CpuStateFrame* Frame, uint64_t JITPC, FEX::HLE::SyscallArguments* Args); + + bool HasFilter(FEXCore::Core::CpuStateFrame* Frame) const { + auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame); + return !Thread->Filters.empty(); + } + int GetKillSignal() const { return CurrentKillSignal; } diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp index c391337de..01600e605 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp @@ -808,63 +808,109 @@ uint32_t SyscallHandler::CalculateGuestKernelVersion() { return std::max(LinuxVersion::KernelVersion(5, 15), std::min(LinuxVersion::KernelVersion(6, 11), GetHostKernelVersion())); } -uint64_t SyscallHandler::HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) { - // Grab the return address which will be inside the JIT. - const uint64_t JITPC = reinterpret_cast(__builtin_extract_return_addr(__builtin_return_address(0))); +template +void SyscallHandler::HandleSyscallImpl(FEXCore::Core::CpuStateFrame* Frame, uint64_t JITPC) { + auto SetResult = [](FEXCore::Core::CpuStateFrame* Frame, uint64_t Result) { + const auto Mask = Is64Bit ? ~0ULL : ~0U; + auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame); - const auto SeccompResult = SeccompEmulator.ExecuteFilter(Frame, JITPC, Args); + Thread->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RAX] = Result & Mask; + }; - if (SeccompResult.EarlyReturn) { - return SeccompResult.Result; + if (SeccompEmulator.HasFilter(Frame)) { + FEX::HLE::SyscallArguments Args { + .Argument = + { + GetArg(Is64Bit, Frame, 0), + GetArg(Is64Bit, Frame, 1), + GetArg(Is64Bit, Frame, 2), + GetArg(Is64Bit, Frame, 3), + GetArg(Is64Bit, Frame, 4), + GetArg(Is64Bit, Frame, 5), + GetArg(Is64Bit, Frame, 6), + }, + }; + const auto SeccompResult = SeccompEmulator.ExecuteFilter(Frame, JITPC, &Args); + + if (SeccompResult.EarlyReturn) { + SetResult(Frame, SeccompResult.Result); + return; + } } - if (Args->Argument[0] >= Definitions.size()) { - return -ENOSYS; + const auto SyscallNum = GetArg(Is64Bit, Frame, 0); + if (SyscallNum >= Definitions.size()) { + SetResult(Frame, -ENOSYS); + return; } - auto& Def = Definitions[Args->Argument[0]]; + auto& Def = Definitions[SyscallNum]; uint64_t Result {}; switch (Def.NumArgs) { case 0: Result = std::invoke(Def.Ptr0, Frame); break; - case 1: Result = std::invoke(Def.Ptr1, Frame, Args->Argument[1]); break; - case 2: Result = std::invoke(Def.Ptr2, Frame, Args->Argument[1], Args->Argument[2]); break; - case 3: Result = std::invoke(Def.Ptr3, Frame, Args->Argument[1], Args->Argument[2], Args->Argument[3]); break; - case 4: Result = std::invoke(Def.Ptr4, Frame, Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4]); break; + case 1: Result = std::invoke(Def.Ptr1, Frame, GetArg(Is64Bit, Frame, 1)); break; + case 2: Result = std::invoke(Def.Ptr2, Frame, GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2)); break; + case 3: Result = std::invoke(Def.Ptr3, Frame, GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2), GetArg(Is64Bit, Frame, 3)); break; + case 4: + Result = + std::invoke(Def.Ptr4, Frame, GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2), GetArg(Is64Bit, Frame, 3), GetArg(Is64Bit, Frame, 4)); + break; case 5: - Result = std::invoke(Def.Ptr5, Frame, Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5]); + Result = std::invoke(Def.Ptr5, Frame, GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2), GetArg(Is64Bit, Frame, 3), + GetArg(Is64Bit, Frame, 4), GetArg(Is64Bit, Frame, 5)); break; case 6: - Result = std::invoke(Def.Ptr6, Frame, Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5], - Args->Argument[6]); + Result = std::invoke(Def.Ptr6, Frame, GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2), GetArg(Is64Bit, Frame, 3), + GetArg(Is64Bit, Frame, 4), GetArg(Is64Bit, Frame, 5), GetArg(Is64Bit, Frame, 6)); break; // for missing syscalls - case 255: return std::invoke(Def.Ptr1, Frame, Args->Argument[0]); + case 255: Result = std::invoke(Def.Ptr1, Frame, GetArg(Is64Bit, Frame, 0)); break; default: - LOGMAN_MSG_A_FMT("Unhandled syscall: {}", Args->Argument[0]); - return -1; + LOGMAN_MSG_A_FMT("Unhandled syscall: {}", GetArg(Is64Bit, Frame, 0)); + Result = -ENOSYS; break; } #ifdef DEBUG_STRACE - Strace(Args, Result); + Strace(Frame, Result); #endif - return Result; + SetResult(Frame, Result); +} + +void SyscallHandler::HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) { + // Grab the return address which will be inside the JIT. + const uint64_t JITPC = reinterpret_cast(__builtin_extract_return_addr(__builtin_return_address(0))); + const auto Is64Bit = Is64BitMode(); + + // TODO: At some point these will be runtime selectable based on `syscall` versus `int 0x80` entrypoint. + if (Is64Bit) { + HandleSyscallImpl(Frame, JITPC); + } else { + HandleSyscallImpl(Frame, JITPC); + } } #ifdef DEBUG_STRACE -void SyscallHandler::Strace(FEXCore::HLE::SyscallArguments* Args, uint64_t Ret) { - auto& Def = Definitions[Args->Argument[0]]; +void SyscallHandler::Strace(FEXCore::Core::CpuStateFrame* Frame, uint64_t Ret) { + const auto Is64Bit = Is64BitMode(); + auto& Def = Definitions[GetArg(Is64Bit, Frame, 0)]; switch (Def.NumArgs) { case 0: LogMan::Msg::DFmt(Def.StraceFmt.c_str(), Ret); break; - case 1: LogMan::Msg::DFmt(Def.StraceFmt.c_str(), Args->Argument[1], Ret); break; - case 2: LogMan::Msg::DFmt(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Ret); break; - case 3: LogMan::Msg::DFmt(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Args->Argument[3], Ret); break; - case 4: LogMan::Msg::DFmt(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Ret); break; + case 1: LogMan::Msg::DFmt(Def.StraceFmt.c_str(), GetArg(Is64Bit, Frame, 1), Ret); break; + case 2: LogMan::Msg::DFmt(Def.StraceFmt.c_str(), GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2), Ret); break; + case 3: + LogMan::Msg::DFmt(Def.StraceFmt.c_str(), GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2), GetArg(Is64Bit, Frame, 3), Ret); + break; + case 4: + LogMan::Msg::DFmt(Def.StraceFmt.c_str(), GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2), GetArg(Is64Bit, Frame, 3), + GetArg(Is64Bit, Frame, 4), Ret); + break; case 5: - LogMan::Msg::DFmt(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5], Ret); + LogMan::Msg::DFmt(Def.StraceFmt.c_str(), GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2), GetArg(Is64Bit, Frame, 3), + GetArg(Is64Bit, Frame, 4), GetArg(Is64Bit, Frame, 5), Ret); break; case 6: - LogMan::Msg::DFmt(Def.StraceFmt.c_str(), Args->Argument[1], Args->Argument[2], Args->Argument[3], Args->Argument[4], Args->Argument[5], - Args->Argument[6], Ret); + LogMan::Msg::DFmt(Def.StraceFmt.c_str(), GetArg(Is64Bit, Frame, 1), GetArg(Is64Bit, Frame, 2), GetArg(Is64Bit, Frame, 3), + GetArg(Is64Bit, Frame, 4), GetArg(Is64Bit, Frame, 5), GetArg(Is64Bit, Frame, 6), Ret); break; default: break; } diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h index 3909930cb..7b417e83e 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h @@ -69,6 +69,10 @@ namespace Core { } // namespace FEXCore namespace FEX::HLE { +struct SyscallArguments { + static constexpr std::size_t MAX_ARGS = 7; + uint64_t Argument[MAX_ARGS]; +}; class SyscallHandler; class SignalDelegator; @@ -124,8 +128,7 @@ public: virtual ~SyscallHandler(); - // In the case that the syscall doesn't hit the optimized path then we still need to go here - uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) final override; + void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) final override; void DefaultProgramBreak(uint64_t Base, uint64_t Size); void DeserializeSeccompFD(FEX::HLE::ThreadStateObject* Thread, int FD) { @@ -362,12 +365,36 @@ private: bool NeedToCheckXID {true}; #ifdef DEBUG_STRACE - void Strace(FEXCore::HLE::SyscallArguments* Args, uint64_t Ret); + void Strace(FEXCore::Core::CpuStateFrame* Frame, uint64_t Ret); #endif fextl::unique_ptr GenerateMap(std::string_view GuestBinaryFile, std::string_view GuestBinaryFileId) override; fextl::unique_ptr Alloc32Handler {}; std::atomic AnonSharedId {1}; + + static inline uint64_t GetArg(bool Is64Bit, FEXCore::Core::CpuStateFrame* Frame, size_t Arg) { + constexpr size_t SyscallArgs = 7; + using SyscallArray = std::array; + + static constexpr SyscallArray GPRIndexes_64 = { + FEXCore::X86State::REG_RAX, FEXCore::X86State::REG_RDI, FEXCore::X86State::REG_RSI, FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_R10, FEXCore::X86State::REG_R8, FEXCore::X86State::REG_R9, + }; + + static constexpr SyscallArray GPRIndexes_32 = { + FEXCore::X86State::REG_RAX, FEXCore::X86State::REG_RBX, FEXCore::X86State::REG_RCX, FEXCore::X86State::REG_RDX, + FEXCore::X86State::REG_RSI, FEXCore::X86State::REG_RDI, FEXCore::X86State::REG_RBP, + }; + + const auto Index = Is64Bit ? GPRIndexes_64[Arg] : GPRIndexes_32[Arg]; + const auto Mask = Is64Bit ? ~0ULL : ~0U; + const auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame); + + return Thread->Thread->CurrentFrame->State.gregs[Index] & Mask; + }; + + template + void HandleSyscallImpl(FEXCore::Core::CpuStateFrame* Frame, uint64_t JITPC); }; #define SYSCALL_ERRNO() \ diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.cpp index ea52df84c..7cea2833b 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.cpp @@ -29,7 +29,6 @@ x32SyscallHandler::x32SyscallHandler(FEXCore::Context::Context* ctx, FEX::HLE::S FEX::HLE::ThunkHandler* ThunkHandler, fextl::unique_ptr Allocator) : SyscallHandler {ctx, _SignalDelegation, ThunkHandler} , AllocHandler {std::move(Allocator)} { - OSABI = FEXCore::HLE::SyscallOSABI::OS_LINUX32; RegisterSyscallHandlers(); } diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.cpp index 55d8b1922..5b69a5d7b 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.cpp @@ -15,8 +15,6 @@ namespace FEX::HLE::x64 { x64SyscallHandler::x64SyscallHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler) : SyscallHandler {ctx, _SignalDelegation, ThunkHandler} { - OSABI = FEXCore::HLE::SyscallOSABI::OS_LINUX64; - RegisterSyscallHandlers(); } diff --git a/Source/Windows/ARM64EC/Module.S b/Source/Windows/ARM64EC/Module.S index 4c18172ea..f76f92d77 100644 --- a/Source/Windows/ARM64EC/Module.S +++ b/Source/Windows/ARM64EC/Module.S @@ -108,15 +108,11 @@ ExitFunctionSuspendPoint: Name:; \ adrp x16, WineSyscallDispatcher; \ ldr x16, [x16, HASH:lo12:WineSyscallDispatcher]; \ - cbz x16, 1f; \ mov x9, x30; \ adrp x8, WineIdName; \ ldr x8, [x8, HASH:lo12:WineIdName]; \ blr x16; \ ret; \ - 1:; \ - svc HASH WindowsId; \ - ret // Allows for continuing from a full native context, as the NTDLL NtContinue export takes in an x64 context with EC and // the conversion to that loses the ARM64EC ABI-disallowed registers that FEX uses. diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index c8df78bfe..d15d0676c 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -513,11 +513,9 @@ static void RethrowGuestException(const EXCEPTION_RECORD& Rec, ARM64_NT_CONTEXT& class ECSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators { public: - ECSyscallHandler() { - OSABI = FEXCore::HLE::SyscallOSABI::OS_GENERIC; - } + ECSyscallHandler() = default; - uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) override { + void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) override { ProcessPendingCrossProcessEmulatorWork(); // Manually raise an exeption with the current JIT state packed into a native context, ntdll handles this and diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index f6533cf7a..0fca8574d 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -417,6 +417,20 @@ bool HandleSuspendInterrupt(TLS TLS, CONTEXT* Context, uint64_t FaultAddress) { } } // namespace Context +// Calls a 1-argument function `Func` setting the parent unwind frame information to the given SP and PC +__attribute__((naked)) extern "C" uint64_t SEHFrameTrampoline1Args(void* Arg0, void* Func, uint64_t Sp, uint64_t Pc) { + asm(".seh_proc SEHFrameTrampoline1Args;" + "stp x2, x3, [sp, #-0x10]!;" + ".seh_pushframe;" + "stp x29, x30, [sp, #-0x10]!;" + ".seh_save_fplr_x 16;" + ".seh_endprologue;" + "blr x1;" + "ldp x29, x30, [sp], 0x20;" + "ret;" + ".seh_endproc;"); +} + // Calls a 2-argument function `Func` setting the parent unwind frame information to the given SP and PC __attribute__((naked)) extern "C" uint64_t SEHFrameTrampoline2Args(void* Arg0, void* Arg1, void* Func, uint64_t Sp, uint64_t Pc) { asm(".seh_proc SEHFrameTrampoline2Args;" @@ -433,11 +447,9 @@ __attribute__((naked)) extern "C" uint64_t SEHFrameTrampoline2Args(void* Arg0, v class WowSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators { public: - WowSyscallHandler() { - OSABI = FEXCore::HLE::SyscallOSABI::OS_GENERIC; - } + WowSyscallHandler() = default; - static uint64_t HandleSyscallImpl(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) { + static void HandleSyscallImpl(FEXCore::Core::CpuStateFrame* Frame) { const uint64_t ReturnRIP = *(uint32_t*)(Frame->State.gregs[FEXCore::X86State::REG_RSP]); // Return address from the stack uint64_t ReturnRSP = Frame->State.gregs[FEXCore::X86State::REG_RSP] + 4; // Stack pointer after popping return address uint64_t ReturnRAX = 0; @@ -470,20 +482,16 @@ public: Context::LockJITContext(TLS); Frame->State.gregs[FEXCore::X86State::REG_RAX] = ReturnRAX; } - - // NORETURNEDRESULT causes this result to be ignored since we restore all registers back from memory after a syscall anyway - return 0; } - uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) override { + void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) override { const auto TLS = GetTLS(); // Stash the the context pointer on the stack, as Simulate can be called from this syscall handler which would overwrite it CONTEXT* EntryContext = TLS.EntryContext(); // Call the syscall handler with unwind information pointing to Simulate as its caller - uint64_t Ret = SEHFrameTrampoline2Args(reinterpret_cast(Frame), reinterpret_cast(Args), - reinterpret_cast(&HandleSyscallImpl), EntryContext->Sp, EntryContext->Pc); + uint64_t Ret = + SEHFrameTrampoline1Args(reinterpret_cast(Frame), reinterpret_cast(&HandleSyscallImpl), EntryContext->Sp, EntryContext->Pc); TLS.EntryContext() = EntryContext; - return Ret; } std::optional LookupExecutableFileSection(FEXCore::Core::InternalThreadState*, uint64_t Address) override {