Compare commits

...
Author SHA1 Message Date
Billy Laws 9fe5eb1979 JIT: Restore behaviour of emitting interrupt checks at every block entry
This is needed to handle suspend in infinite loops that occur as a
result of block-size constraints or indirect jumps. Fixes grow home.
2025-10-29 00:35:46 +00:00
Ryan Houdek f414c92963 Code view 2025-10-28 23:53:15 +00:00
Ryan Houdek 90c59e37cb unittests/ASM: Adds test for too large branch objects 2025-10-28 23:53:15 +00:00
Ryan Houdek b7c7789a01 FEXCore/JIT: Supports restarting JIT in case of encoding failure
ARM64 branches have fairly small relative distances they can encode.
These can be +-1MB, or even +-32KB. The largest relative branch is
+-128MB, which we already set as an upper limit of our block JIT cache
size.

We have for a long time just compiled these without checking with the
expectation that things just happen to work. We didn't hit the asserts
so it was relatively low priority. Apparently now with Steam and a
MaxInst limit of 5000, we are now hitting an assert where we are
encoding too large of a range.

Implement support for long jumping from anywhere in the JIT for when a
long jump tries to be encoded and fails, allowing us to restart the JIT
at any moment. This is implemented as a long jump when this singular
feature could have gotten away with some sort of invasive check and
early exit path for two reasons. For one, that would be even more
invasive, effectively doing try-catch logic manually. And two, the next
step is supporting JIT buffer overflow for when our block size heuristic
fails.

This next step will mandate longjump on SIGSEGV (with cooperative
interaction with the frontend) from effectively /anywhere/ in the JIT.
One of the design goals of the CodeEmitter is that every code emission
function doesn't do a size remaining check to allow the compiler to do
some very effective optimization of emitting code blocks to memory (and
it works!).

But we lose the ability to sanely size check. When writing the emitter I
knew we were going to need to write this cooperative guard page handler,
and we're finally at a point where it needs to be done. This will be in
the next PR although.
2025-10-28 23:53:15 +00:00
Ryan Houdek f653c5e0c0 FEXCore/JIT: Ignore local encoding limit checks
These are guaranteed not to hit encoding distance limits, so we can
ignore the returns.
2025-10-28 23:53:15 +00:00
Ryan Houdek 65fff73959 FEXCore/Dispatcher: Check encoding errors 2025-10-28 23:53:15 +00:00
Ryan Houdek 93b7c513d8 FEXCore/VectorRegType: Trivial header fix 2025-10-28 23:53:15 +00:00
Ryan Houdek f1d14c6325 Linux/BPFEmitter: Explicitly ignored encoding bool
We know these won't encode in errors.
2025-10-28 23:53:15 +00:00
Ryan Houdek 8223c6ac36 unittests/Emitter: Explicitly ignore encoding bool
We know these won't encode in errors.
2025-10-28 23:53:15 +00:00
Ryan Houdek e17677580d CodeEmitter: Return bool if Label instructions can't be encoded
Programming error if they aren't checked, as they will encode
incorrectly if they are too large for their respective instructions.
2025-10-28 23:53:15 +00:00
Ryan Houdek 150bf7b30c FEXCore: Moves longjump implementation from FEX frontend
This will be getting used by FEXCore in a bit.
2025-10-28 23:53:15 +00:00
Ryan Houdek 674efc69c4 FEX: Print a log when kernel unaligned atomics are used 2025-10-28 23:53:15 +00:00
Billy Laws 38049c5281 Windows: Enable downstream kernel-side unaligned atomic handling 2025-10-28 23:53:15 +00:00
Billy Laws e3627349a1 FEXLoader: Enable downstream kernel-side unaligned atomic handling 2025-10-28 23:53:15 +00:00
Billy Laws 7c207080a4 Windows: Support new two-stage invalidation model 2025-10-28 23:53:12 +00:00
Billy Laws eeee5b53ca Linux: Support new two-stage invalidation model 2025-10-28 23:53:12 +00:00
Billy Laws 47619063c2 LookupCache: Introduce two-pass code invalidation model
Shared code buffer support introduced the concept of having a single
GuestToHostMaps shared across many threads. In the common case all
threads will share one however if e.g. a resize recently occured and
specific thread is yet to compile any code with the new codebuffer it
will still use the old GuestToHostMap. The current invalidation
approach handles this by repeatedly calling erase for every single
thread's GuestToHostMap, even if it is repeated. An accumulator is used
to ensure when two threads share a map, the L1/L2 cache entries in the
second thread will still be invalidated even if the the iteration for
the first thread removed them from the map.

Unfortunately this is incredibly slow in cases with many threads, as
a significant number of redundant map lookups and L1/L2 cache erasures
on threads that never even observed a given block can occur. Solve this
by introducing a two-pass model:
- First, all active codebuffers (and their associated GuestToHostMaps)
  have their entries invalidated for the given range, these codebuffers
  are tracked internally within FEXCore. It is at this point that delinking
  callbacks are ran.
- Second, each thread will have its caches invalidated. But rather than
  naively invalidating the L1/L2 caches for every invalidated block for
  every thread, threads now track on their own what specific entries
  have been potentially fetched into their L1/L2 caches. This is
  aided by GuestToHostMap now tracking the pages each block touches. (an
  inverse CodePages so to speak).
2025-10-28 23:53:12 +00:00
Billy Laws cb7076cbab FEXCore: Keep a list of weak refs to all allocated codebuffers
We currently rely on the frontend to keep track of threads and then
iterate over all threads to perform per-codebuffer operations. However
as codebuffers are shared between many threads (the common case is a
single code buffer across all) this ends up being inefficient. Introduce
a list of codebuffers to solve that (new codebuffers are very rare, so a
vector is plenty fine here for erasing invalid weak refs).
2025-10-28 23:53:12 +00:00
Billy Laws 8dde79826e LookupCache: Drop unused state frame argument for delinker cbs 2025-10-28 23:53:12 +00:00
Billy Laws 4e1d10a46f Profiler: Fix missing include 2025-10-28 23:52:57 +00:00
LC 46d019fe02 Merge pull request #5001 from Sonicadvance1/non_repeating_strings
FEXCore: Have non-repeat strings operations listen to non-tso config
2025-10-27 22:41:37 -04:00
LC d56f689e15 Merge pull request #5002 from Sonicadvance1/fix_typo_in_the_long_long
LinuxSyscalls/Threads: Fixes typo in long jump handler
2025-10-27 22:40:03 -04:00
Ryan Houdek 90702b4102 LinuxSyscalls/Threads: Fixes typo in long jump handler
PR #4892 already found this, but since that isn't merged, make sure this
typo is fixed at least.
2025-10-27 14:05:11 -07:00
Ryan Houdek 0cf105b64a Merge pull request #4993 from Sonicadvance1/moar_stats
FEXCore: Adds some more per-thread stats.
2025-10-27 13:46:12 -07:00
Ryan Houdek 5c74d9458c FEXCore: Have non-repeat strings operations listen to non-tso config
This was missed before, where the non-repeating strings instructions
were still using TSO even when the memcpy/set config option was
disabled. Make sure it listens to the config option and disable TSO in
those instances.

Noticed this while profiling Dishonored, and WINE's `sse2_memmove`
function was showing up as a high amount of CPU time. This is due to
them using non-repeating string operations on the header and tail of
their memmove to align to 16-byte.

With this fixed, it causes the game to go from ~62FPS to ~67FPS,
becoming bottlenecked by x87 emulation instead of memmove. Doing about
23 million soft-float operations per second, because it needs full
precision to remove some flickering artifacts.
2025-10-27 13:05:54 -07:00
Ryan Houdek 1ab79bd72e FEXCore: Adds some more per-thread stats.
- Cache miss counts
  - Useful for determining if L2 cache or dynamic cache could help
- Cache read/write lock contention times
  - Useful to see if threads are blocking each other on contention
  - Read lock is the case where a read-lock is beneficial, even if we
    currently use a write lock.
- JIT count
  - Useful to see if any new JIT blocks are generating

On top of #4951 because it fiddles with the cache stuff.
2025-10-27 11:25:59 -07:00
Tony Wasserka d716e22476 Merge pull request #4997 from Sonicadvance1/delete_bad_flags
FEXCore: Remove ABILocalFlags hack
2025-10-27 09:33:35 +01:00
Ryan Houdek bbb8e1ccab FEXCore: Remove ABILocalFlags hack
With our flags being optimized, this does even less than when it was
introduced. It's a hack, people are tinkering with it thinking it'll do
something. Get rid of it.
2025-10-24 17:34:57 -07:00
Ryan Houdek 96f20779c6 Merge pull request #4951 from Sonicadvance1/dynamically_delicious
LookupCache: Adds an option to dynamically scale L1 cache
2025-10-24 14:01:18 -07:00
Ryan Houdek 908313e378 Merge pull request #4996 from cjacek/stlxr-xzr
Arm64: Fix XZR register handling in ARM64EC unaligned STLXR emulation
2025-10-24 13:57:22 -07:00
Jacek Caban 12e5c60633 Arm64: Fix XZR register handling in ARM64EC unaligned STLXR emulation 2025-10-24 22:38:42 +02:00
Ryan Houdek 11946ffc4c InstcountCI: Update 2025-10-24 11:11:54 -07:00
Ryan Houdek f44cd9c545 LookupCache: Adds an option to dynamically scale L1 cache
L1 cache residency can get quite large. Solution, start out small and
scale quickly on L1 cache misses but L2/L3 cache hits.

Some stats on L1 cache residency change:
- Teardown: 40MB -> 16MB (40%)
- Ender Lilies: 79MB -> 32MB (40.5%)
- Death Stranding: 186MB -> 93MB (50%)
- Steam: 75MB -> 7MB (9.3%)

The cost of this option is effectively free in our JIT. It changes a
single LDR to be a single LDP, which on Cortex CPUs cost the same. We do
this by moving the L1 pointer mask in to the CPUState object, making it
dynamic so it lives next to the L1 pointer. We then use that directly
rather than having the hardcoded value.

The lookup cache does a little bit of additional tracking and heuristics
to determine when the current L1 cache should increase or decrease in
size. From 128KB to 16MB per thread, allocating the full VA range as
previously.

Once the heuristic determines that L1 should be increased, it simply
changes the max and the L1 pointer size to compensate, the kernel will
fault in whichever pages are necessary.

Decreasing the size is a little bit more complex, as we want to madvise
the resulting L1 range to ensure we don't have that memory as resident
anymore. Same heuristic but going in the opposite direction otherwise.

Tends to be the case that L1 cache increases a bit on loading screens
then backs down once in-game.

These heuristic values are exposed for increasing and decreasing because
while I think I've picked reasonable values, we will likely need some
more fine tuning over time. Kind of expert user toggles at that point.

Based on #4940 as a base which needs to be merged first.

Full tracked stats from steam as an example of where we are:
```
Total (1000 millisecond sample period):
       JIT Time: 0.486630 ms/second (0.00 percent)
    Signal Time: 0.065880 ms/second (0.00 percent)
     SIGBUS Cnt: 38 (38.160780 per second)
        SMC Cnt: 0
  Softfloat Cnt: 0
FEX JIT Load: 0.004585 (cycles: 552510)
Total FEX Anon memory resident: 368 mB
    JIT resident:             95 mB
    OpDispatcher resident:    38 mB
    Frontend resident:        8 mB
    CPUBackend resident:      624 kB
    Lookup cache resident:    0 (null)
    Lookup L1 cache resident: 7 mB
    ThreadStates resident:    460 kB
    Unaccounted resident:     217 mB
```
2025-10-24 11:11:54 -07:00
Tony Wasserka 9c5ccb13de Merge pull request #4994 from lioncash/cast
SpinWaitLock, etc: Make use of std::atomic_ref over reinterpret_cast
2025-10-23 17:43:36 +02:00
Lioncache e9bcfd4784 Thread: Make use of std::atomic_ref over cast 2025-10-22 11:07:02 -04:00
Lioncache fd1e8d4566 Arm64: Make use of std::atomic_ref over cast 2025-10-22 11:01:50 -04:00
Lioncache 68dcce0739 SpinWaitLock: Make use of std::atomic_ref over cast
Has a more well-defined way of applying atomic operations to values.
2025-10-22 10:32:49 -04:00
LC 3c554cd787 Merge pull request #4992 from Sonicadvance1/Remove_the_paranoia
FEXCore: Remove Paranoid TSO mode.
2025-10-22 10:05:48 -04:00
LC 9e5f2269d9 Merge pull request #4990 from Sonicadvance1/gettls
wow64/arm64ec: Call GetTLS less frequently
2025-10-21 23:53:36 -04:00
Ryan Houdek 81fc502c6c FEXCore: Remove Paranoid TSO mode.
This mode has been broken for a long time because it's mostly untested.
Barriers, and backpatching while slow have proven that they work.
Maintain the one TSO path, at least until all ARM hardware gains support for
x86-TSO memory model mode.
2025-10-21 10:53:34 -07:00
LC eda8ca5449 Merge pull request #4984 from Sonicadvance1/shm_guaranteed_or_your_money_back
SHMStats: Add a 16-byte alignment guarantee
2025-10-21 13:46:53 -04:00
Tony Wasserka 35dd8972e2 Merge pull request #4991 from Sonicadvance1/4096
Removes some hardcoded 4096 constants
2025-10-21 18:28:06 +02:00
Ryan Houdek 643dd56b74 FEX: Removes sone hardcoded 4096 constants
Use our defined variable instead.
2025-10-21 09:18:07 -07:00
Ryan Houdek b748eab4ed FEXCore: Removes some hardcoded 4096 constants
Use our defined variable instead.
2025-10-21 09:18:06 -07:00
Ryan Houdek f4eaab6977 Merge pull request #4971 from Sonicadvance1/fix_fixington 2025-10-21 06:16:43 -07:00
Ryan Houdek 900c114831 Revert "TestHarnessRunner: Avoid frontend SMC handling"
This reverts commit 2556acb82d.
2025-10-20 16:46:38 -07:00
Ryan Houdek 7937b7e52d unittests: Fixes mixture of code and data in the same page
Test behaviour themselves not changed at all, just data moved or
aligned.

For tests that aren't explicitly testing out SMC behaviour, we were
accidentally relying on some aggressive SMC tracking by mixing data and
code in the same page. To fix this just align the test's data to the
next page boundary which means FEX's SMC tracking won't get triggered
since it is no longer living in the same page.

This has been a thorn for a while, so just get rid of it. We obviously
still have ASM tests that still exist that /do/ rely on SMC, and those
are still expected to work.
2025-10-20 16:46:36 -07:00
Ryan Houdek b40e707771 wow64/arm64ec: Call GetTLS less frequently
If called back-to-back, the compiler can't optimize the object creation
resulting in multiple indirections. Save the creation and pass it
around, allowing the compiler to merge loadstores, and remove redundant
loads.

NFC
2025-10-20 15:45:31 -07:00
Ryan Houdek edde5c8516 Merge pull request #4986 from Sonicadvance1/disable_trace_profiler_default
FEX: Disable trace profiler by default
2025-10-20 12:29:39 -07:00
Ryan Houdek 197facb845 Merge pull request #4980 from neobrain/fix_infer_mapping_base
LinuxSyscalls: Fix base address inference for ELF binaries
2025-10-20 10:26:20 -07:00
Ryan Houdek ca697d0d5d FEX: Disable trace profiler by default
Use a config option to turn it on.
2025-10-20 10:25:17 -07:00
Tony Wasserka 32ddf790d5 LinuxSyscalls: Skip ELF checks for non-header mappings
The full consistency check is only ran in assertion builds now.
2025-10-20 18:00:25 +02:00
Tony Wasserka 2cfba8c6d0 LinuxSyscalls: Fix LookupExecutableFileSection implementation
The file offset of a file mapping doesn't necessarily match its address
offset in virtual memory from the base file mapping. Indeed, most libraries
violate this assumption.

Now that the MappedResource::FirstVMA reliably identifies the base memory
mapping for a given library (even when that library is mapped multiple times),
this can easily be fixed.
2025-10-20 18:00:25 +02:00
Tony Wasserka cf6c0765fa LinuxSyscalls: Create separated MappedResources for re-mappings of the same ELF file
PE/ELF binaries are sometimes mapped multiple times in the same process.
If this happens, there is no longer a unique base virtual address per file.
This breaks assumptions required for code caching: Any time a file mapping
is created, FEX must be able to unambiguously determine the base virtual
address of the mapped library.

This becomes possible by creating a separate MappedResource each time an
ELF header is re-mapped.
2025-10-20 18:00:25 +02:00
Tony Wasserka ad93f27271 Common: Add helper function to find the base virtual address corresponding to an mmap() call 2025-10-20 18:00:25 +02:00
Tony Wasserka 92428e5cbc LinuxSyscalls: Use a multimap to store MappedResources
This allows for creating separate MappedResources when an ELF file is mapped
multiple times at different base addresses.
2025-10-20 18:00:25 +02:00
Tony Wasserka 02c00a87dd LinuxSyscalls: Track MappedResources only for regular files that are executable 2025-10-20 18:00:25 +02:00
Tony Wasserka 6c77bc12c1 LinuxSyscalls: Don't skip MappedResource creation on path query failure
MarkGuestExecutableRange requires a MappedResource to be available even when
FEX doesn't do anything else with it.
2025-10-20 18:00:25 +02:00
LC fbb428c249 Merge pull request #4988 from Sonicadvance1/fix_4982
OpcodeDispatcher: Fix #4982
2025-10-17 23:01:54 -04:00
Ryan Houdek 854a741ea4 OpcodeDispatcher: Fix #4982
Forgot to move the OpcodeDispatcher
2025-10-17 14:01:47 -07:00
Ryan Houdek ed1952a79a Merge pull request #4983 from Sonicadvance1/detect_partial_decode
Frontend: Detect partial decoded instructions
2025-10-17 13:19:27 -07:00
Ryan Houdek 39e8f5122f Merge pull request #4982 from Sonicadvance1/fix_fex_conflict
OpcodeDispatcher: Move FEX reserved instruction
2025-10-17 11:23:09 -07:00
Ryan Houdek 6ee77eb1a1 SHMStats: Adds ThreadStats size to header
Reused the padding area so the header format doesn't change. If it is
non-zero then it should be used by the tool.
2025-10-16 14:07:29 -07:00
Ryan Houdek 46fc45b952 SHMStats: Add a 16-byte alignment guarantee
We want to take advantage of 16-byte single-copy atomicity. Which I am
relying on, but didn't codify it the first time.

Additionally add comments to explain that new members should be added to
the end to allow tools time to gain support gradually. This will allow
me to add new members without fully breaking mangohud, they'll just not
display the new information until support is added.

We're not guaranteeing backwards compatibility, just an attempt not to
constantly churn the format unless necessary. This way if we do break
compatibility, the tool will have an upper bound on supported versions
before needing to rewrite code.
2025-10-16 13:58:31 -07:00
Ryan Houdek 79d90f3c7f unittests: Adds a test to check for partial decode
Known failure so added to the known failure list.
2025-10-16 13:17:16 -07:00
Ryan Houdek eb41cb2261 Frontend: Detect partial decoded instructions
Currently FEX doesn't properly support partial decoded instructions,
which behave slightly differently than full noexec or invalid
instruction decodings. Before this commit we didn't even have a way to
detect the difference.

Primary difference is that the faulting RIP is the beginning of
instruction decode, while the fault address is the first byte that
couldn't be fetched due to memory permissions. This shows up as a
difference between the RIP in mcontext and si_addr in siginfo in the
Linux signal handler.

Right now just change the log so we can determine if we need to support
this edge case.
2025-10-16 13:14:26 -07:00
LC 87e8a9b6aa Merge pull request #4979 from Sonicadvance1/fix_fexpidof_crash
FEXpidof: Fixes potential crash
2025-10-16 14:45:29 -04:00
Ryan Houdek 69f5aa8e35 Merge pull request #4981 from pmatos/revert/nan-work
Revert quiet/signaling nan detection
2025-10-16 11:23:17 -07:00
Ryan Houdek d654f55c3c OpcodeDispatcher: Move FEX reserved instruction
This now conflicts with an SMX instruction, so move it over to another
bytecode that is unlikely to be used.
2025-10-16 11:20:58 -07:00
Paulo Matos eb1689e79f instcountci: Revert Fix quiet and signalling nan propagation 2025-10-16 09:11:01 +02:00
Paulo Matos cf6472df90 Revert "asm_tests: Fix quiet and signalling nan propagation"
This reverts commit c480b0ba41.
2025-10-16 08:53:58 +02:00
Paulo Matos bc6295a78d Revert "Fix quiet and signalling nan propagation"
This reverts commit e7a47a647c.
2025-10-16 08:53:44 +02:00
Ryan Houdek ed5774b88f Merge pull request #4945 from Sonicadvance1/cachy_fexos
FEXCore: Adds option to disable L2 cache lookups
2025-10-15 14:08:14 -07:00
Ryan Houdek 40a29ca9a7 FEXCore: Adds option to disable L2 cache lookups
This saves a whole bunch of memory. Cutting `Just Cause 2`'s title
screen from 1132MB anonymous FEX memory down to 438MB. 629MB in L2
alone.

L2 is primarily a means to reduce overhead in map queries, so it's all
about performance. But because it consumes a lot of people it's kind of
hard.

One idea is that the L2 lookups can be moved to shared data structures,
since we already pull the shared lock when doing an L2 lookup this is
already halfway there.

Side note, we're using unique locks even with read-only code paths
which we can't use the shared lock because this terrible recursive
mutex!

Instead of outright changing L2 behaviour and potentially wrecking
havoc, add a config option for now so testing can happen over time.

before:
```
Total FEX Anon memory resident: 1132 mB
    JIT resident:             60 mB
    OpDispatcher resident:    97 mB
    Frontend resident:        37 mB
    CPUBackend resident:      500 kB
    Lookup cache resident:    629 mB
    Lookup L1 cache resident: 108 mB
    ThreadStates resident:    436 kB
```

after:
```
Total FEX Anon memory resident: 438 mB
    JIT resident:             62 mB
    OpDispatcher resident:    56 mB
    Frontend resident:        22 mB
    CPUBackend resident:      496 kB
    Lookup cache resident:    0 (null)
    Lookup L1 cache resident: 109 mB
    ThreadStates resident:    436 kB
```
2025-10-15 13:22:19 -07:00
Ryan Houdek a1e1838b11 FEXpidof: Fixes potential crash
Trivial fix, if a symlink gets deleted between checking if it is a
symlink versus getting a path of it then it resulted in a crash.
Happened periodically for me.
2025-10-15 11:41:44 -07:00
Ryan Houdek 4731dbab2f Merge pull request #4965 from Sonicadvance1/remove_recursive_brain_waves
FEXCore: Remove the last recursive_mutex
2025-10-15 09:20:58 -07:00
Ryan Houdek f2841ccb5e FEXCore: Remove the last recursive_mutex
Every time I see this recursive mutex I glare at it. Remove the last one
so that we no longer need to deal with it.

The only reason why this recursive mutex still existed today was because
it is fairly intertwined with the ContextImpl and tracing it all was a
pain.

Peel back the layers and follow the idiom to have ContextImpl pull the
write mutex when requiredand pass it through by reference to ensure it stays alive.
This allows us to entirely give rid of the recursive nature of the
mutex, which means that `FindBlock` can eventually be switched over to a
read-lock to improve multiple threads reading the caches at the same
time.

I didn't do that exercise since that can be followed up in a subsequent
PR.
2025-10-15 08:42:36 -07:00
Tony Wasserka 81a474fe12 Merge pull request #4978 from dramforever/fix-unittests-llvm-21
unittests/ThunkLibs: Fix build with LLVM 21
2025-10-15 17:36:19 +02:00
dramforever 5af2477d00 unittests/ThunkLibs: Fix build with LLVM 21 2025-10-15 23:04:10 +08:00
Tony Wasserka 4cbba94a29 Merge pull request #4974 from Sonicadvance1/support_deferred_flag
SignalDelegator: Remove bool for deferring signals
2025-10-15 09:15:22 +02:00
Ryan Houdek 2a57428314 Merge pull request #4977 from pmatos/fix/fexserver-socket-uid 2025-10-14 09:07:15 -07:00
Paulo Matos f9ac57205b Fix FEXServer socket resolution
Use getuid() instead of geteuid() when determining FEXServer socket names.
Ensures setuid binaries (like chrome-sandbox from Discord) connect to their parent
user's FEXServer instance instead of trying to spawn a separate server.

Fixes connection errors when running applications that spawn setuid children.
2025-10-14 11:11:24 +02:00
Ryan Houdek 3c6246f99a SignalDelegator: Remove bool for deferring signals
Useful while deferred signals were still a bit more fragile but I
haven't touched this in quite a while no, so just deleted.
2025-10-13 16:47:42 -07:00
Ryan Houdek 072e7bd241 Merge pull request #4973 from pmatos/fix/fcntl64_409
Add F_ADD_SEALS and F_GET_SEALS support to 32-bit fcntl
2025-10-13 11:01:42 -07:00
Ryan Houdek 4080dca816 Merge pull request #4966 from Sonicadvance1/name_remaining
FEX: Name remaining allocations as "Misc"
2025-10-13 10:55:04 -07:00
Ryan Houdek c22fb80129 Merge pull request #4964 from lioncash/vma
SyscallsVMATracking: Make list management internally linked
2025-10-13 10:54:31 -07:00
Paulo Matos 123fcd4bea Add F_ADD_SEALS and F_GET_SEALS support to 32-bit fcntl
Fixes crash with "Unhandled fcntl64: 0x409"
Seen with steam running Bayonetta with Proton Experimental.
2025-10-13 15:27:33 +02:00
Ryan Houdek e0c17672f2 Merge pull request #4970 from bexcran/rdseed-test-fix
unitests/ASM: Fix typo in 09_XX_07.asm
2025-10-11 21:08:39 -07:00
Rebecca Cran 53d484c5f4 unitests/ASM: Fix typo in 09_XX_07.asm
Fix a typo in unittests/ASM/Secondary/09_XX_07.asm which caused it to
jump back to the test_32bit label instead of test_64bit.

On Arm systems with FEAT_RNG support, a significant amount of time may
be required before successive uses of RNDRRS. This is because it returns
a random number with fresh full entropy, and it can take a while to
collect the new entropy. That time may be hundreds or thousands of
instructions, so by jumping back to test_32bit the 64-bit test will
alway fail because an RNDRRS has been executed too recently.

Signed-off-by: Rebecca Cran <rebecca@bsdio.com>
2025-10-11 21:56:41 -06:00
LC a2ea767b77 Merge pull request #4968 from Sonicadvance1/lets_go_questing
InstallFEX:  Add support for 25.10
2025-10-11 14:50:00 -04:00
Ryan Houdek 4bd51a4be0 InstallFEX: Add support for 25.10
The PPA was already updated for this last month.

Fixes #4967
2025-10-11 11:36:32 -07:00
Ryan Houdek 474f2dc267 FEX: Name remaining allocations as "Misc"
This captures the remaining FEX allocations that /aren't/ coming from
JEMalloc, allowing us to separate our mapped regions versus just
jemalloc allocations.

With some additional naming in jemalloc (which I'm not adding here) this
gets us interesting results:
```
        Misc resident:        54 MiB
    JEMalloc resident:        208 MiB
```

So 208MB of active jemalloc allocations in this particular case. These will be able to be tracked in heaptrack-like applications if careful.
This should let us target down whatever live allocations we're keeping
large amounts of data around if possible.
2025-10-10 17:34:03 -07:00
Ryan Houdek 033310234e Merge pull request #4963 from lioncash/src
SyscallHandler: Avoid self-referencing global
2025-10-10 12:44:45 -07:00
Lioncache 10835c4b62 SyscallsVMATracking: Make list management internally linked
These don't require being bound to class state directly, and so the list
management can be completely opaque to the outside (also means less
rebuilding if these change)
2025-10-10 15:36:46 -04:00
Lioncache 3e741df77b SyscallHandler: Avoid self-referencing global
We don't need to indirect through the handler global inside of the class
itself for instance member functions.
2025-10-10 15:29:29 -04:00
Ryan Houdek 31d4b4a201 Merge pull request #4960 from Sonicadvance1/enable_avx_32bit
HostFeatures: Enable AVX for 32-bit by default
2025-10-10 10:31:55 -07:00
Ryan Houdek 678985cbf5 Merge pull request #4962 from lioncash/json
JSONPool: Make allocator funcs internally linked
2025-10-09 21:39:17 -07:00
Lioncache e83e9cb6b3 JSONPool: Make allocator funcs internally linked
Makes it a little more explicit that these aren't used outside of this
TU.
2025-10-10 00:11:17 -04:00
Ryan Houdek 48a51679b8 Merge pull request #4961 from lioncash/str
FileManagement: Minor string churn avoiding
2025-10-09 19:29:06 -07:00
Lioncache 987fc925ee FileManagement: Use emplace instead of insert for set
These are cases where the string can be directly constructed internally
instead of being copied/moved.
2025-10-09 22:02:06 -04:00
Lioncache b6cbb23781 FileManagement: Make use of std::string_view in LoadThunkDatabase()
Same behavior, but avoids allocating except for the case where its
necessary.
2025-10-09 22:02:03 -04:00
Ryan Houdek cbb9017c12 Merge pull request #4940 from cjacek/unaligned-ec
ARM64EC: Emulate unaligned atomic access in non-JIT EC code
2025-10-09 15:41:10 -07:00
Ryan Houdek 832d1e8d2c HostFeatures: Enable AVX for 32-bit by default
With the gather overflow fixes in place, I've been having this running
for a while. Now that we just kicked out a release, enable AVX even on
32-bit.

We'll need to eventually create a list of games that explode with AVX
enabled, but that same list would match what happens on real x86 hosts,
so there can be some collaboration there.
2025-10-09 15:18:26 -07:00
Ryan Houdek 34af7f942b unittests/32Bit_ASM: Adds test for VEX.W bug 2025-10-09 15:18:26 -07:00
Ryan Houdek 5f390c16be OpcodeDispatcher: Fixes Scalar FMA size calculation
The frontend did a quirky widening check which was accidentally working
in this case, but it is supposed to be for the couple of GPR handling
AVX instructions.

Correct the implementation to use the correct register size for FMA.
2025-10-09 15:18:26 -07:00
Jacek Caban f36ac0498d Arm64: Emulate LDAXR/STLXR instructions in non-JIT ARM64EC code 2025-10-10 00:14:48 +02:00
Jacek Caban 18bdd9b665 Arm64: Factor out DoCAS 2025-10-10 00:13:57 +02:00
Jacek Caban 5fd3852fd3 ARM64EC: Emulate unaligned atomic access in non-JIT EC code 2025-10-10 00:13:56 +02:00
Ryan Houdek 764432c77c Merge pull request #4959 from lioncash/config
Config: Prevent duplicate lookup in EnvLoader::Load()
2025-10-09 12:09:26 -07:00
Lioncache 55c43229ca Config: Remove unnecessary check in GetConfigDirectory()
This is already checked for in the outer if.
2025-10-09 14:57:00 -04:00
Lioncache dbc4daaaa4 Config: std::move string into EnvMap
Same behavior, but without a reallocation.
2025-10-09 14:57:00 -04:00
Lioncache acdbbec78d Config: Prevent duplicate lookup in EnvLoader::Load()
We can check the iterator itself instead of doing the lookup again.
2025-10-09 14:56:57 -04:00
Ryan Houdek 3247e777c0 Merge pull request #4958 from lioncash/move
Core: Add missing std::move in AddForceTSOInformation()
2025-10-09 09:48:44 -07:00
Lioncache c6e60ff3f5 Core: Add missing std::move in AddForceTSOInformation()
All callsites move the instructions into the function, but we weren't
further passing the rvalue-reference to merge().
2025-10-09 12:36:44 -04:00
Ryan Houdek b7df102919 Merge pull request #4957 from lioncash/bitwise
OpcodeDispatcher: Amend wonky bitwise AND usage in LoadMemPairAutoTSO/_StoreMemPairAutoTSO
2025-10-09 09:03:41 -07:00
Lioncache f5d450b95c OpcodeDispatcher: Amend wonky bitwise AND usage in LoadMemPairAutoTSO/_StoreMemPairAutoTSO 2025-10-09 11:39:17 -04:00
Ryan Houdek 972aaf9f5f Merge pull request #4956 from lioncash/init 2025-10-09 07:49:23 -07:00
Lioncache b98d5f30c0 RegisterAllocationPass: Ensure relevant members are initialized
Ensures that they have deterministic values on construction
2025-10-09 04:17:21 -04:00
Ryan Houdek d2e2e189d8 Merge pull request #4955 from lioncash/bpf
BPFEmitter: Minor cleanup
2025-10-08 22:27:41 -07:00
Lioncache 17d110575c BPFEmitter: Remove unused SeccopEmulator include 2025-10-09 00:57:49 -04:00
Lioncache a967976420 BPFEmitter: Simplify iterator declarations
Makes the jump labels a little quicker to read.
2025-10-09 00:57:49 -04:00
Lioncache 7e1e1ccccc BPFEmitter: Move private API into implementation
Keeps some internals fully private (and also avoids dumping
some defines into other headers).
2025-10-09 00:57:49 -04:00
Ryan Houdek 502452602e Merge pull request #4954 from lioncash/parser
ELFParser: Avoid unnecessary copies
2025-10-08 21:42:20 -07:00
LC f8ff46f3e3 Merge pull request #4952 from Sonicadvance1/naming_block_links
FEXCore/fexl: Support a named monotonic_buffer_resource
2025-10-08 23:04:41 -04:00
LC 8f572deaed Merge pull request #4953 from Sonicadvance1/remove_inlinesyscall
FEXCore: Remove InlineSyscall
2025-10-08 23:02:40 -04:00
Ryan Houdek 601dd1d2b5 InstcountCI: Update 2025-10-08 19:33:58 -07:00
Ryan Houdek 673e826e46 FEX: Remove InlineSyscall and related flags
FEXCore no longer optimizes syscalls to be inline.

NFC, just avoids passing around a bunch of data structures for no
reason.
2025-10-08 19:33:58 -07:00
Ryan Houdek f1f81f9de2 FEXCore: Remove InlineSyscall
Due to IR changes we can no longer do this, its use was fairly limited
anyway.
2025-10-08 18:47:18 -07:00
Lioncache 8513c02ec1 ELFParser: Avoid unnecessary copies
Gets rid of a few 56 byte copies.
2025-10-08 21:20:46 -04:00
Ryan Houdek 282f091e85 FEXCore/fexl: Support a named monotonic_buffer_resource
Lets us track our memory usage of our block links.
2025-10-08 16:43:28 -07:00
Ryan Houdek 8647033029 FEXCore: Support naming a bunch of VMA regions
Useful for memory usage tracking.
2025-10-08 16:43:28 -07:00
580 changed files with 34030 additions and 41528 deletions

No files matched your search

+47 -29
View File
@@ -36,24 +36,33 @@ public:
DataProcessing_PCRel_Imm(Op, rd, Imm); DataProcessing_PCRel_Imm(Op, rd, Imm);
} }
void adr(ARMEmitter::Register rd, const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded adr(ARMEmitter::Register rd, const BackwardLabel* Label) {
int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>()); int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>());
LOGMAN_THROW_A_FMT(IsADRRange(Imm), "Unscaled offset too large"); LOGMAN_THROW_A_FMT(IsADRRange(Imm), "Unscaled offset too large");
constexpr uint32_t Op = 0b0001'0000 << 24; if (IsADRRange(Imm)) [[likely]] {
DataProcessing_PCRel_Imm(Op, rd, Imm); constexpr uint32_t Op = 0b0001'0000 << 24;
DataProcessing_PCRel_Imm(Op, rd, Imm);
return BranchEncodeSucceeded::Success;
}
// Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void adr(ARMEmitter::Register rd, ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded adr(ARMEmitter::Register rd, ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::ADR}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::ADR});
constexpr uint32_t Op = 0b0001'0000 << 24; constexpr uint32_t Op = 0b0001'0000 << 24;
DataProcessing_PCRel_Imm(Op, rd, 0); DataProcessing_PCRel_Imm(Op, rd, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void adr(ARMEmitter::Register rd, BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded adr(ARMEmitter::Register rd, BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
adr(rd, &Label->Backward); return adr(rd, &Label->Backward);
} else { } else {
adr(rd, &Label->Forward); return adr(rd, &Label->Forward);
} }
} }
@@ -62,32 +71,42 @@ public:
DataProcessing_PCRel_Imm(Op, rd, Imm); DataProcessing_PCRel_Imm(Op, rd, Imm);
} }
void adrp(ARMEmitter::Register rd, const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded adrp(ARMEmitter::Register rd, const BackwardLabel* Label) {
int64_t Imm = reinterpret_cast<int64_t>(Label->Location) - (GetCursorAddress<int64_t>() & ~0xFFFLL); int64_t Imm = reinterpret_cast<int64_t>(Label->Location) - (GetCursorAddress<int64_t>() & ~0xFFFLL);
LOGMAN_THROW_A_FMT(IsADRPRange(Imm) && IsADRPAligned(Imm), "Unscaled offset too large"); LOGMAN_THROW_A_FMT(IsADRPRange(Imm) && IsADRPAligned(Imm), "Unscaled offset too large");
constexpr uint32_t Op = 0b1001'0000 << 24; if (IsADRPRange(Imm) && IsADRPAligned(Imm)) [[likely]] {
DataProcessing_PCRel_Imm(Op, rd, Imm); constexpr uint32_t Op = 0b1001'0000 << 24;
DataProcessing_PCRel_Imm(Op, rd, Imm);
return BranchEncodeSucceeded::Success;
}
// Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void adrp(ARMEmitter::Register rd, ForwardLabel* Label) {
[[nodiscard]] BranchEncodeSucceeded adrp(ARMEmitter::Register rd, ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::ADRP}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::ADRP});
constexpr uint32_t Op = 0b1001'0000 << 24; constexpr uint32_t Op = 0b1001'0000 << 24;
DataProcessing_PCRel_Imm(Op, rd, 0); DataProcessing_PCRel_Imm(Op, rd, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void adrp(ARMEmitter::Register rd, BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded adrp(ARMEmitter::Register rd, BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
adrp(rd, &Label->Backward); return adrp(rd, &Label->Backward);
} else { } else {
adrp(rd, &Label->Forward); return adrp(rd, &Label->Forward);
} }
} }
void LongAddressGen(ARMEmitter::Register rd, const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded LongAddressGen(ARMEmitter::Register rd, const BackwardLabel* Label) {
int64_t Imm = reinterpret_cast<int64_t>(Label->Location) - (GetCursorAddress<int64_t>()); int64_t Imm = reinterpret_cast<int64_t>(Label->Location) - (GetCursorAddress<int64_t>());
if (IsADRRange(Imm)) { if (IsADRRange(Imm)) {
// If the range is in ADR range then we can just use ADR. // If the range is in ADR range then we can just use ADR.
adr(rd, Label); return adr(rd, Label);
} else if (IsADRPRange(Imm)) { } else if (IsADRPRange(Imm)) {
int64_t ADRPImm = (reinterpret_cast<int64_t>(Label->Location) & ~0xFFFLL) - (GetCursorAddress<int64_t>() & ~0xFFFLL); int64_t ADRPImm = (reinterpret_cast<int64_t>(Label->Location) & ~0xFFFLL) - (GetCursorAddress<int64_t>() & ~0xFFFLL);
@@ -102,23 +121,28 @@ public:
// Now even an add // Now even an add
add(ARMEmitter::Size::i64Bit, rd, rd, AlignedOffset); add(ARMEmitter::Size::i64Bit, rd, rd, AlignedOffset);
} }
} else {
LOGMAN_MSG_A_FMT("Unscaled offset too large"); return BranchEncodeSucceeded::Success;
FEX_UNREACHABLE;
} }
// Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void LongAddressGen(ARMEmitter::Register rd, ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded LongAddressGen(ARMEmitter::Register rd, ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::LONG_ADDRESS_GEN}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::LONG_ADDRESS_GEN});
// Emit a register index and a nop. These will be backpatched. // Emit a register index and a nop. These will be backpatched.
dc32(rd.Idx()); dc32(rd.Idx());
nop(); nop();
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void LongAddressGen(ARMEmitter::Register rd, BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded LongAddressGen(ARMEmitter::Register rd, BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
LongAddressGen(rd, &Label->Backward); return LongAddressGen(rd, &Label->Backward);
} else { } else {
LongAddressGen(rd, &Label->Forward); return LongAddressGen(rd, &Label->Forward);
} }
} }
@@ -862,12 +886,6 @@ public:
} }
private: private:
static constexpr Condition InvertCondition(Condition cond) {
// These behave as always, so it makes no sense to allow inverting these.
LOGMAN_THROW_A_FMT(cond != Condition::CC_AL && cond != Condition::CC_NV, "Cannot invert CC_AL or CC_NV");
return static_cast<Condition>(FEXCore::ToUnderlying(cond) ^ 1);
}
void and_(ARMEmitter::Size s, ARMEmitter::Register rd, ARMEmitter::Register rn, uint32_t n, uint32_t immr, uint32_t imms) { void and_(ARMEmitter::Size s, ARMEmitter::Register rd, ARMEmitter::Register rn, uint32_t n, uint32_t immr, uint32_t imms) {
constexpr uint32_t Op = 0b001'0010'00 << 22; constexpr uint32_t Op = 0b001'0010'00 << 22;
DataProcessing_Logical_Imm(Op, s, rd, rn, n, immr, imms); DataProcessing_Logical_Imm(Op, s, rd, rn, n, immr, imms);
+123 -63
View File
@@ -20,23 +20,31 @@ public:
constexpr uint32_t Op = 0b0101'010 << 25; constexpr uint32_t Op = 0b0101'010 << 25;
Branch_Conditional(Op, 0, 0, Cond, Imm); Branch_Conditional(Op, 0, 0, Cond, Imm);
} }
void b(ARMEmitter::Condition Cond, const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded b(ARMEmitter::Condition Cond, const BackwardLabel* Label) {
int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>()); int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>());
LOGMAN_THROW_A_FMT(Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0), "Unscaled offset too large"); if (Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0)) [[likely]] {
constexpr uint32_t Op = 0b0101'010 << 25; constexpr uint32_t Op = 0b0101'010 << 25;
Branch_Conditional(Op, 0, 0, Cond, Imm >> 2); Branch_Conditional(Op, 0, 0, Cond, Imm >> 2);
return BranchEncodeSucceeded::Success;
}
// Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void b(ARMEmitter::Condition Cond, ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded b(ARMEmitter::Condition Cond, ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::BC}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::BC});
constexpr uint32_t Op = 0b0101'010 << 25; constexpr uint32_t Op = 0b0101'010 << 25;
Branch_Conditional(Op, 0, 0, Cond, 0); Branch_Conditional(Op, 0, 0, Cond, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void b(ARMEmitter::Condition Cond, BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded b(ARMEmitter::Condition Cond, BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
b(Cond, &Label->Backward); return b(Cond, &Label->Backward);
} else { } else {
b(Cond, &Label->Forward); return b(Cond, &Label->Forward);
} }
} }
@@ -45,24 +53,32 @@ public:
constexpr uint32_t Op = 0b0101'010 << 25; constexpr uint32_t Op = 0b0101'010 << 25;
Branch_Conditional(Op, 0, 1, Cond, Imm); Branch_Conditional(Op, 0, 1, Cond, Imm);
} }
void bc(ARMEmitter::Condition Cond, const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded bc(ARMEmitter::Condition Cond, const BackwardLabel* Label) {
int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>()); int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>());
LOGMAN_THROW_A_FMT(Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0), "Unscaled offset too large"); if (Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0)) [[likely]] {
constexpr uint32_t Op = 0b0101'010 << 25; constexpr uint32_t Op = 0b0101'010 << 25;
Branch_Conditional(Op, 0, 1, Cond, Imm >> 2); Branch_Conditional(Op, 0, 1, Cond, Imm >> 2);
return BranchEncodeSucceeded::Success;
}
// Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void bc(ARMEmitter::Condition Cond, ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded bc(ARMEmitter::Condition Cond, ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::BC}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::BC});
constexpr uint32_t Op = 0b0101'010 << 25; constexpr uint32_t Op = 0b0101'010 << 25;
Branch_Conditional(Op, 0, 1, Cond, 0); Branch_Conditional(Op, 0, 1, Cond, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void bc(ARMEmitter::Condition Cond, BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded bc(ARMEmitter::Condition Cond, BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
bc(Cond, &Label->Backward); return bc(Cond, &Label->Backward);
} else { } else {
bc(Cond, &Label->Forward); return bc(Cond, &Label->Forward);
} }
} }
@@ -98,25 +114,32 @@ public:
UnconditionalBranch(Op, Imm); UnconditionalBranch(Op, Imm);
} }
void b(const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded b(const BackwardLabel* Label) {
int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>()); int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>());
LOGMAN_THROW_A_FMT(Imm >= -134217728 && Imm <= 134217724 && ((Imm & 0b11) == 0), "Unscaled offset too large"); if (Imm >= -134217728 && Imm <= 134217724 && ((Imm & 0b11) == 0)) [[likely]] {
constexpr uint32_t Op = 0b0001'01 << 26; constexpr uint32_t Op = 0b0001'01 << 26;
UnconditionalBranch(Op, Imm >> 2);
return BranchEncodeSucceeded::Success;
}
UnconditionalBranch(Op, Imm >> 2); // Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void b(ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded b(ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::B}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::B});
constexpr uint32_t Op = 0b0001'01 << 26; constexpr uint32_t Op = 0b0001'01 << 26;
UnconditionalBranch(Op, 0); UnconditionalBranch(Op, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void b(BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded b(BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
b(&Label->Backward); return b(&Label->Backward);
} else { } else {
b(&Label->Forward); return b(&Label->Forward);
} }
} }
@@ -126,25 +149,33 @@ public:
UnconditionalBranch(Op, Imm); UnconditionalBranch(Op, Imm);
} }
void bl(const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded bl(const BackwardLabel* Label) {
int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>()); int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>());
LOGMAN_THROW_A_FMT(Imm >= -134217728 && Imm <= 134217724 && ((Imm & 0b11) == 0), "Unscaled offset too large"); if (Imm >= -134217728 && Imm <= 134217724 && ((Imm & 0b11) == 0)) [[likely]] {
constexpr uint32_t Op = 0b1001'01 << 26; constexpr uint32_t Op = 0b1001'01 << 26;
UnconditionalBranch(Op, Imm >> 2);
UnconditionalBranch(Op, Imm >> 2); return BranchEncodeSucceeded::Success;
}
// Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void bl(ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded bl(ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::B}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::B});
constexpr uint32_t Op = 0b1001'01 << 26; constexpr uint32_t Op = 0b1001'01 << 26;
UnconditionalBranch(Op, 0); UnconditionalBranch(Op, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void bl(BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded bl(BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
bl(&Label->Backward); return bl(&Label->Backward);
} else { } else {
bl(&Label->Forward); return bl(&Label->Forward);
} }
} }
@@ -155,28 +186,35 @@ public:
CompareAndBranch(Op, s, rt, Imm); CompareAndBranch(Op, s, rt, Imm);
} }
void cbz(ARMEmitter::Size s, ARMEmitter::Register rt, const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded cbz(ARMEmitter::Size s, ARMEmitter::Register rt, const BackwardLabel* Label) {
int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>()); int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>());
LOGMAN_THROW_A_FMT(Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0), "Unscaled offset too large");
constexpr uint32_t Op = 0b0011'0100 << 24; if (Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0)) [[likely]] {
constexpr uint32_t Op = 0b0011'0100 << 24;
CompareAndBranch(Op, s, rt, Imm >> 2);
return BranchEncodeSucceeded::Success;
}
CompareAndBranch(Op, s, rt, Imm >> 2); // Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void cbz(ARMEmitter::Size s, ARMEmitter::Register rt, ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded cbz(ARMEmitter::Size s, ARMEmitter::Register rt, ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::BC}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::BC});
constexpr uint32_t Op = 0b0011'0100 << 24; constexpr uint32_t Op = 0b0011'0100 << 24;
CompareAndBranch(Op, s, rt, 0); CompareAndBranch(Op, s, rt, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void cbz(ARMEmitter::Size s, ARMEmitter::Register rt, BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded cbz(ARMEmitter::Size s, ARMEmitter::Register rt, BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
cbz(s, rt, &Label->Backward); return cbz(s, rt, &Label->Backward);
} else { } else {
cbz(s, rt, &Label->Forward); return cbz(s, rt, &Label->Forward);
} }
} }
@@ -186,28 +224,35 @@ public:
CompareAndBranch(Op, s, rt, Imm); CompareAndBranch(Op, s, rt, Imm);
} }
void cbnz(ARMEmitter::Size s, ARMEmitter::Register rt, const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded cbnz(ARMEmitter::Size s, ARMEmitter::Register rt, const BackwardLabel* Label) {
int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>()); int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>());
LOGMAN_THROW_A_FMT(Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0), "Unscaled offset too large");
constexpr uint32_t Op = 0b0011'0101 << 24; if (Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0)) [[likely]] {
constexpr uint32_t Op = 0b0011'0101 << 24;
CompareAndBranch(Op, s, rt, Imm >> 2);
return BranchEncodeSucceeded::Success;
}
CompareAndBranch(Op, s, rt, Imm >> 2); // Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void cbnz(ARMEmitter::Size s, ARMEmitter::Register rt, ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded cbnz(ARMEmitter::Size s, ARMEmitter::Register rt, ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::BC}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::BC});
constexpr uint32_t Op = 0b0011'0101 << 24; constexpr uint32_t Op = 0b0011'0101 << 24;
CompareAndBranch(Op, s, rt, 0); CompareAndBranch(Op, s, rt, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void cbnz(ARMEmitter::Size s, ARMEmitter::Register rt, BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded cbnz(ARMEmitter::Size s, ARMEmitter::Register rt, BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
cbnz(s, rt, &Label->Backward); return cbnz(s, rt, &Label->Backward);
} else { } else {
cbnz(s, rt, &Label->Forward); return cbnz(s, rt, &Label->Forward);
} }
} }
@@ -217,28 +262,35 @@ public:
TestAndBranch(Op, rt, Bit, Imm); TestAndBranch(Op, rt, Bit, Imm);
} }
void tbz(ARMEmitter::Register rt, uint32_t Bit, const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded tbz(ARMEmitter::Register rt, uint32_t Bit, const BackwardLabel* Label) {
int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>()); int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>());
LOGMAN_THROW_A_FMT(Imm >= -32768 && Imm <= 32764 && ((Imm & 0b11) == 0), "Unscaled offset too large");
constexpr uint32_t Op = 0b0011'0110 << 24; if (Imm >= -32768 && Imm <= 32764 && ((Imm & 0b11) == 0)) [[likely]] {
constexpr uint32_t Op = 0b0011'0110 << 24;
TestAndBranch(Op, rt, Bit, Imm >> 2);
return BranchEncodeSucceeded::Success;
}
TestAndBranch(Op, rt, Bit, Imm >> 2); // Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void tbz(ARMEmitter::Register rt, uint32_t Bit, ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded tbz(ARMEmitter::Register rt, uint32_t Bit, ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::TEST_BRANCH}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::TEST_BRANCH});
constexpr uint32_t Op = 0b0011'0110 << 24; constexpr uint32_t Op = 0b0011'0110 << 24;
TestAndBranch(Op, rt, Bit, 0); TestAndBranch(Op, rt, Bit, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void tbz(ARMEmitter::Register rt, uint32_t Bit, BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded tbz(ARMEmitter::Register rt, uint32_t Bit, BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
tbz(rt, Bit, &Label->Backward); return tbz(rt, Bit, &Label->Backward);
} else { } else {
tbz(rt, Bit, &Label->Forward); return tbz(rt, Bit, &Label->Forward);
} }
} }
@@ -247,27 +299,35 @@ public:
TestAndBranch(Op, rt, Bit, Imm); TestAndBranch(Op, rt, Bit, Imm);
} }
void tbnz(ARMEmitter::Register rt, uint32_t Bit, const BackwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded tbnz(ARMEmitter::Register rt, uint32_t Bit, const BackwardLabel* Label) {
int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>()); int32_t Imm = static_cast<int32_t>(Label->Location - GetCursorAddress<uint8_t*>());
LOGMAN_THROW_A_FMT(Imm >= -32768 && Imm <= 32764 && ((Imm & 0b11) == 0), "Unscaled offset too large"); LOGMAN_THROW_A_FMT(Imm >= -32768 && Imm <= 32764 && ((Imm & 0b11) == 0), "Unscaled offset too large");
constexpr uint32_t Op = 0b0011'0111 << 24; if (Imm >= -32768 && Imm <= 32764 && ((Imm & 0b11) == 0)) [[likely]] {
constexpr uint32_t Op = 0b0011'0111 << 24;
TestAndBranch(Op, rt, Bit, Imm >> 2);
return BranchEncodeSucceeded::Success;
}
TestAndBranch(Op, rt, Bit, Imm >> 2); // Can't encode.
return BranchEncodeSucceeded::Failure;
} }
void tbnz(ARMEmitter::Register rt, uint32_t Bit, ForwardLabel* Label) { [[nodiscard]] BranchEncodeSucceeded tbnz(ARMEmitter::Register rt, uint32_t Bit, ForwardLabel* Label) {
AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::TEST_BRANCH}); AddLocationToLabel(Label, ForwardLabel::Reference {.Location = GetCursorAddress<uint8_t*>(), .Type = ForwardLabel::InstType::TEST_BRANCH});
constexpr uint32_t Op = 0b0011'0111 << 24; constexpr uint32_t Op = 0b0011'0111 << 24;
TestAndBranch(Op, rt, Bit, 0); TestAndBranch(Op, rt, Bit, 0);
// Forward label doesn't know if it can encode until Bind.
return BranchEncodeSucceeded::Success;
} }
void tbnz(ARMEmitter::Register rt, uint32_t Bit, BiDirectionalLabel* Label) { [[nodiscard]] BranchEncodeSucceeded tbnz(ARMEmitter::Register rt, uint32_t Bit, BiDirectionalLabel* Label) {
if (Label->Backward.Location) { if (Label->Backward.Location) {
tbnz(rt, Bit, &Label->Backward); return tbnz(rt, Bit, &Label->Backward);
} else { } else {
tbnz(rt, Bit, &Label->Forward); return tbnz(rt, Bit, &Label->Forward);
} }
} }
+56 -15
View File
@@ -586,6 +586,15 @@ concept IsXOrWRegister = std::is_same_v<T, XRegister> || std::is_same_v<T, WRegi
template<typename T> template<typename T>
concept IsQOrDRegister = std::is_same_v<T, QRegister> || std::is_same_v<T, DRegister>; concept IsQOrDRegister = std::is_same_v<T, QRegister> || std::is_same_v<T, DRegister>;
template<typename T>
concept IsLabel = std::is_same_v<T, ARMEmitter::ForwardLabel> || std::is_same_v<T, ARMEmitter::BackwardLabel> ||
std::is_same_v<T, ARMEmitter::BiDirectionalLabel> || std::is_same_v<T, ARMEmitter::ForwardLabel::Reference>;
enum class BranchEncodeSucceeded {
Success,
Failure,
};
// Whether or not a given set of vector registers are sequential // Whether or not a given set of vector registers are sequential
// in increasing order as far as the register file is concerned (modulo its size) // in increasing order as far as the register file is concerned (modulo its size)
// //
@@ -638,19 +647,25 @@ public:
// Bind a backward label to an address. // Bind a backward label to an address.
// Address that is bound is the current emitter location. // Address that is bound is the current emitter location.
void Bind(BackwardLabel* Label) { [[nodiscard]] bool Bind(BackwardLabel* Label) {
LOGMAN_THROW_A_FMT(Label->Location == nullptr, "Trying to bind a label twice"); LOGMAN_THROW_A_FMT(Label->Location == nullptr, "Trying to bind a label twice");
Label->Location = GetCursorAddress<uint8_t*>(); Label->Location = GetCursorAddress<uint8_t*>();
// Always binds because it is only storing a location.
return true;
} }
void Bind(const ForwardLabel::Reference* Label) { [[nodiscard]] bool Bind(const ForwardLabel::Reference* Label) {
uint8_t* CurrentAddress = GetCursorAddress<uint8_t*>(); uint8_t* CurrentAddress = GetCursorAddress<uint8_t*>();
// Patch up the instructions // Patch up the instructions
switch (Label->Type) { switch (Label->Type) {
case ForwardLabel::InstType::ADR: { case ForwardLabel::InstType::ADR: {
uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location); uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location);
int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction); int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction);
LOGMAN_THROW_A_FMT(IsADRRange(Imm), "Unscaled offset too large"); if (!IsADRRange(Imm)) [[unlikely]] {
// Can't bind.
return false;
}
uint32_t InstMask = 0b11 << 29 | 0b1111'1111'1111'1111'111 << 5; uint32_t InstMask = 0b11 << 29 | 0b1111'1111'1111'1111'111 << 5;
uint32_t Offset = static_cast<uint32_t>(Imm) & 0x3F'FFFF; uint32_t Offset = static_cast<uint32_t>(Imm) & 0x3F'FFFF;
uint32_t Inst = *Instruction & ~InstMask; uint32_t Inst = *Instruction & ~InstMask;
@@ -662,7 +677,12 @@ public:
case ForwardLabel::InstType::ADRP: { case ForwardLabel::InstType::ADRP: {
uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location); uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location);
int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction); int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction);
LOGMAN_THROW_A_FMT(IsADRPRange(Imm) && IsADRPAligned(Imm), "Unscaled offset too large");
if (!(IsADRPRange(Imm) && IsADRPAligned(Imm))) [[unlikely]] {
// Can't bind.
return false;
}
Imm >>= 12; Imm >>= 12;
uint32_t InstMask = 0b11 << 29 | 0b1111'1111'1111'1111'111 << 5; uint32_t InstMask = 0b11 << 29 | 0b1111'1111'1111'1111'111 << 5;
uint32_t Offset = static_cast<uint32_t>(Imm) & 0x3F'FFFF; uint32_t Offset = static_cast<uint32_t>(Imm) & 0x3F'FFFF;
@@ -672,11 +692,13 @@ public:
*Instruction = Inst; *Instruction = Inst;
break; break;
} }
case ForwardLabel::InstType::B: { case ForwardLabel::InstType::B: {
uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location); uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location);
int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction); int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction);
LOGMAN_THROW_A_FMT(Imm >= -134217728 && Imm <= 134217724 && ((Imm & 0b11) == 0), "Unscaled offset too large"); if (!(Imm >= -134217728 && Imm <= 134217724 && ((Imm & 0b11) == 0))) [[unlikely]] {
// Can't bind.
return false;
}
Imm >>= 2; Imm >>= 2;
uint32_t InstMask = 0x3FF'FFFF; uint32_t InstMask = 0x3FF'FFFF;
uint32_t Offset = static_cast<uint32_t>(Imm) & InstMask; uint32_t Offset = static_cast<uint32_t>(Imm) & InstMask;
@@ -686,11 +708,13 @@ public:
break; break;
} }
case ForwardLabel::InstType::TEST_BRANCH: { case ForwardLabel::InstType::TEST_BRANCH: {
uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location); uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location);
int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction); int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction);
LOGMAN_THROW_A_FMT(Imm >= -32768 && Imm <= 32764 && ((Imm & 0b11) == 0), "Unscaled offset too large"); if (!(Imm >= -32768 && Imm <= 32764 && ((Imm & 0b11) == 0))) [[unlikely]] {
// Can't bind.
return false;
}
Imm >>= 2; Imm >>= 2;
uint32_t InstMask = 0x3FFF; uint32_t InstMask = 0x3FFF;
uint32_t Offset = static_cast<uint32_t>(Imm) & InstMask; uint32_t Offset = static_cast<uint32_t>(Imm) & InstMask;
@@ -704,7 +728,10 @@ public:
case ForwardLabel::InstType::RELATIVE_LOAD: { case ForwardLabel::InstType::RELATIVE_LOAD: {
uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location); uint32_t* Instruction = reinterpret_cast<uint32_t*>(Label->Location);
int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction); int64_t Imm = reinterpret_cast<int64_t>(CurrentAddress) - reinterpret_cast<int64_t>(Instruction);
LOGMAN_THROW_A_FMT(Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0), "Unscaled offset too large"); if (!(Imm >= -1048576 && Imm <= 1048575 && ((Imm & 0b11) == 0))) [[unlikely]] {
// Can't bind.
return false;
}
Imm >>= 2; Imm >>= 2;
uint32_t InstMask = 0x7'FFFF; uint32_t InstMask = 0x7'FFFF;
uint32_t Offset = static_cast<uint32_t>(Imm) & InstMask; uint32_t Offset = static_cast<uint32_t>(Imm) & InstMask;
@@ -753,27 +780,41 @@ public:
} }
default: LOGMAN_MSG_A_FMT("Unexpected inst type in label fixup"); default: LOGMAN_MSG_A_FMT("Unexpected inst type in label fixup");
} }
return true;
} }
// Bind a forward label to a location. // Bind a forward label to a location.
// This walks all the instructions in the label's vector. // This walks all the instructions in the label's vector.
// Then backpatching all instructions that have used the label. // Then backpatching all instructions that have used the label.
void Bind(ForwardLabel* Label) { [[nodiscard]] bool Bind(ForwardLabel* Label) {
bool Bound = true;
if (Label->FirstInst.Location) { if (Label->FirstInst.Location) {
Bind(&Label->FirstInst); Bound &= Bind(&Label->FirstInst);
} }
for (auto& Inst : Label->Insts) { for (auto& Inst : Label->Insts) {
Bind(&Inst); Bound &= Bind(&Inst);
} }
return Bound;
} }
// Bind a bidirectional location to a location. // Bind a bidirectional location to a location.
// Binds both forwards and backwards depending on how the label was used. // Binds both forwards and backwards depending on how the label was used.
void Bind(BiDirectionalLabel* Label) { [[nodiscard]] bool Bind(BiDirectionalLabel* Label) {
bool Bound = true;
if (!Label->Backward.Location) { if (!Label->Backward.Location) {
Bind(&Label->Backward); Bound &= Bind(&Label->Backward);
} }
Bind(&Label->Forward); Bound &= Bind(&Label->Forward);
return Bound;
}
static constexpr Condition InvertCondition(Condition cond) {
// These behave as always, so it makes no sense to allow inverting these.
LOGMAN_THROW_A_FMT(cond != Condition::CC_AL && cond != Condition::CC_NV, "Cannot invert CC_AL or CC_NV");
return static_cast<Condition>(FEXCore::ToUnderlying(cond) ^ 1);
} }
#include <CodeEmitter/VixlUtils.inl> #include <CodeEmitter/VixlUtils.inl>
+1
View File
@@ -66,6 +66,7 @@ set (SRCS
Interface/IR/Passes/RedundantFlagCalculationElimination.cpp Interface/IR/Passes/RedundantFlagCalculationElimination.cpp
Interface/IR/Passes/RegisterAllocationPass.cpp Interface/IR/Passes/RegisterAllocationPass.cpp
Interface/IR/Passes/x87StackOptimizationPass.cpp Interface/IR/Passes/x87StackOptimizationPass.cpp
Utils/LongJump.cpp
Utils/Telemetry.cpp Utils/Telemetry.cpp
Utils/Threads.cpp Utils/Threads.cpp
Utils/Profiler.cpp Utils/Profiler.cpp
+3 -2
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#pragma once #pragma once
#include <FEXCore/Utils/TypeDefines.h>
#include <FEXCore/fextl/memory.h> #include <FEXCore/fextl/memory.h>
@@ -12,7 +13,7 @@ namespace FEXCore {
// Buffered JIT symbol tracking. // Buffered JIT symbol tracking.
struct JITSymbolBuffer { struct JITSymbolBuffer {
// Maximum buffer size to ensure we are a page in size. // Maximum buffer size to ensure we are a page in size.
constexpr static size_t BUFFER_SIZE = 4096 - (8 * 2); constexpr static size_t BUFFER_SIZE = FEXCore::Utils::FEX_PAGE_SIZE - (8 * 2);
// Maximum distance until the end of the buffer to do a write. // Maximum distance until the end of the buffer to do a write.
constexpr static size_t NEEDS_WRITE_DISTANCE = BUFFER_SIZE - 64; constexpr static size_t NEEDS_WRITE_DISTANCE = BUFFER_SIZE - 64;
// Maximum time threshhold to wait before a buffer write occurs. // Maximum time threshhold to wait before a buffer write occurs.
@@ -27,7 +28,7 @@ struct JITSymbolBuffer {
size_t Offset {}; size_t Offset {};
char Buffer[BUFFER_SIZE] {}; char Buffer[BUFFER_SIZE] {};
}; };
static_assert(sizeof(JITSymbolBuffer) == 4096, "Ensure this is one page in size"); static_assert(sizeof(JITSymbolBuffer) == FEXCore::Utils::FEX_PAGE_SIZE, "Ensure this is one page in size");
class JITSymbols final { class JITSymbols final {
public: public:
-74
View File
@@ -504,47 +504,6 @@ struct FEX_PACKED X80SoftFloat {
return std::bit_cast<float>(Result); return std::bit_cast<float>(Result);
} }
bool IsSignalingNaN() const {
return (Exponent == 0x7FFF) && (Significand & 0x8000000000000000ULL) && !(Significand & 0x4000000000000000ULL) && // Bit 62 clear (signaling)
(Significand & 0x3FFFFFFFFFFFFFFFULL);
}
bool IsQuietNaN() const {
return (Exponent == 0x7FFF) && (Significand & 0x8000000000000000ULL) && (Significand & 0x4000000000000000ULL); // Bit 62 set (quiet)
}
// Helper to detect if this is any NaN
bool IsNaN() const {
return IsSignalingNaN() || IsQuietNaN();
}
// X87 value to F64 while preserving signaling nan property
double ToF64_PreserveNan(softfloat_state* state) const {
if (IsSignalingNaN()) {
// we keep it as a signaling nan in ieee754 in 64bits
uint64_t sign_bit = Sign ? 0x8000000000000000ULL : 0;
uint64_t exp_bits = 0x7FF0000000000000ULL;
uint64_t x87_frac = Significand & 0x3FFFFFFFFFFFFFFFULL;
uint64_t ieee_frac = (x87_frac >> 11) & 0x0007FFFFFFFFFFFFULL;
if (ieee_frac == 0) {
ieee_frac = 1;
}
ieee_frac &= ~0x0008000000000000ULL;
uint64_t result_bits = sign_bit | exp_bits | ieee_frac;
return std::bit_cast<double>(result_bits);
} else if (IsQuietNaN()) {
const float64_t Result = extF80_to_f64(state, *this);
uint64_t result_bits = std::bit_cast<uint64_t>(Result);
result_bits |= 0x0008000000000000ULL;
return std::bit_cast<double>(result_bits);
} else {
const float64_t Result = extF80_to_f64(state, *this);
return std::bit_cast<double>(Result);
}
}
double ToF64(softfloat_state* state) const { double ToF64(softfloat_state* state) const {
const float64_t Result = extF80_to_f64(state, *this); const float64_t Result = extF80_to_f64(state, *this);
return std::bit_cast<double>(Result); return std::bit_cast<double>(Result);
@@ -625,39 +584,6 @@ struct FEX_PACKED X80SoftFloat {
*this = f64_to_extF80(state, std::bit_cast<float64_t>(rhs)); *this = f64_to_extF80(state, std::bit_cast<float64_t>(rhs));
} }
// Create X80SoftFloat from double while preserving NaN signaling properties
static X80SoftFloat FromF64_PreserveNaN(softfloat_state* state, double value) {
uint64_t bits = std::bit_cast<uint64_t>(value);
// Check if it's a nan
if ((bits & 0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && (bits & 0x000FFFFFFFFFFFFFULL) != 0) {
X80SoftFloat result;
result.Sign = (bits >> 63) & 1;
result.Exponent = 0x7FFF;
bool is_signaling = !(bits & 0x0008000000000000ULL);
uint64_t ieee_payload = bits & 0x0007FFFFFFFFFFFFULL;
// set bit 63 required for x87
result.Significand = 0x8000000000000000ULL;
if (is_signaling) { // clear bit 62 for signaling nan
result.Significand &= ~0x4000000000000000ULL;
} else { // clear bit 62 for quiet nan
result.Significand |= 0x4000000000000000ULL;
}
// ieee754 51-bit payload -> x87 62-bit payload
result.Significand |= (ieee_payload << 11) & 0x3FFFFFFFFFFFFFFFULL;
return result;
}
// For non-NaN values, use standard conversion
return X80SoftFloat(state, value);
}
X80SoftFloat(softfloat_state* state, BIGFLOAT rhs) { X80SoftFloat(softfloat_state* state, BIGFLOAT rhs) {
#if BIGFLOATSIZE == 16 #if BIGFLOATSIZE == 16
*this = f128_to_extF80(state, std::bit_cast<float128_t>(rhs)); *this = f128_to_extF80(state, std::bit_cast<float128_t>(rhs));
+2
View File
@@ -4,6 +4,8 @@
#ifdef _M_X86_64 #ifdef _M_X86_64
#include <xmmintrin.h> #include <xmmintrin.h>
#include <immintrin.h> #include <immintrin.h>
#else
#include <cstdint>
#endif #endif
namespace FEXCore { namespace FEXCore {
+46 -26
View File
@@ -161,6 +161,44 @@
"Desc": [ "Desc": [
"Allows the user to pass additional arguments to the application" "Allows the user to pass additional arguments to the application"
] ]
},
"DisableL2Cache": {
"Type": "bool",
"Default": "false",
"Desc": [
"Disables FEXCore's JIT L2 cache lookup. Saving memory.",
"Can potentially introduce more stutters."
]
},
"DynamicL1Cache": {
"Type": "bool",
"Default": "false",
"Desc": [
"Switches FEXCore's JIT L1 cache to be dynamically sized. Saving memory.",
"Can potentially introduce more stutters."
]
},
"DynamicL1CacheIncreaseCountHeuristic": {
"Type": "uint64",
"Default": "250",
"Desc": [
"Threshold of lookups per second that the L1 dynamic cache should increase its size.",
"Lower numbers means more aggressive scaling upward to the maximum size.",
"Higher numbers means more conservative scaling, using less memory.",
"Can potentially introduce stutters, more likely the higher the number.",
"Don't have this number smaller than the decrease count!"
]
},
"DynamicL1CacheDecreaseCountHeuristic": {
"Type": "uint64",
"Default": "50",
"Desc": [
"Threshold of lookups per second that the L1 dynamic cache should decrease its size.",
"The higher the number, the more aggressively it reduces the L1 cache size.",
"Lower numbers means more conservative memory savings.",
"Can potentially introduce more stutters, more likely the higher the number.",
"Don't have this number larger than the increase count!"
]
} }
}, },
"Debug": { "Debug": {
@@ -330,6 +368,13 @@
"Enables FEX's low-overhead sampling profile statistics.", "Enables FEX's low-overhead sampling profile statistics.",
"Requires a supported version of Mangohud to see the results" "Requires a supported version of Mangohud to see the results"
] ]
},
"EnableGpuvisProfiling": {
"Type": "bool",
"Default": "false",
"Desc": [
"Enables profiling when FEX was built with the gpuvis profiler backend."
]
} }
}, },
"Hacks": { "Hacks": {
@@ -389,7 +434,7 @@
"Default": "true", "Default": "true",
"Desc": [ "Desc": [
"Use volatile metadata in PE files to inform TSO instructions when available.", "Use volatile metadata in PE files to inform TSO instructions when available.",
"When metadata is unavailable falls back to the currently enabled TSO options." "When metadata is unavailable falls back to the currently enabled TSO options."
] ]
}, },
"X87ReducedPrecision": { "X87ReducedPrecision": {
@@ -399,31 +444,6 @@
"Emulates X87 floating point using 64-bit precision. This reduces emulation accuracy and may result in rendering bugs." "Emulates X87 floating point using 64-bit precision. This reduces emulation accuracy and may result in rendering bugs."
] ]
}, },
"X87StrictReducedPrecision": {
"Type": "bool",
"Default": "false",
"Desc": [
"Enables stricter X87 floating point behavior when X87ReducedPrecision is enabled.",
"Adds additional checks and implementations like NaN propagation for better compatibility."
]
},
"ABILocalFlags": {
"Type": "bool",
"Default": "false",
"Desc": [
"When enabled enables an optimization around flags.",
"Assumes flags are not used across cals.",
"Hand-written assembly can violate this assumption."
]
},
"ParanoidTSO": {
"Type": "bool",
"Default": "false",
"Desc": [
"Makes TSO operations even more strict.",
"Forces vector loadstores to also become atomic."
]
},
"StallProcess": { "StallProcess": {
"Type": "bool", "Type": "bool",
"Default": "false", "Default": "false",
+11 -23
View File
@@ -29,6 +29,7 @@
namespace FEXCore { namespace FEXCore {
class SignalDelegator; class SignalDelegator;
class ThunkHandler; class ThunkHandler;
struct LookupCacheWriteLockToken;
namespace Core { namespace Core {
struct DebugData; struct DebugData;
@@ -61,7 +62,7 @@ struct CustomIRResult {
, Data(Data) {} , Data(Data) {}
}; };
using BlockDelinkerFunc = void (*)(FEXCore::Core::CpuStateFrame* Frame, FEXCore::Context::ExitFunctionLinkData* Record); using BlockDelinkerFunc = void (*)(FEXCore::Context::ExitFunctionLinkData* Record);
constexpr uint32_t TSC_SCALE_MAXIMUM = 1'000'000'000; ///< 1Ghz constexpr uint32_t TSC_SCALE_MAXIMUM = 1'000'000'000; ///< 1Ghz
class CodeCache : public AbstractCodeCache { class CodeCache : public AbstractCodeCache {
@@ -154,10 +155,10 @@ public:
return CodeCache; return CodeCache;
} }
void OnCodeBufferAllocated(CPU::CodeBuffer&) override; void OnCodeBufferAllocated(const std::shared_ptr<CPU::CodeBuffer> &) override;
void ClearCodeCache(FEXCore::Core::InternalThreadState* Thread, bool NewCodeBuffer = true) override; void ClearCodeCache(FEXCore::Core::InternalThreadState* Thread, bool NewCodeBuffer = true) override;
void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState* Thread, InvalidatedEntryAccumulator& Accumulator, uint64_t Start, void InvalidateCodeBuffersCodeRange(uint64_t Start, uint64_t Length) override;
uint64_t Length) override; void InvalidateThreadCachedCodeRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) override;
FEXCore::ForkableSharedMutex& GetCodeInvalidationMutex() override { FEXCore::ForkableSharedMutex& GetCodeInvalidationMutex() override {
return CodeInvalidationMutex; return CodeInvalidationMutex;
} }
@@ -197,7 +198,6 @@ public:
FEX_CONFIG_OPT(TSOEnabled, TSOENABLED); FEX_CONFIG_OPT(TSOEnabled, TSOENABLED);
FEX_CONFIG_OPT(VectorTSOEnabled, VECTORTSOENABLED); FEX_CONFIG_OPT(VectorTSOEnabled, VECTORTSOENABLED);
FEX_CONFIG_OPT(MemcpySetTSOEnabled, MEMCPYSETTSOENABLED); FEX_CONFIG_OPT(MemcpySetTSOEnabled, MEMCPYSETTSOENABLED);
FEX_CONFIG_OPT(ABILocalFlags, ABILOCALFLAGS);
FEX_CONFIG_OPT(SMCChecks, SMCCHECKS); FEX_CONFIG_OPT(SMCChecks, SMCCHECKS);
FEX_CONFIG_OPT(MaxInstPerBlock, MAXINST); FEX_CONFIG_OPT(MaxInstPerBlock, MAXINST);
FEX_CONFIG_OPT(RootFSPath, ROOTFS); FEX_CONFIG_OPT(RootFSPath, ROOTFS);
@@ -205,9 +205,7 @@ public:
FEX_CONFIG_OPT(LibraryJITNaming, LIBRARYJITNAMING); FEX_CONFIG_OPT(LibraryJITNaming, LIBRARYJITNAMING);
FEX_CONFIG_OPT(BlockJITNaming, BLOCKJITNAMING); FEX_CONFIG_OPT(BlockJITNaming, BLOCKJITNAMING);
FEX_CONFIG_OPT(GDBSymbols, GDBSYMBOLS); FEX_CONFIG_OPT(GDBSymbols, GDBSYMBOLS);
FEX_CONFIG_OPT(ParanoidTSO, PARANOIDTSO);
FEX_CONFIG_OPT(x87ReducedPrecision, X87REDUCEDPRECISION); FEX_CONFIG_OPT(x87ReducedPrecision, X87REDUCEDPRECISION);
FEX_CONFIG_OPT(x87StrictReducedPrecision, X87STRICTREDUCEDPRECISION);
FEX_CONFIG_OPT(DisableTelemetry, DISABLETELEMETRY); FEX_CONFIG_OPT(DisableTelemetry, DISABLETELEMETRY);
FEX_CONFIG_OPT(DisableVixlIndirectCalls, DISABLE_VIXL_INDIRECT_RUNTIME_CALLS); FEX_CONFIG_OPT(DisableVixlIndirectCalls, DISABLE_VIXL_INDIRECT_RUNTIME_CALLS);
FEX_CONFIG_OPT(SmallTSCScale, SMALLTSCSCALE); FEX_CONFIG_OPT(SmallTSCScale, SMALLTSCSCALE);
@@ -233,8 +231,6 @@ public:
ContextImpl(const FEXCore::HostFeatures& Features); ContextImpl(const FEXCore::HostFeatures& Features);
static bool ThreadRemoveCodeEntry(FEXCore::Core::InternalThreadState* Thread, uint64_t GuestRIP);
static void ThreadRemoveCodeEntryFromJit(FEXCore::Core::CpuStateFrame* Frame, uint64_t GuestRIP); static void ThreadRemoveCodeEntryFromJit(FEXCore::Core::CpuStateFrame* Frame, uint64_t GuestRIP);
// This is used as a replacement for the SMC writes in the mono callsite backpatcher that avoids atomic operations // This is used as a replacement for the SMC writes in the mono callsite backpatcher that avoids atomic operations
@@ -269,9 +265,9 @@ public:
FEXCore::JITSymbols Symbols; FEXCore::JITSymbols Symbols;
FEXCore::Utils::PooledAllocatorVirtual OpDispatcherAllocator; FEXCore::Utils::PooledAllocatorVirtual OpDispatcherAllocator {"FEXMem_OpDispatcher"};
FEXCore::Utils::PooledAllocatorVirtual FrontendAllocator; FEXCore::Utils::PooledAllocatorVirtual FrontendAllocator {"FEXMem_Frontend"};
FEXCore::Utils::PooledAllocatorVirtual CPUBackendAllocator; FEXCore::Utils::PooledAllocatorVirtual CPUBackendAllocator {"FEXMem_CPUBackend"};
// If Atomic-based TSO emulation is enabled or not. // If Atomic-based TSO emulation is enabled or not.
bool IsAtomicTSOEnabled() const { bool IsAtomicTSOEnabled() const {
@@ -312,10 +308,6 @@ protected:
AtomicTSOEmulationEnabled = false; AtomicTSOEmulationEnabled = false;
VectorAtomicTSOEmulationEnabled = false; VectorAtomicTSOEmulationEnabled = false;
MemcpyAtomicTSOEmulationEnabled = false; MemcpyAtomicTSOEmulationEnabled = false;
} else if (Config.ParanoidTSO) {
AtomicTSOEmulationEnabled = true;
VectorAtomicTSOEmulationEnabled = true;
MemcpyAtomicTSOEmulationEnabled = true;
} else { } else {
AtomicTSOEmulationEnabled = Config.TSOEnabled; AtomicTSOEmulationEnabled = Config.TSOEnabled;
VectorAtomicTSOEmulationEnabled = Config.TSOEnabled && Config.VectorTSOEnabled; VectorAtomicTSOEmulationEnabled = Config.TSOEnabled && Config.VectorTSOEnabled;
@@ -323,13 +315,6 @@ protected:
} }
} }
void UpdateX87PrecisionConfig() {
// If strict reduced precision is enabled, automatically enable reduced precision
if (Config.x87StrictReducedPrecision() && !Config.x87ReducedPrecision()) {
FEXCore::Config::Set(FEXCore::Config::CONFIG_X87REDUCEDPRECISION, "1");
}
}
private: private:
/** /**
* @brief Initializes the JIT compilers for the thread * @brief Initializes the JIT compilers for the thread
@@ -361,5 +346,8 @@ private:
bool MonoDetected = false; bool MonoDetected = false;
std::atomic<uint64_t> MonoBackpatcherBlock; std::atomic<uint64_t> MonoBackpatcherBlock;
std::mutex CodeBufferListLock;
fextl::vector<std::weak_ptr<CPU::CodeBuffer>> CodeBufferList;
}; };
} // namespace FEXCore::Context } // namespace FEXCore::Context
+2 -4
View File
@@ -360,9 +360,7 @@ namespace CPU {
LogMan::Msg::EFmt("Failed to mprotect last page of code buffer."); LogMan::Msg::EFmt("Failed to mprotect last page of code buffer.");
} }
#ifndef _WIN32 FEXCore::Allocator::VirtualName("FEXMemJIT", reinterpret_cast<void*>(Ptr), Size);
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, Ptr, Size, "FEXMemJIT");
#endif
LookupCache = fextl::make_unique<GuestToHostMap>(); LookupCache = fextl::make_unique<GuestToHostMap>();
} }
@@ -402,7 +400,7 @@ namespace CPU {
Latest = Buffer; Latest = Buffer;
LatestOffset = 0; LatestOffset = 0;
OnCodeBufferAllocated(*Buffer); OnCodeBufferAllocated(Buffer);
return Buffer; return Buffer;
} }
+1 -1
View File
@@ -81,7 +81,7 @@ namespace CPU {
// Protects writes to the latest CodeBuffer and changes to LatestOffset // Protects writes to the latest CodeBuffer and changes to LatestOffset
FEXCore::ForkableUniqueMutex CodeBufferWriteMutex; FEXCore::ForkableUniqueMutex CodeBufferWriteMutex;
virtual void OnCodeBufferAllocated(CodeBuffer&) {}; virtual void OnCodeBufferAllocated(const std::shared_ptr<CodeBuffer>&) {};
private: private:
fextl::shared_ptr<CodeBuffer> Latest; fextl::shared_ptr<CodeBuffer> Latest;
+58 -48
View File
@@ -57,13 +57,20 @@ $end_info$
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <chrono>
#include <condition_variable>
#include <fcntl.h> #include <fcntl.h>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
#include <queue>
#include <shared_mutex> #include <shared_mutex>
#include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <string_view>
#include <sys/stat.h> #include <sys/stat.h>
#include <type_traits>
#include <unistd.h> #include <unistd.h>
#include <unordered_map>
#include <utility> #include <utility>
#include <xxhash.h> #include <xxhash.h>
@@ -93,8 +100,6 @@ ContextImpl::ContextImpl(const FEXCore::HostFeatures& Features)
// Track atomic TSO emulation configuration. // Track atomic TSO emulation configuration.
UpdateAtomicTSOEmulationConfig(); UpdateAtomicTSOEmulationConfig();
// Ensure X87 precision constraints are respected.
UpdateX87PrecisionConfig();
} }
struct GetFrameBlockInfoResult { struct GetFrameBlockInfoResult {
@@ -371,7 +376,9 @@ void ContextImpl::InitializeCompiler(FEXCore::Core::InternalThreadState* Thread)
Thread->FrontendDecoder = fextl::make_unique<FEXCore::Frontend::Decoder>(Thread); Thread->FrontendDecoder = fextl::make_unique<FEXCore::Frontend::Decoder>(Thread);
Thread->PassManager = fextl::make_unique<FEXCore::IR::PassManager>(); Thread->PassManager = fextl::make_unique<FEXCore::IR::PassManager>();
Thread->CurrentFrame->Pointers.Common.L1Pointer = Thread->LookupCache->GetL1Pointer(); Thread->CurrentFrame->State.L1Pointer = Thread->LookupCache->GetL1Pointer();
Thread->CurrentFrame->State.L1Mask = Thread->LookupCache->GetScaledL1PointerMask();
Thread->CurrentFrame->Pointers.Common.L2Pointer = Thread->LookupCache->GetPagePointer(); Thread->CurrentFrame->Pointers.Common.L2Pointer = Thread->LookupCache->GetPagePointer();
Dispatcher->InitThreadPointers(Thread); Dispatcher->InitThreadPointers(Thread);
@@ -393,6 +400,7 @@ ContextImpl::CreateThread(uint64_t InitialRIP, uint64_t StackPointer, const FEXC
FEXCore::Core::InternalThreadState* Thread = new FEXCore::Core::InternalThreadState { FEXCore::Core::InternalThreadState* Thread = new FEXCore::Core::InternalThreadState {
.CTX = this, .CTX = this,
}; };
FEXCore::Allocator::VirtualName("FEXMem_ThreadState", Thread, sizeof(*Thread));
Thread->CurrentFrame->State.gregs[X86State::REG_RSP] = StackPointer; Thread->CurrentFrame->State.gregs[X86State::REG_RSP] = StackPointer;
Thread->CurrentFrame->State.rip = InitialRIP; Thread->CurrentFrame->State.rip = InitialRIP;
@@ -451,9 +459,14 @@ void ContextImpl::LockBeforeFork(FEXCore::Core::InternalThreadState* Thread) {
} }
#endif #endif
void ContextImpl::OnCodeBufferAllocated(CPU::CodeBuffer& Buffer) { void ContextImpl::OnCodeBufferAllocated(const fextl::shared_ptr<CPU::CodeBuffer>& Buffer) {
if (Config.GlobalJITNaming()) { if (Config.GlobalJITNaming()) {
Symbols.RegisterJITSpace(Buffer.Ptr, Buffer.Size); Symbols.RegisterJITSpace(Buffer->Ptr, Buffer->Size);
}
{
std::scoped_lock lk{CodeBufferListLock};
CodeBufferList.emplace_back(Buffer);
} }
} }
@@ -465,7 +478,8 @@ void ContextImpl::ClearCodeCache(FEXCore::Core::InternalThreadState* Thread, boo
Thread->CPUBackend->ClearCache(); Thread->CPUBackend->ClearCache();
} else { } else {
// Clear L1+L2 cache of this thread, and clear L3 cache across any threads using it // Clear L1+L2 cache of this thread, and clear L3 cache across any threads using it
Thread->LookupCache->ClearCache(); auto lk = Thread->LookupCache->AcquireWriteLock();
Thread->LookupCache->ClearCache(lk);
} }
Allocator::VirtualDontNeed(Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE); Allocator::VirtualDontNeed(Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE);
} }
@@ -637,10 +651,10 @@ ContextImpl::GenerateIR(FEXCore::Core::InternalThreadState* Thread, uint64_t Gue
LogMan::Msg::EFmt("Invalid or Unknown instruction: {} 0x{:x}", TableInfo->Name ?: "UND", Block.Entry - GuestRIP); LogMan::Msg::EFmt("Invalid or Unknown instruction: {} 0x{:x}", TableInfo->Name ?: "UND", Block.Entry - GuestRIP);
} }
if (Block.BlockStatus == Frontend::Decoder::DecodedBlockStatus::NOEXEC_INST) { if (Block.BlockStatus == Frontend::Decoder::DecodedBlockStatus::INVALID_INST) {
Thread->OpDispatcher->NoExecOp(DecodedInfo);
} else {
Thread->OpDispatcher->InvalidOp(DecodedInfo); Thread->OpDispatcher->InvalidOp(DecodedInfo);
} else {
Thread->OpDispatcher->NoExecOp(DecodedInfo);
} }
} }
@@ -723,7 +737,7 @@ ContextImpl::CompileCodeResult ContextImpl::CompileCode(FEXCore::Core::InternalT
// but this would increase lock contention. Redundant frontend runs aren't // but this would increase lock contention. Redundant frontend runs aren't
// as expensive and are easily reverted. // as expensive and are easily reverted.
if (MaxInst != 1) { if (MaxInst != 1) {
if (auto Block = Thread->LookupCache->FindBlock(GuestRIP)) { if (auto Block = Thread->LookupCache->FindBlock(Thread, GuestRIP)) {
Thread->OpDispatcher->DelayedDisownBuffer(); Thread->OpDispatcher->DelayedDisownBuffer();
return {.CompiledCode = {.BlockBegin = reinterpret_cast<uint8_t*>(Block), .EntryPoints = {{GuestRIP, reinterpret_cast<uint8_t*>(Block)}}}, return {.CompiledCode = {.BlockBegin = reinterpret_cast<uint8_t*>(Block), .EntryPoints = {{GuestRIP, reinterpret_cast<uint8_t*>(Block)}}},
.DebugData = nullptr, .DebugData = nullptr,
@@ -764,10 +778,13 @@ uintptr_t ContextImpl::CompileBlock(FEXCore::Core::CpuStateFrame* Frame, uint64_
// Is the code in the cache? // Is the code in the cache?
// The backends only check L1 and L2, not L3 // The backends only check L1 and L2, not L3
if (auto HostCode = Thread->LookupCache->FindBlock(GuestRIP)) { if (auto HostCode = Thread->LookupCache->FindBlock(Thread, GuestRIP)) {
return HostCode; return HostCode;
} }
// Accumulate a JIT count now, as even if another thread raced us, it should count as a compile.
FEXCORE_PROFILE_INSTANT_INCREMENT(Thread, AccumulatedJITCount, 1);
auto [CompiledCode, DebugData, StartAddr, Length, NeedsAddGuestCodeRanges] = CompileCode(Thread, GuestRIP, MaxInst); auto [CompiledCode, DebugData, StartAddr, Length, NeedsAddGuestCodeRanges] = CompileCode(Thread, GuestRIP, MaxInst);
auto CodePtr = CompiledCode.EntryPoints[GuestRIP]; auto CodePtr = CompiledCode.EntryPoints[GuestRIP];
if (CodePtr == nullptr) { if (CodePtr == nullptr) {
@@ -821,20 +838,25 @@ uintptr_t ContextImpl::CompileBlock(FEXCore::Core::CpuStateFrame* Frame, uint64_
Thread->CPUBackend->ClearRelocations(); Thread->CPUBackend->ClearRelocations();
} }
fextl::vector<uint64_t> CodePages;
if (NeedsAddGuestCodeRanges) { if (NeedsAddGuestCodeRanges) {
// Track in the guest to host map all entrypoints for all pages the compiled block touches, if any page didn't previously // Track in the guest to host map all entrypoints for all pages the compiled block touches, if any page didn't previously
// contain code, inform the frontend so it can setup SMC detection. // contain code, inform the frontend so it can setup SMC detection.
auto BlockInfo = Thread->FrontendDecoder->GetDecodedBlockInfo(); auto BlockInfo = Thread->FrontendDecoder->GetDecodedBlockInfo();
CodePages.reserve(BlockInfo->CodePages.size());
CodePages.insert(CodePages.end(), BlockInfo->CodePages.begin(), BlockInfo->CodePages.end());
for (auto CodePage : BlockInfo->CodePages) { for (auto CodePage : BlockInfo->CodePages) {
if (Thread->LookupCache->AddBlockExecutableRange(BlockInfo->EntryPoints, CodePage, FEXCore::Utils::FEX_PAGE_SIZE)) { if (Thread->LookupCache->AddBlockExecutableRange(Thread, BlockInfo->EntryPoints, CodePage, FEXCore::Utils::FEX_PAGE_SIZE)) {
SyscallHandler->MarkGuestExecutableRange(Thread, CodePage, FEXCore::Utils::FEX_PAGE_SIZE); SyscallHandler->MarkGuestExecutableRange(Thread, CodePage, FEXCore::Utils::FEX_PAGE_SIZE);
} }
} }
} }
// Insert to lookup cache // Insert to lookup cache
for (auto [GuestAddr, HostAddr] : CompiledCode.EntryPoints) { for (auto [GuestAddr, HostAddr] : CompiledCode.EntryPoints) {
Thread->LookupCache->AddBlockMapping(GuestAddr, HostAddr); Thread->LookupCache->AddBlockMapping(Thread, GuestAddr, CodePages, HostAddr);
} }
return (uintptr_t)CodePtr; return (uintptr_t)CodePtr;
@@ -861,49 +883,37 @@ uintptr_t ContextImpl::CompileSingleStep(FEXCore::Core::CpuStateFrame* Frame, ui
return (uintptr_t)CodePtr; return (uintptr_t)CodePtr;
} }
static void InvalidateGuestThreadCodeRange(FEXCore::Core::InternalThreadState* Thread, InvalidatedEntryAccumulator& Accumulator, void ContextImpl::InvalidateCodeBuffersCodeRange(uint64_t Start, uint64_t Length) {
uint64_t Start, uint64_t Length) { FEXCORE_PROFILE_SCOPED("InvalidateCodeBuffersCodeRange");
// Ensures now-modified mappings aren't cached as being in their previous non-executable state.
LogMan::Throw::AFmt(CodeInvalidationMutex.try_lock() == false, "CodeInvalidationMutex needs to be unique_locked here");
std::scoped_lock lk {CodeBufferListLock};
auto it = CodeBufferList.begin();
while (it != CodeBufferList.end()) {
if (auto Strong = it->lock(); Strong) {
Strong->LookupCache->InvalidateRange(Start, Length);
it++;
} else {
it = CodeBufferList.erase(it);
}
}
}
void ContextImpl::InvalidateThreadCachedCodeRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) {
LogMan::Throw::AFmt(CodeInvalidationMutex.try_lock() == false, "CodeInvalidationMutex needs to be unique_locked here");
// Ensures now-modified mappings aren't cached as being in their previous non-executable state.
// Accessing FrontendDecoder is safe as the thread's code invalidation mutex must be locked here. // Accessing FrontendDecoder is safe as the thread's code invalidation mutex must be locked here.
Thread->FrontendDecoder->ResetExecutableRangeCache(); Thread->FrontendDecoder->ResetExecutableRangeCache();
auto lk = Thread->LookupCache->AcquireLock(); if (Thread->LookupCache->InvalidateCacheRange(Start, Length)) {
auto& CodePages = Thread->LookupCache->Shared->CodePages; FEXCORE_PROFILE_SCOPED("InvalidateCallRet");
auto lower = CodePages.lower_bound(Start >> 12);
auto upper = CodePages.upper_bound((Start + Length - 1) >> 12);
for (auto it = lower; it != upper; it++) {
Accumulator.emplace_back(std::move(it->second));
}
bool InvalidatedAnyEntries = false;
for (const auto& PageEntries : Accumulator) {
for (const auto& Entry : PageEntries) {
if (ContextImpl::ThreadRemoveCodeEntry(Thread, Entry)) {
InvalidatedAnyEntries = true;
}
}
}
if (InvalidatedAnyEntries) {
// This may cause access violations in the thread on Windows as zeroing is not atomic, this is handled by the frontend // This may cause access violations in the thread on Windows as zeroing is not atomic, this is handled by the frontend
Allocator::VirtualDontNeed(Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE); Allocator::VirtualDontNeed(Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE);
} }
} }
void ContextImpl::InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState* Thread, InvalidatedEntryAccumulator& Accumulator,
uint64_t Start, uint64_t Length) {
InvalidateGuestThreadCodeRange(Thread, Accumulator, Start, Length);
}
bool ContextImpl::ThreadRemoveCodeEntry(FEXCore::Core::InternalThreadState* Thread, uint64_t GuestRIP) {
LogMan::Throw::AFmt(static_cast<ContextImpl*>(Thread->CTX)->CodeInvalidationMutex.try_lock() == false, "CodeInvalidationMutex needs to "
"be unique_locked here");
return Thread->LookupCache->Erase(Thread->CurrentFrame, GuestRIP);
}
void ContextImpl::ThreadRemoveCodeEntryFromJit(FEXCore::Core::CpuStateFrame* Frame, uint64_t GuestRIP) { void ContextImpl::ThreadRemoveCodeEntryFromJit(FEXCore::Core::CpuStateFrame* Frame, uint64_t GuestRIP) {
static_cast<ContextImpl*>(Frame->Thread->CTX)->SyscallHandler->InvalidateGuestCodeRange(Frame->Thread, GuestRIP, 1); static_cast<ContextImpl*>(Frame->Thread->CTX)->SyscallHandler->InvalidateGuestCodeRange(Frame->Thread, GuestRIP, 1);
} }
@@ -971,7 +981,7 @@ void ContextImpl::AddThunkTrampolineIRHandler(uintptr_t Entrypoint, uintptr_t Gu
void ContextImpl::AddForceTSOInformation(const IntervalList<uint64_t>& ValidRanges, fextl::set<uint64_t>&& Instructions) { void ContextImpl::AddForceTSOInformation(const IntervalList<uint64_t>& ValidRanges, fextl::set<uint64_t>&& Instructions) {
LogMan::Throw::AFmt(CodeInvalidationMutex.try_lock() == false, "CodeInvalidationMutex needs to be unique_locked here"); LogMan::Throw::AFmt(CodeInvalidationMutex.try_lock() == false, "CodeInvalidationMutex needs to be unique_locked here");
ForceTSOValidRanges.Insert(ValidRanges); ForceTSOValidRanges.Insert(ValidRanges);
ForceTSOInstructions.merge(Instructions); ForceTSOInstructions.merge(std::move(Instructions));
} }
void ContextImpl::RemoveForceTSOInformation(uint64_t Address, uint64_t Size) { void ContextImpl::RemoveForceTSOInformation(uint64_t Address, uint64_t Size) {
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#include "Common/SoftFloat.h" #include "Common/VectorRegType.h"
#include "Interface/Context/Context.h" #include "Interface/Context/Context.h"
#include "Interface/Core/CPUBackend.h" #include "Interface/Core/CPUBackend.h"
#include "Interface/Core/Dispatcher/Dispatcher.h" #include "Interface/Core/Dispatcher/Dispatcher.h"
@@ -26,9 +26,7 @@
#endif #endif
#include <array> #include <array>
#include <atomic>
#include <bit> #include <bit>
#include <condition_variable>
#include <csignal> #include <csignal>
#include <cstring> #include <cstring>
@@ -38,12 +36,14 @@ static void SleepThread(FEXCore::Context::ContextImpl* CTX, FEXCore::Core::CpuSt
CTX->SyscallHandler->SleepThread(CTX, Frame); CTX->SyscallHandler->SleepThread(CTX, Frame);
} }
constexpr size_t MAX_DISPATCHER_CODE_SIZE = 4096 * 4; constexpr size_t MAX_DISPATCHER_CODE_SIZE = FEXCore::Utils::FEX_PAGE_SIZE * 4;
Dispatcher::Dispatcher(FEXCore::Context::ContextImpl* ctx) Dispatcher::Dispatcher(FEXCore::Context::ContextImpl* ctx)
: Arm64Emitter(ctx, FEXCore::Allocator::VirtualAlloc(MAX_DISPATCHER_CODE_SIZE, true), MAX_DISPATCHER_CODE_SIZE) : Arm64Emitter(ctx, FEXCore::Allocator::VirtualAlloc(MAX_DISPATCHER_CODE_SIZE, true), MAX_DISPATCHER_CODE_SIZE)
, CTX {ctx} { , CTX {ctx} {
EmitDispatcher(); EmitDispatcher();
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(GetBufferBase()), MAX_DISPATCHER_CODE_SIZE);
} }
Dispatcher::~Dispatcher() { Dispatcher::~Dispatcher() {
@@ -93,7 +93,7 @@ void Dispatcher::EmitDispatcher() {
FillStaticRegs(); FillStaticRegs();
ldr(RipReg, STATE_PTR(CpuStateFrame, State.rip)); ldr(RipReg, STATE_PTR(CpuStateFrame, State.rip));
cbnz(ARMEmitter::Size::i32Bit, ENTRY_FILL_SRA_SINGLE_INST_REG, &CompileSingleStep); (void)cbnz(ARMEmitter::Size::i32Bit, ENTRY_FILL_SRA_SINGLE_INST_REG, &CompileSingleStep);
ARMEmitter::BiDirectionalLabel LoopTop {}; ARMEmitter::BiDirectionalLabel LoopTop {};
@@ -142,7 +142,7 @@ void Dispatcher::EmitDispatcher() {
// We want to ensure that we are 16 byte aligned at the top of this loop // We want to ensure that we are 16 byte aligned at the top of this loop
Align16B(); Align16B();
Bind(&LoopTop); (void)Bind(&LoopTop);
AbsoluteLoopTopAddress = GetCursorAddress<uint64_t>(); AbsoluteLoopTopAddress = GetCursorAddress<uint64_t>();
// Load in our RIP // Load in our RIP
@@ -169,66 +169,73 @@ void Dispatcher::EmitDispatcher() {
ldr(TMP2, STATE_PTR(CpuStateFrame, Pointers.Common.ExitFunctionEC)); ldr(TMP2, STATE_PTR(CpuStateFrame, Pointers.Common.ExitFunctionEC));
br(TMP2); br(TMP2);
Bind(&l_NotECCode); (void)Bind(&l_NotECCode);
#endif #endif
ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC])); ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC]));
cbnz(ARMEmitter::Size::i32Bit, TMP1, &CompileSingleStep); (void)cbnz(ARMEmitter::Size::i32Bit, TMP1, &CompileSingleStep);
// This is the block cache lookup routine
// It matches what is going on it LookupCache.h::FindBlock
ldr(TMP1, STATE_PTR(CpuStateFrame, Pointers.Common.L2Pointer));
// Mask the address by the virtual address size so we can check for aliases
uint64_t VirtualMemorySize = CTX->Config.VirtualMemSize;
if (std::popcount(VirtualMemorySize) == 1) {
and_(ARMEmitter::Size::i64Bit, TMP4, RipReg.R(), VirtualMemorySize - 1);
} else {
LoadConstant(ARMEmitter::Size::i64Bit, TMP4, VirtualMemorySize);
and_(ARMEmitter::Size::i64Bit, TMP4, RipReg.R(), TMP4);
}
ARMEmitter::ForwardLabel NoBlock; ARMEmitter::ForwardLabel NoBlock;
{ if (DisableL2Cache()) {
// Offset the address and add to our page pointer (void)b(&NoBlock);
lsr(ARMEmitter::Size::i64Bit, TMP2, TMP4, 12); } else {
// This is the block cache lookup routine
// It matches what is going on it LookupCache.h::FindBlock
ldr(TMP1, STATE_PTR(CpuStateFrame, Pointers.Common.L2Pointer));
// Load the pointer from the offset // Mask the address by the virtual address size so we can check for aliases
ldr(TMP1, TMP1, TMP2, ARMEmitter::ExtendedType::LSL_64, 3); uint64_t VirtualMemorySize = CTX->Config.VirtualMemSize;
if (std::popcount(VirtualMemorySize) == 1) {
and_(ARMEmitter::Size::i64Bit, TMP4, RipReg.R(), VirtualMemorySize - 1);
} else {
LoadConstant(ARMEmitter::Size::i64Bit, TMP4, VirtualMemorySize);
and_(ARMEmitter::Size::i64Bit, TMP4, RipReg.R(), TMP4);
}
// If page pointer is zero then we have no block
cbz(ARMEmitter::Size::i64Bit, TMP1, &NoBlock);
// Steal the page offset
and_(ARMEmitter::Size::i64Bit, TMP2, TMP4, 0x0FFF);
// Shift the offset by the size of the block cache entry
add(TMP1, TMP1, TMP2, ARMEmitter::ShiftType::LSL, (int)log2(sizeof(FEXCore::LookupCache::LookupCacheEntry)));
// The the full LookupCacheEntry with a single LDP.
// Check the guest address first to ensure it maps to the address we are currently at.
// This fixes aliasing problems
ldp<ARMEmitter::IndexType::OFFSET>(TMP4, TMP2, TMP1, 0);
// If the guest address doesn't match, Compile the block.
sub(TMP2, TMP2, RipReg);
cbnz(ARMEmitter::Size::i64Bit, TMP2, &NoBlock);
// Check the host address to see if it matches, else compile the block.
cbz(ARMEmitter::Size::i64Bit, TMP4, &NoBlock);
// If we've made it here then we have a real compiled block
{ {
// update L1 cache // Offset the address and add to our page pointer
ldr(TMP1, STATE_PTR(CpuStateFrame, Pointers.Common.L1Pointer)); lsr(ARMEmitter::Size::i64Bit, TMP2, TMP4, 12);
and_(ARMEmitter::Size::i64Bit, TMP2, RipReg.R(), LookupCache::L1_ENTRIES_MASK); // Load the pointer from the offset
add(TMP1, TMP1, TMP2, ARMEmitter::ShiftType::LSL, 4); ldr(TMP1, TMP1, TMP2, ARMEmitter::ExtendedType::LSL_64, 3);
stp<ARMEmitter::IndexType::OFFSET>(TMP4, RipReg, TMP1);
// Jump to the block // If page pointer is zero then we have no block
br(TMP4); (void)cbz(ARMEmitter::Size::i64Bit, TMP1, &NoBlock);
// Steal the page offset
and_(ARMEmitter::Size::i64Bit, TMP2, TMP4, 0x0FFF);
// Shift the offset by the size of the block cache entry
add(TMP1, TMP1, TMP2, ARMEmitter::ShiftType::LSL, FEXCore::ilog2(sizeof(LookupCache::LookupCacheEntry)));
// The the full LookupCacheEntry with a single LDP.
// Check the guest address first to ensure it maps to the address we are currently at.
// This fixes aliasing problems
ldp<ARMEmitter::IndexType::OFFSET>(TMP4, TMP2, TMP1, 0);
// If the guest address doesn't match, Compile the block.
sub(TMP2, TMP2, RipReg);
(void)cbnz(ARMEmitter::Size::i64Bit, TMP2, &NoBlock);
// Check the host address to see if it matches, else compile the block.
(void)cbz(ARMEmitter::Size::i64Bit, TMP4, &NoBlock);
// If we've made it here then we have a real compiled block
{
// update L1 cache
ldp<ARMEmitter::IndexType::OFFSET>(TMP1, TMP2, STATE, offsetof(FEXCore::Core::CpuStateFrame, State.L1Pointer));
// Calculate (tmp1 + ((ripreg & L1_ENTRIES_MASK) << 4)) for the address
// L1Mask is pre-shifted.
and_(ARMEmitter::Size::i64Bit, TMP2, TMP2, RipReg.R(), ARMEmitter::ShiftType::LSL, FEXCore::ilog2(sizeof(LookupCache::LookupCacheEntry)));
add(TMP1, TMP1, TMP2);
stp<ARMEmitter::IndexType::OFFSET>(TMP4, RipReg, TMP1);
// Jump to the block
br(TMP4);
}
} }
} }
@@ -304,7 +311,7 @@ void Dispatcher::EmitDispatcher() {
// Need to create the block // Need to create the block
{ {
Bind(&NoBlock); (void)Bind(&NoBlock);
EmitSignalGuardedRegion([&]() { EmitSignalGuardedRegion([&]() {
SpillStaticRegs(TMP1); SpillStaticRegs(TMP1);
@@ -338,7 +345,7 @@ void Dispatcher::EmitDispatcher() {
} }
{ {
Bind(&CompileSingleStep); (void)Bind(&CompileSingleStep);
EmitSignalGuardedRegion([&]() { EmitSignalGuardedRegion([&]() {
SpillStaticRegs(TMP1); SpillStaticRegs(TMP1);
@@ -500,7 +507,7 @@ void Dispatcher::EmitDispatcher() {
stp<ARMEmitter::IndexType::PRE>(ARMEmitter::XReg::zr, ARMEmitter::XReg::zr, REG_CALLRET_SP, -0x10); stp<ARMEmitter::IndexType::PRE>(ARMEmitter::XReg::zr, ARMEmitter::XReg::zr, REG_CALLRET_SP, -0x10);
// Now go back to the regular dispatcher loop // Now go back to the regular dispatcher loop
b(&LoopTop); (void)b(&LoopTop);
} }
auto EmitLongALUOpHandler = [&](auto R, auto Offset) { auto EmitLongALUOpHandler = [&](auto R, auto Offset) {
@@ -569,14 +576,15 @@ void Dispatcher::EmitDispatcher() {
} }
} }
Bind(&l_CTX); (void)Bind(&l_CTX);
dc64(reinterpret_cast<uintptr_t>(CTX)); dc64(reinterpret_cast<uintptr_t>(CTX));
Bind(&l_Sleep); (void)Bind(&l_Sleep);
dc64(reinterpret_cast<uint64_t>(SleepThread)); dc64(reinterpret_cast<uint64_t>(SleepThread));
Bind(&l_CompileBlock); (void)Bind(&l_CompileBlock);
FEXCore::Utils::MemberFunctionToPointerCast PMFCompileBlock(&FEXCore::Context::ContextImpl::CompileBlock); FEXCore::Utils::MemberFunctionToPointerCast PMFCompileBlock(&FEXCore::Context::ContextImpl::CompileBlock);
dc64(PMFCompileBlock.GetConvertedPointer()); dc64(PMFCompileBlock.GetConvertedPointer());
Bind(&l_CompileSingleStep); (void)Bind(&l_CompileSingleStep);
FEXCore::Utils::MemberFunctionToPointerCast PMFCompileSingleStep(&FEXCore::Context::ContextImpl::CompileSingleStep); FEXCore::Utils::MemberFunctionToPointerCast PMFCompileSingleStep(&FEXCore::Context::ContextImpl::CompileSingleStep);
dc64(PMFCompileSingleStep.GetConvertedPointer()); dc64(PMFCompileSingleStep.GetConvertedPointer());
@@ -4,6 +4,7 @@
#include "Interface/Core/ArchHelpers/Arm64Emitter.h" #include "Interface/Core/ArchHelpers/Arm64Emitter.h"
#include "Interface/Core/Interpreter/InterpreterOps.h" #include "Interface/Core/Interpreter/InterpreterOps.h"
#include <FEXCore/Config/Config.h>
#include <FEXCore/fextl/memory.h> #include <FEXCore/fextl/memory.h>
#include <array> #include <array>
@@ -92,6 +93,8 @@ private:
void EmitDispatcher(); void EmitDispatcher();
uint64_t GenerateABICall(FallbackABI ABI); uint64_t GenerateABICall(FallbackABI ABI);
FEX_CONFIG_OPT(DisableL2Cache, DISABLEL2CACHE);
}; };
} // namespace FEXCore::CPU } // namespace FEXCore::CPU
+8 -2
View File
@@ -1047,8 +1047,11 @@ Decoder::DecodedBlockStatus Decoder::DecodeInstruction(uint64_t PC) {
// Put an invalid instruction in the stream so the core can raise SIGILL if hit // Put an invalid instruction in the stream so the core can raise SIGILL if hit
// Error while decoding instruction. We don't know the table or instruction size // Error while decoding instruction. We don't know the table or instruction size
DecodeInst->TableInfo = nullptr; DecodeInst->TableInfo = nullptr;
auto Result = ErrorDuringDecoding ? DecodedBlockStatus::INVALID_INST :
DecodeInst->InstSize ? DecodedBlockStatus::PARTIAL_DECODE_INST :
DecodedBlockStatus::NOEXEC_INST;
DecodeInst->InstSize = 0; DecodeInst->InstSize = 0;
return ErrorDuringDecoding ? DecodedBlockStatus::INVALID_INST : DecodedBlockStatus::NOEXEC_INST; return Result;
} else if (!DecodeInst->TableInfo || (DecodeInst->TableInfo->Type == TYPE_INST && !DecodeInst->TableInfo->OpcodeDispatcher.OpDispatch)) { } else if (!DecodeInst->TableInfo || (DecodeInst->TableInfo->Type == TYPE_INST && !DecodeInst->TableInfo->OpcodeDispatcher.OpDispatch)) {
// If there wasn't an error during decoding but we have no dispatcher for the instruction then claim invalid instruction. // If there wasn't an error during decoding but we have no dispatcher for the instruction then claim invalid instruction.
return DecodedBlockStatus::INVALID_INST; return DecodedBlockStatus::INVALID_INST;
@@ -1450,7 +1453,10 @@ void Decoder::DecodeInstructionsAtEntry(FEXCore::Core::InternalThreadState* Thre
EraseBlock = true; EraseBlock = true;
} else { } else {
LogMan::Msg::EFmt("{} instruction in entry block: {:X}", LogMan::Msg::EFmt("{} instruction in entry block: {:X}",
BlockIt->BlockStatus == DecodedBlockStatus::INVALID_INST ? "Invalid" : "NoExec", OpAddress); BlockIt->BlockStatus == DecodedBlockStatus::INVALID_INST ? "Invalid" :
BlockIt->BlockStatus == DecodedBlockStatus::NOEXEC_INST ? "NoExec" :
"PartialDecode",
OpAddress);
} }
break; break;
} }
+1
View File
@@ -27,6 +27,7 @@ public:
SUCCESS, SUCCESS,
INVALID_INST, INVALID_INST,
NOEXEC_INST, NOEXEC_INST,
PARTIAL_DECODE_INST,
}; };
// New Frontend decoding // New Frontend decoding
@@ -2,13 +2,11 @@
#pragma once #pragma once
#include "Common/SoftFloat.h" #include "Common/SoftFloat.h"
#include "Interface/Context/Context.h"
#include "Interface/Core/Interpreter/Fallbacks/FallbackOpHandler.h" #include "Interface/Core/Interpreter/Fallbacks/FallbackOpHandler.h"
#include "Interface/IR/IR.h" #include "Interface/IR/IR.h"
#include <FEXCore/Debug/InternalThreadState.h> #include <FEXCore/Debug/InternalThreadState.h>
#include <FEXCore/Utils/SHMStats.h> #include <FEXCore/Utils/SHMStats.h>
#include <FEXCore/Config/Config.h>
namespace FEXCore::CPU { namespace FEXCore::CPU {
FEXCORE_PRESERVE_ALL_ATTR static softfloat_state SoftFloatStateFromFCW(uint16_t FCW, bool Force80BitPrecision = false) { FEXCORE_PRESERVE_ALL_ATTR static softfloat_state SoftFloatStateFromFCW(uint16_t FCW, bool Force80BitPrecision = false) {
@@ -79,12 +77,6 @@ struct OpHandlers<IR::OP_F80CVTTO> {
FEXCORE_PRESERVE_ALL_ATTR static VectorRegType handle8(uint16_t FCW, double src, FEXCore::Core::CpuStateFrame* Frame) { FEXCORE_PRESERVE_ALL_ATTR static VectorRegType handle8(uint16_t FCW, double src, FEXCore::Core::CpuStateFrame* Frame) {
FEXCORE_PROFILE_INSTANT_INCREMENT(Frame->Thread, AccumulatedFloatFallbackCount, 1); FEXCORE_PROFILE_INSTANT_INCREMENT(Frame->Thread, AccumulatedFloatFallbackCount, 1);
ScopedSoftFloatState State {FCW, Frame}; ScopedSoftFloatState State {FCW, Frame};
auto Context = static_cast<Context::ContextImpl*>(Frame->Thread->CTX);
auto ReducedPrecisionMode = Context->Config.x87ReducedPrecision;
auto StrictReducedPrecisionMode = Context->Config.x87StrictReducedPrecision;
if (!ReducedPrecisionMode || StrictReducedPrecisionMode) {
return X80SoftFloat::FromF64_PreserveNaN(&State.State, src);
}
return X80SoftFloat(&State.State, src); return X80SoftFloat(&State.State, src);
} }
}; };
@@ -123,12 +115,6 @@ struct OpHandlers<IR::OP_F80CVT> {
FEXCORE_PRESERVE_ALL_ATTR static double handle8(uint16_t FCW, VectorRegType src, FEXCore::Core::CpuStateFrame* Frame) { FEXCORE_PRESERVE_ALL_ATTR static double handle8(uint16_t FCW, VectorRegType src, FEXCore::Core::CpuStateFrame* Frame) {
FEXCORE_PROFILE_INSTANT_INCREMENT(Frame->Thread, AccumulatedFloatFallbackCount, 1); FEXCORE_PROFILE_INSTANT_INCREMENT(Frame->Thread, AccumulatedFloatFallbackCount, 1);
ScopedSoftFloatState State {FCW, Frame}; ScopedSoftFloatState State {FCW, Frame};
auto Context = static_cast<Context::ContextImpl*>(Frame->Thread->CTX);
auto ReducedPrecisionMode = Context->Config.x87ReducedPrecision;
auto StrictReducedPrecisionMode = Context->Config.x87StrictReducedPrecision;
if (!ReducedPrecisionMode || StrictReducedPrecisionMode) {
return X80SoftFloat(src).ToF64_PreserveNan(&State.State);
}
return X80SoftFloat(src).ToF64(&State.State); return X80SoftFloat(src).ToF64(&State.State);
} }
}; };
+22 -22
View File
@@ -588,7 +588,7 @@ DEF_OP(ShiftFlags) {
and_(ARMEmitter::Size::i32Bit, TMP1, Src2, OpSize == IR::OpSize::i64Bit ? 0x3f : 0x1f); and_(ARMEmitter::Size::i32Bit, TMP1, Src2, OpSize == IR::OpSize::i64Bit ? 0x3f : 0x1f);
ARMEmitter::ForwardLabel Done; ARMEmitter::ForwardLabel Done;
cbz(EmitSize, TMP1, &Done); (void)cbz(EmitSize, TMP1, &Done);
{ {
// PF/SF/ZF/OF // PF/SF/ZF/OF
if (OpSize >= IR::OpSize::i32Bit) { if (OpSize >= IR::OpSize::i32Bit) {
@@ -652,7 +652,7 @@ DEF_OP(ShiftFlags) {
msr(ARMEmitter::SystemRegister::NZCV, TMP2); msr(ARMEmitter::SystemRegister::NZCV, TMP2);
} }
} }
Bind(&Done); (void)Bind(&Done);
// TODO: Make RA less dumb so this can't happen (e.g. with late-kill). // TODO: Make RA less dumb so this can't happen (e.g. with late-kill).
if (PFOutput != PFTemp) { if (PFOutput != PFTemp) {
@@ -669,7 +669,7 @@ DEF_OP(RotateFlags) {
// If shift=0, flags are unaffected. Wrap the whole implementation in a cbz. // If shift=0, flags are unaffected. Wrap the whole implementation in a cbz.
ARMEmitter::ForwardLabel Done; ARMEmitter::ForwardLabel Done;
cbz(EmitSize, Shift, &Done); (void)cbz(EmitSize, Shift, &Done);
{ {
// Extract the last bit shifted in to CF // Extract the last bit shifted in to CF
const auto BitSize = IR::OpSizeToSize(Op->Size) * 8; const auto BitSize = IR::OpSizeToSize(Op->Size) * 8;
@@ -701,7 +701,7 @@ DEF_OP(RotateFlags) {
msr(ARMEmitter::SystemRegister::NZCV, TMP3); msr(ARMEmitter::SystemRegister::NZCV, TMP3);
} }
} }
Bind(&Done); (void)Bind(&Done);
} }
DEF_OP(Extr) { DEF_OP(Extr) {
@@ -767,14 +767,14 @@ DEF_OP(PDep) {
// Now, they're copied, so we can start setting Dest (even if it overlaps with // Now, they're copied, so we can start setting Dest (even if it overlaps with
// one of them). Handle early exit case // one of them). Handle early exit case
mov(EmitSize, Dest, 0); mov(EmitSize, Dest, 0);
cbz(EmitSize, OrigMask, &Done); (void)cbz(EmitSize, OrigMask, &Done);
// Setup for first iteration // Setup for first iteration
neg(EmitSize, T0, Mask); neg(EmitSize, T0, Mask);
and_(EmitSize, T0, T0, Mask); and_(EmitSize, T0, T0, Mask);
// Main loop // Main loop
Bind(&NextBit); (void)Bind(&NextBit);
sbfx(EmitSize, T1, Input, 0, 1); sbfx(EmitSize, T1, Input, 0, 1);
eor(EmitSize, Mask, Mask, T0); eor(EmitSize, Mask, Mask, T0);
and_(EmitSize, T0, T1, T0); and_(EmitSize, T0, T1, T0);
@@ -782,10 +782,10 @@ DEF_OP(PDep) {
orr(EmitSize, Dest, Dest, T0); orr(EmitSize, Dest, Dest, T0);
lsr(EmitSize, Input, Input, 1); lsr(EmitSize, Input, Input, 1);
and_(EmitSize, T0, Mask, T1); and_(EmitSize, T0, Mask, T1);
cbnz(EmitSize, T0, &NextBit); (void)cbnz(EmitSize, T0, &NextBit);
// All done with nothing to do. // All done with nothing to do.
Bind(&Done); (void)Bind(&Done);
} }
} }
@@ -821,27 +821,27 @@ DEF_OP(PExt) {
ARMEmitter::BackwardLabel NextBit; ARMEmitter::BackwardLabel NextBit;
ARMEmitter::ForwardLabel Done; ARMEmitter::ForwardLabel Done;
cbz(EmitSize, Mask, &EarlyExit); (void)cbz(EmitSize, Mask, &EarlyExit);
mov(EmitSize, MaskReg, Mask); mov(EmitSize, MaskReg, Mask);
mov(EmitSize, ValueReg, Input); mov(EmitSize, ValueReg, Input);
mov(EmitSize, Dest, ARMEmitter::Reg::zr); mov(EmitSize, Dest, ARMEmitter::Reg::zr);
// Main loop // Main loop
Bind(&NextBit); (void)Bind(&NextBit);
cbz(EmitSize, MaskReg, &Done); (void)cbz(EmitSize, MaskReg, &Done);
clz(EmitSize, BitReg, MaskReg); clz(EmitSize, BitReg, MaskReg);
lslv(EmitSize, ValueReg, ValueReg, BitReg); lslv(EmitSize, ValueReg, ValueReg, BitReg);
lslv(EmitSize, MaskReg, MaskReg, BitReg); lslv(EmitSize, MaskReg, MaskReg, BitReg);
extr(EmitSize, Dest, Dest, ValueReg, OpSizeBitsM1); extr(EmitSize, Dest, Dest, ValueReg, OpSizeBitsM1);
bfc(EmitSize, MaskReg, OpSizeBitsM1, 1); bfc(EmitSize, MaskReg, OpSizeBitsM1, 1);
b(&NextBit); (void)b(&NextBit);
// Early exit // Early exit
Bind(&EarlyExit); (void)Bind(&EarlyExit);
mov(EmitSize, Dest, ARMEmitter::Reg::zr); mov(EmitSize, Dest, ARMEmitter::Reg::zr);
// All done with nothing to do. // All done with nothing to do.
Bind(&Done); (void)Bind(&Done);
} }
} }
@@ -909,7 +909,7 @@ DEF_OP(Div) {
eor(EmitSize, TMP1, TMP1, Upper); eor(EmitSize, TMP1, TMP1, Upper);
// If the sign bit matches then the result is zero // If the sign bit matches then the result is zero
cbz(EmitSize, TMP1, &Only64Bit); (void)cbz(EmitSize, TMP1, &Only64Bit);
// Long divide // Long divide
{ {
@@ -928,17 +928,17 @@ DEF_OP(Div) {
mov(EmitSize, Remainder, TMP2); mov(EmitSize, Remainder, TMP2);
// Skip 64-bit path // Skip 64-bit path
b(&LongDIVRet); (void)b(&LongDIVRet);
} }
Bind(&Only64Bit); (void)Bind(&Only64Bit);
// 64-Bit only // 64-Bit only
{ {
sdiv(EmitSize, Quotient, Lower, Divisor); sdiv(EmitSize, Quotient, Lower, Divisor);
msub(EmitSize, Remainder, Quotient, Divisor, Lower); msub(EmitSize, Remainder, Quotient, Divisor, Lower);
} }
Bind(&LongDIVRet); (void)Bind(&LongDIVRet);
break; break;
} }
default: LOGMAN_MSG_A_FMT("Unknown DIV Size: {}", OpSize); break; default: LOGMAN_MSG_A_FMT("Unknown DIV Size: {}", OpSize); break;
@@ -992,7 +992,7 @@ DEF_OP(UDiv) {
// Check the upper bits for zero // Check the upper bits for zero
// If the upper bits are zero then we can do a 64-bit divide // If the upper bits are zero then we can do a 64-bit divide
cbz(EmitSize, Upper, &Only64Bit); (void)cbz(EmitSize, Upper, &Only64Bit);
// Long divide // Long divide
{ {
@@ -1011,17 +1011,17 @@ DEF_OP(UDiv) {
mov(EmitSize, Remainder, TMP2); mov(EmitSize, Remainder, TMP2);
// Skip 64-bit path // Skip 64-bit path
b(&LongDIVRet); (void)b(&LongDIVRet);
} }
Bind(&Only64Bit); (void)Bind(&Only64Bit);
// 64-Bit only // 64-Bit only
{ {
udiv(EmitSize, Quotient, Lower, Divisor); udiv(EmitSize, Quotient, Lower, Divisor);
msub(EmitSize, Remainder, Quotient, Divisor, Lower); msub(EmitSize, Remainder, Quotient, Divisor, Lower);
} }
Bind(&LongDIVRet); (void)Bind(&LongDIVRet);
break; break;
} }
default: LOGMAN_MSG_A_FMT("Unknown LUDIV Size: {}", OpSize); break; default: LOGMAN_MSG_A_FMT("Unknown LUDIV Size: {}", OpSize); break;
@@ -63,7 +63,7 @@ void Arm64JITCore::PlaceNamedSymbolLiteral(NamedSymbolLiteralPair& Lit) {
auto CurrentCursor = GetCursorAddress<uint8_t*>(); auto CurrentCursor = GetCursorAddress<uint8_t*>();
Lit.MoveABI.NamedSymbolLiteral.Offset = CurrentCursor - CodeData.BlockBegin; Lit.MoveABI.NamedSymbolLiteral.Offset = CurrentCursor - CodeData.BlockBegin;
Bind(&Lit.Loc); BindOrRestart(&Lit.Loc);
dc64(Lit.Lit); dc64(Lit.Lit);
Relocations.emplace_back(Lit.MoveABI); Relocations.emplace_back(Lit.MoveABI);
} }
+34 -34
View File
@@ -62,27 +62,27 @@ DEF_OP(CASPair) {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
ARMEmitter::ForwardLabel LoopNotExpected; ARMEmitter::ForwardLabel LoopNotExpected;
ARMEmitter::ForwardLabel LoopExpected; ARMEmitter::ForwardLabel LoopExpected;
Bind(&LoopTop); (void)Bind(&LoopTop);
// This instruction sequence must be synced with HandleCASPAL_Armv8. // This instruction sequence must be synced with HandleCASPAL_Armv8.
ldaxp(EmitSize, TMP2, TMP3, MemSrc); ldaxp(EmitSize, TMP2, TMP3, MemSrc);
cmp(EmitSize, TMP2, Expected0); cmp(EmitSize, TMP2, Expected0);
ccmp(EmitSize, TMP3, Expected1, ARMEmitter::StatusFlags::None, ARMEmitter::Condition::CC_EQ); ccmp(EmitSize, TMP3, Expected1, ARMEmitter::StatusFlags::None, ARMEmitter::Condition::CC_EQ);
b(ARMEmitter::Condition::CC_NE, &LoopNotExpected); (void)b(ARMEmitter::Condition::CC_NE, &LoopNotExpected);
stlxp(EmitSize, TMP2, Desired0, Desired1, MemSrc); stlxp(EmitSize, TMP2, Desired0, Desired1, MemSrc);
cbnz(EmitSize, TMP2, &LoopTop); (void)cbnz(EmitSize, TMP2, &LoopTop);
mov(EmitSize, Dst0, Expected0); mov(EmitSize, Dst0, Expected0);
mov(EmitSize, Dst1, Expected1); mov(EmitSize, Dst1, Expected1);
b(&LoopExpected); (void)b(&LoopExpected);
Bind(&LoopNotExpected); (void)Bind(&LoopNotExpected);
mov(EmitSize, Dst0, TMP2.R()); mov(EmitSize, Dst0, TMP2.R());
mov(EmitSize, Dst1, TMP3.R()); mov(EmitSize, Dst1, TMP3.R());
// exclusive monitor needs to be cleared here // exclusive monitor needs to be cleared here
// Might have hit the case where ldaxr was hit but stlxr wasn't // Might have hit the case where ldaxr was hit but stlxr wasn't
clrex(); clrex();
Bind(&LoopExpected); (void)Bind(&LoopExpected);
// Restore // Restore
msr(ARMEmitter::SystemRegister::NZCV, TMP1); msr(ARMEmitter::SystemRegister::NZCV, TMP1);
@@ -114,7 +114,7 @@ DEF_OP(CAS) {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
ARMEmitter::ForwardLabel LoopNotExpected; ARMEmitter::ForwardLabel LoopNotExpected;
ARMEmitter::ForwardLabel LoopExpected; ARMEmitter::ForwardLabel LoopExpected;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
if (IROp->Size == IR::OpSize::i8Bit) { if (IROp->Size == IR::OpSize::i8Bit) {
cmp(EmitSize, TMP2, Expected, ARMEmitter::ExtendedType::UXTB, 0); cmp(EmitSize, TMP2, Expected, ARMEmitter::ExtendedType::UXTB, 0);
@@ -123,18 +123,18 @@ DEF_OP(CAS) {
} else { } else {
cmp(EmitSize, TMP2, Expected); cmp(EmitSize, TMP2, Expected);
} }
b(ARMEmitter::Condition::CC_NE, &LoopNotExpected); (void)b(ARMEmitter::Condition::CC_NE, &LoopNotExpected);
stlxr(SubEmitSize, TMP3, Desired, MemSrc); stlxr(SubEmitSize, TMP3, Desired, MemSrc);
cbnz(EmitSize, TMP3, &LoopTop); (void)cbnz(EmitSize, TMP3, &LoopTop);
mov(EmitSize, Dst, Expected); mov(EmitSize, Dst, Expected);
b(&LoopExpected); (void)b(&LoopExpected);
Bind(&LoopNotExpected); (void)Bind(&LoopNotExpected);
mov(EmitSize, Dst, TMP2.R()); mov(EmitSize, Dst, TMP2.R());
// exclusive monitor needs to be cleared here // exclusive monitor needs to be cleared here
// Might have hit the case where ldaxr was hit but stlxr wasn't // Might have hit the case where ldaxr was hit but stlxr wasn't
clrex(); clrex();
Bind(&LoopExpected); (void)Bind(&LoopExpected);
} }
} }
@@ -150,11 +150,11 @@ DEF_OP(AtomicXor) {
steorl(SubEmitSize, Src, MemSrc); steorl(SubEmitSize, Src, MemSrc);
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
eor(EmitSize, TMP2, TMP2, Src); eor(EmitSize, TMP2, TMP2, Src);
stlxr(SubEmitSize, TMP2, TMP2, MemSrc); stlxr(SubEmitSize, TMP2, TMP2, MemSrc);
cbnz(EmitSize, TMP2, &LoopTop); (void)cbnz(EmitSize, TMP2, &LoopTop);
} }
} }
@@ -179,10 +179,10 @@ DEF_OP(AtomicSwap) {
ldswpal(SubEmitSize, Src, GetReg(Node), MemSrc); ldswpal(SubEmitSize, Src, GetReg(Node), MemSrc);
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
stlxr(SubEmitSize, TMP4, Src, MemSrc); stlxr(SubEmitSize, TMP4, Src, MemSrc);
cbnz(EmitSize, TMP4, &LoopTop); (void)cbnz(EmitSize, TMP4, &LoopTop);
ubfm(EmitSize, GetReg(Node), TMP2, 0, IR::OpSizeAsBits(OpSize) - 1); ubfm(EmitSize, GetReg(Node), TMP2, 0, IR::OpSizeAsBits(OpSize) - 1);
} }
} }
@@ -199,11 +199,11 @@ DEF_OP(AtomicFetchAdd) {
ldaddal(SubEmitSize, Src, GetReg(Node), MemSrc); ldaddal(SubEmitSize, Src, GetReg(Node), MemSrc);
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
add(EmitSize, TMP3, TMP2, Src); add(EmitSize, TMP3, TMP2, Src);
stlxr(SubEmitSize, TMP4, TMP3, MemSrc); stlxr(SubEmitSize, TMP4, TMP3, MemSrc);
cbnz(EmitSize, TMP4, &LoopTop); (void)cbnz(EmitSize, TMP4, &LoopTop);
mov(EmitSize, GetReg(Node), TMP2.R()); mov(EmitSize, GetReg(Node), TMP2.R());
} }
} }
@@ -221,11 +221,11 @@ DEF_OP(AtomicFetchSub) {
ldaddal(SubEmitSize, TMP2, GetReg(Node), MemSrc); ldaddal(SubEmitSize, TMP2, GetReg(Node), MemSrc);
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
sub(EmitSize, TMP3, TMP2, Src); sub(EmitSize, TMP3, TMP2, Src);
stlxr(SubEmitSize, TMP4, TMP3, MemSrc); stlxr(SubEmitSize, TMP4, TMP3, MemSrc);
cbnz(EmitSize, TMP4, &LoopTop); (void)cbnz(EmitSize, TMP4, &LoopTop);
mov(EmitSize, GetReg(Node), TMP2.R()); mov(EmitSize, GetReg(Node), TMP2.R());
} }
} }
@@ -243,11 +243,11 @@ DEF_OP(AtomicFetchAnd) {
ldclral(SubEmitSize, TMP2, GetReg(Node), MemSrc); ldclral(SubEmitSize, TMP2, GetReg(Node), MemSrc);
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
and_(EmitSize, TMP3, TMP2, Src); and_(EmitSize, TMP3, TMP2, Src);
stlxr(SubEmitSize, TMP4, TMP3, MemSrc); stlxr(SubEmitSize, TMP4, TMP3, MemSrc);
cbnz(EmitSize, TMP4, &LoopTop); (void)cbnz(EmitSize, TMP4, &LoopTop);
mov(EmitSize, GetReg(Node), TMP2.R()); mov(EmitSize, GetReg(Node), TMP2.R());
} }
} }
@@ -264,11 +264,11 @@ DEF_OP(AtomicFetchCLR) {
ldclral(SubEmitSize, Src, GetReg(Node), MemSrc); ldclral(SubEmitSize, Src, GetReg(Node), MemSrc);
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
bic(EmitSize, TMP3, TMP2, Src); bic(EmitSize, TMP3, TMP2, Src);
stlxr(SubEmitSize, TMP4, TMP3, MemSrc); stlxr(SubEmitSize, TMP4, TMP3, MemSrc);
cbnz(EmitSize, TMP4, &LoopTop); (void)cbnz(EmitSize, TMP4, &LoopTop);
mov(EmitSize, GetReg(Node), TMP2.R()); mov(EmitSize, GetReg(Node), TMP2.R());
} }
} }
@@ -285,11 +285,11 @@ DEF_OP(AtomicFetchOr) {
ldsetal(SubEmitSize, Src, GetReg(Node), MemSrc); ldsetal(SubEmitSize, Src, GetReg(Node), MemSrc);
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
orr(EmitSize, TMP3, TMP2, Src); orr(EmitSize, TMP3, TMP2, Src);
stlxr(SubEmitSize, TMP4, TMP3, MemSrc); stlxr(SubEmitSize, TMP4, TMP3, MemSrc);
cbnz(EmitSize, TMP4, &LoopTop); (void)cbnz(EmitSize, TMP4, &LoopTop);
mov(EmitSize, GetReg(Node), TMP2.R()); mov(EmitSize, GetReg(Node), TMP2.R());
} }
} }
@@ -306,11 +306,11 @@ DEF_OP(AtomicFetchXor) {
ldeoral(SubEmitSize, Src, GetReg(Node), MemSrc); ldeoral(SubEmitSize, Src, GetReg(Node), MemSrc);
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
eor(EmitSize, TMP3, TMP2, Src); eor(EmitSize, TMP3, TMP2, Src);
stlxr(SubEmitSize, TMP4, TMP3, MemSrc); stlxr(SubEmitSize, TMP4, TMP3, MemSrc);
cbnz(EmitSize, TMP4, &LoopTop); (void)cbnz(EmitSize, TMP4, &LoopTop);
mov(EmitSize, GetReg(Node), TMP2.R()); mov(EmitSize, GetReg(Node), TMP2.R());
} }
} }
@@ -326,20 +326,20 @@ DEF_OP(AtomicFetchNeg) {
// Use a CAS loop to avoid needing to emulate unaligned LLSC atomics // Use a CAS loop to avoid needing to emulate unaligned LLSC atomics
ldr(SubEmitSize, TMP2, MemSrc); ldr(SubEmitSize, TMP2, MemSrc);
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
mov(EmitSize, TMP4, TMP2); mov(EmitSize, TMP4, TMP2);
neg(EmitSize, TMP3, TMP2); neg(EmitSize, TMP3, TMP2);
casal(SubEmitSize, TMP2, TMP3, MemSrc); casal(SubEmitSize, TMP2, TMP3, MemSrc);
sub(EmitSize, TMP3, TMP2, TMP4); sub(EmitSize, TMP3, TMP2, TMP4);
cbnz(EmitSize, TMP3, &LoopTop); (void)cbnz(EmitSize, TMP3, &LoopTop);
mov(EmitSize, GetReg(Node), TMP2.R()); mov(EmitSize, GetReg(Node), TMP2.R());
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(SubEmitSize, TMP2, MemSrc); ldaxr(SubEmitSize, TMP2, MemSrc);
neg(EmitSize, TMP3, TMP2); neg(EmitSize, TMP3, TMP2);
stlxr(SubEmitSize, TMP4, TMP3, MemSrc); stlxr(SubEmitSize, TMP4, TMP3, MemSrc);
cbnz(EmitSize, TMP4, &LoopTop); (void)cbnz(EmitSize, TMP4, &LoopTop);
mov(EmitSize, GetReg(Node), TMP2.R()); mov(EmitSize, GetReg(Node), TMP2.R());
} }
} }
@@ -359,11 +359,11 @@ DEF_OP(TelemetrySetValue) {
stsetl(ARMEmitter::SubRegSize::i64Bit, TMP1, TMP2); stsetl(ARMEmitter::SubRegSize::i64Bit, TMP1, TMP2);
} else { } else {
ARMEmitter::BackwardLabel LoopTop; ARMEmitter::BackwardLabel LoopTop;
Bind(&LoopTop); (void)Bind(&LoopTop);
ldaxr(ARMEmitter::SubRegSize::i64Bit, TMP3, TMP2); ldaxr(ARMEmitter::SubRegSize::i64Bit, TMP3, TMP2);
orr(ARMEmitter::Size::i32Bit, TMP3, TMP3, Src); orr(ARMEmitter::Size::i32Bit, TMP3, TMP3, Src);
stlxr(ARMEmitter::SubRegSize::i64Bit, TMP3, TMP3, TMP2); stlxr(ARMEmitter::SubRegSize::i64Bit, TMP3, TMP3, TMP2);
cbnz(ARMEmitter::Size::i32Bit, TMP3, &LoopTop); (void)cbnz(ARMEmitter::Size::i32Bit, TMP3, &LoopTop);
} }
#endif #endif
} }
+34 -137
View File
@@ -141,7 +141,7 @@ DEF_OP(ExitFunction) {
if (!Op->CallReturnBlock.IsInvalid()) { if (!Op->CallReturnBlock.IsInvalid()) {
auto CallReturnAddressReg = GetReg(Op->CallReturnAddress).X(); auto CallReturnAddressReg = GetReg(Op->CallReturnAddress).X();
PendingCallReturnTargetLabel = &CallReturnTargets.try_emplace(Op->CallReturnBlock.ID()).first->second; PendingCallReturnTargetLabel = &CallReturnTargets.try_emplace(Op->CallReturnBlock.ID()).first->second;
adr(TMP1, &l_CallReturn); (void)adr(TMP1, &l_CallReturn);
stp<ARMEmitter::IndexType::PRE>(CallReturnAddressReg, TMP1, REG_CALLRET_SP, -0x10); stp<ARMEmitter::IndexType::PRE>(CallReturnAddressReg, TMP1, REG_CALLRET_SP, -0x10);
} else { } else {
stp<ARMEmitter::IndexType::PRE>(ARMEmitter::XReg::zr, ARMEmitter::XReg::zr, REG_CALLRET_SP, -0x10); stp<ARMEmitter::IndexType::PRE>(ARMEmitter::XReg::zr, ARMEmitter::XReg::zr, REG_CALLRET_SP, -0x10);
@@ -149,16 +149,16 @@ DEF_OP(ExitFunction) {
} else if (Op->Hint == IR::BranchHint::CheckTF) { } else if (Op->Hint == IR::BranchHint::CheckTF) {
ARMEmitter::ForwardLabel TFUnset; ARMEmitter::ForwardLabel TFUnset;
ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC])); ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC]));
cbz(ARMEmitter::Size::i32Bit, TMP1, &TFUnset); (void)cbz(ARMEmitter::Size::i32Bit, TMP1, &TFUnset);
LoadConstant(ARMEmitter::Size::i64Bit, TMP1, NewRIP); LoadConstant(ARMEmitter::Size::i64Bit, TMP1, NewRIP);
str(TMP1, STATE, offsetof(FEXCore::Core::CpuStateFrame, State.rip)); str(TMP1, STATE, offsetof(FEXCore::Core::CpuStateFrame, State.rip));
ldr(TMP2, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.Common.DispatcherLoopTop)); ldr(TMP2, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.Common.DispatcherLoopTop));
blr(TMP2); blr(TMP2);
Bind(&TFUnset); (void)Bind(&TFUnset);
} }
EmitLinkedBranch(NewRIP, Op->Hint == IR::BranchHint::Call); EmitLinkedBranch(NewRIP, Op->Hint == IR::BranchHint::Call);
Bind(&l_CallReturn); (void)Bind(&l_CallReturn);
#ifdef _M_ARM_64EC #ifdef _M_ARM_64EC
} }
#endif #endif
@@ -170,40 +170,38 @@ DEF_OP(ExitFunction) {
// First try to pop from the call-ret stack, otherwise follow the normal path (but ending in a ret) // First try to pop from the call-ret stack, otherwise follow the normal path (but ending in a ret)
ldp<ARMEmitter::IndexType::POST>(TMP1, TMP2, REG_CALLRET_SP, 0x10); ldp<ARMEmitter::IndexType::POST>(TMP1, TMP2, REG_CALLRET_SP, 0x10);
sub(TMP1, TMP1, RipReg.X()); sub(TMP1, TMP1, RipReg.X());
cbz(ARMEmitter::Size::i64Bit, TMP1, &SkipFullLookup); (void)cbz(ARMEmitter::Size::i64Bit, TMP1, &SkipFullLookup);
} }
// L1 Cache // L1 Cache
ldr(TMP1, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.Common.L1Pointer)); ldp<ARMEmitter::IndexType::OFFSET>(TMP1, TMP2, STATE, offsetof(FEXCore::Core::CpuStateFrame, State.L1Pointer));
// Calculate (tmp1 + ((ripreg & L1_ENTRIES_MASK) << 4)) for the address // Calculate (tmp1 + ((ripreg & L1_ENTRIES_MASK) << 4)) for the address
// arithmetic. ubfiz+add is marginally faster on Firestorm than // L1Mask is pre-shifted.
// and+add(shift). Same performance on Cortex. and_(ARMEmitter::Size::i64Bit, TMP2, TMP2, RipReg, ARMEmitter::ShiftType::LSL, FEXCore::ilog2(sizeof(LookupCache::LookupCacheEntry)));
static_assert(LookupCache::L1_ENTRIES_MASK == ((1u << 20) - 1)); add(TMP1, TMP1, TMP2);
ubfiz(ARMEmitter::Size::i64Bit, TMP4, RipReg, 4, 20);
add(TMP1, TMP1, TMP4);
ldp<ARMEmitter::IndexType::OFFSET>(TMP2, TMP1, TMP1, 0); ldp<ARMEmitter::IndexType::OFFSET>(TMP2, TMP1, TMP1, 0);
// Note: sub+cbnz used over cmp+br to preserve flags. // Note: sub+cbnz used over cmp+br to preserve flags.
sub(TMP1, TMP1, RipReg.X()); sub(TMP1, TMP1, RipReg.X());
cbz(ARMEmitter::Size::i64Bit, TMP1, &SkipFullLookup); (void)cbz(ARMEmitter::Size::i64Bit, TMP1, &SkipFullLookup);
ldr(TMP2, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.Common.DispatcherLoopTop)); ldr(TMP2, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.Common.DispatcherLoopTop));
str(RipReg.X(), STATE, offsetof(FEXCore::Core::CpuStateFrame, State.rip)); str(RipReg.X(), STATE, offsetof(FEXCore::Core::CpuStateFrame, State.rip));
Bind(&SkipFullLookup); (void)Bind(&SkipFullLookup);
if (Op->Hint == IR::BranchHint::Call) { if (Op->Hint == IR::BranchHint::Call) {
ARMEmitter::ForwardLabel l_CallReturn; ARMEmitter::ForwardLabel l_CallReturn;
if (!Op->CallReturnBlock.IsInvalid()) { if (!Op->CallReturnBlock.IsInvalid()) {
auto CallReturnAddressReg = GetReg(Op->CallReturnAddress).X(); auto CallReturnAddressReg = GetReg(Op->CallReturnAddress).X();
PendingCallReturnTargetLabel = &CallReturnTargets.try_emplace(Op->CallReturnBlock.ID()).first->second; PendingCallReturnTargetLabel = &CallReturnTargets.try_emplace(Op->CallReturnBlock.ID()).first->second;
adr(TMP1, &l_CallReturn); (void)adr(TMP1, &l_CallReturn);
stp<ARMEmitter::IndexType::PRE>(CallReturnAddressReg, TMP1, REG_CALLRET_SP, -0x10); stp<ARMEmitter::IndexType::PRE>(CallReturnAddressReg, TMP1, REG_CALLRET_SP, -0x10);
} else { } else {
stp<ARMEmitter::IndexType::PRE>(ARMEmitter::XReg::zr, ARMEmitter::XReg::zr, REG_CALLRET_SP, -0x10); stp<ARMEmitter::IndexType::PRE>(ARMEmitter::XReg::zr, ARMEmitter::XReg::zr, REG_CALLRET_SP, -0x10);
} }
blr(TMP2); blr(TMP2);
Bind(&l_CallReturn); (void)Bind(&l_CallReturn);
} else if (Op->Hint == IR::BranchHint::Return) { } else if (Op->Hint == IR::BranchHint::Return) {
ret(TMP2); ret(TMP2);
} else { } else {
@@ -224,7 +222,7 @@ DEF_OP(CondJump) {
auto TrueTargetLabel = JumpTarget(Op->TrueBlock); auto TrueTargetLabel = JumpTarget(Op->TrueBlock);
if (Op->FromNZCV) { if (Op->FromNZCV) {
b(MapCC(Op->Cond), TrueTargetLabel); b_OrRestart(MapCC(Op->Cond), TrueTargetLabel);
} else { } else {
uint64_t Const; uint64_t Const;
const bool isConst = IsInlineConstant(Op->Cmp2, &Const); const bool isConst = IsInlineConstant(Op->Cmp2, &Const);
@@ -237,16 +235,16 @@ DEF_OP(CondJump) {
if (Op->Cond == IR::CondClass::EQ) { if (Op->Cond == IR::CondClass::EQ) {
LOGMAN_THROW_A_FMT(Const == 0, "CondJump: Expected 0 source"); LOGMAN_THROW_A_FMT(Const == 0, "CondJump: Expected 0 source");
cbz(Size, Reg, TrueTargetLabel); cbz_OrRestart(Size, Reg, TrueTargetLabel);
} else if (Op->Cond == IR::CondClass::NEQ) { } else if (Op->Cond == IR::CondClass::NEQ) {
LOGMAN_THROW_A_FMT(Const == 0, "CondJump: Expected 0 source"); LOGMAN_THROW_A_FMT(Const == 0, "CondJump: Expected 0 source");
cbnz(Size, Reg, TrueTargetLabel); cbnz_OrRestart(Size, Reg, TrueTargetLabel);
} else if (Op->Cond == IR::CondClass::TSTZ) { } else if (Op->Cond == IR::CondClass::TSTZ) {
LOGMAN_THROW_A_FMT(Const < 64, "CondJump: Expected valid bit source"); LOGMAN_THROW_A_FMT(Const < 64, "CondJump: Expected valid bit source");
tbz(Reg, Const, TrueTargetLabel); tbz_OrRestart(Reg, Const, TrueTargetLabel);
} else if (Op->Cond == IR::CondClass::TSTNZ) { } else if (Op->Cond == IR::CondClass::TSTNZ) {
LOGMAN_THROW_A_FMT(Const < 64, "CondJump: Expected valid bit source"); LOGMAN_THROW_A_FMT(Const < 64, "CondJump: Expected valid bit source");
tbnz(Reg, Const, TrueTargetLabel); tbnz_OrRestart(Reg, Const, TrueTargetLabel);
} else { } else {
LOGMAN_THROW_A_FMT(false, "CondJump expected simple condition"); LOGMAN_THROW_A_FMT(false, "CondJump expected simple condition");
} }
@@ -262,16 +260,10 @@ DEF_OP(Syscall) {
// X1: ThreadState // X1: ThreadState
// X2: Pointer to SyscallArguments // X2: Pointer to SyscallArguments
FEXCore::IR::SyscallFlags Flags = Op->Flags;
PushDynamicRegs(TMP1); PushDynamicRegs(TMP1);
uint32_t GPRSpillMask = ~0U; uint32_t GPRSpillMask = ~0U;
uint32_t FPRSpillMask = ~0U; uint32_t FPRSpillMask = ~0U;
if ((Flags & FEXCore::IR::SyscallFlags::NOSYNCSTATEONENTRY) == FEXCore::IR::SyscallFlags::NOSYNCSTATEONENTRY) {
// Need to spill all caller saved registers still
GPRSpillMask = CALLER_GPR_MASK;
FPRSpillMask = CALLER_FPR_MASK;
}
SpillStaticRegs(TMP1, true, GPRSpillMask, FPRSpillMask); SpillStaticRegs(TMP1, true, GPRSpillMask, FPRSpillMask);
@@ -305,117 +297,22 @@ DEF_OP(Syscall) {
add(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::rsp, ARMEmitter::Reg::rsp, SPOffset); add(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::rsp, ARMEmitter::Reg::rsp, SPOffset);
if ((Flags & FEXCore::IR::SyscallFlags::NORETURN) != FEXCore::IR::SyscallFlags::NORETURN) { // Result is now in x0
// Result is now in x0 // Fix the stack and any values that were stepped on
// Fix the stack and any values that were stepped on FillStaticRegs(true, GPRSpillMask, FPRSpillMask, ARMEmitter::Reg::r1, ARMEmitter::Reg::r2);
FillStaticRegs(true, GPRSpillMask, FPRSpillMask, ARMEmitter::Reg::r1, ARMEmitter::Reg::r2);
// Now the registers we've spilled are back in their original host registers // Now the registers we've spilled are back in their original host registers
// We can safely claim we are no longer in a syscall // We can safely claim we are no longer in a syscall
str(ARMEmitter::XReg::zr, STATE, offsetof(FEXCore::Core::CpuStateFrame, InSyscallInfo)); str(ARMEmitter::XReg::zr, STATE, offsetof(FEXCore::Core::CpuStateFrame, InSyscallInfo));
PopDynamicRegs(); PopDynamicRegs();
if ((Flags & FEXCore::IR::SyscallFlags::NORETURNEDRESULT) != FEXCore::IR::SyscallFlags::NORETURNEDRESULT) { const auto OSABI = CTX->SyscallHandler->GetOSABI();
// 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(InlineSyscall) { if (OSABI != FEXCore::HLE::SyscallOSABI::OS_GENERIC) {
auto Op = IROp->C<IR::IROp_InlineSyscall>(); // Move result to its destination register.
// Arguments are passed as follows: // Only if `NORETURNEDRESULT` wasn't set, otherwise we might overwrite the CPUState refilled with `FillStaticRegs`
// X8: SyscallNumber - RA INTERSECT mov(ARMEmitter::Size::i64Bit, GetReg(Node), ARMEmitter::Reg::r0);
// X0: Arg0 & Return
// X1: Arg1
// X2: Arg2
// X3: Arg3
// X4: Arg4 - RA INTERSECT
// X5: Arg5 - RA INTERSECT
// X6: Arg6 - Doesn't exist in x86-64 land. RA INTERSECT
// One argument is removed from the SyscallArguments::MAX_ARGS since the first argument was syscall number
const static std::array<ARMEmitter::XRegister, FEXCore::HLE::SyscallArguments::MAX_ARGS - 1> RegArgs = {
{ARMEmitter::XReg::x0, ARMEmitter::XReg::x1, ARMEmitter::XReg::x2, ARMEmitter::XReg::x3, ARMEmitter::XReg::x4, ARMEmitter::XReg::x5}};
bool Intersects {};
// We always need to spill x8 since we can't know if it is live at this SSA location
uint32_t SpillMask = 1U << 8;
for (uint32_t i = 0; i < FEXCore::HLE::SyscallArguments::MAX_ARGS - 1; ++i) {
if (Op->Header.Args[i].IsInvalid()) {
break;
}
auto Reg = GetReg(Op->Header.Args[i]);
if (Reg == ARMEmitter::Reg::r8 || Reg == ARMEmitter::Reg::r4 || Reg == ARMEmitter::Reg::r5) {
SpillMask |= (1U << Reg.Idx());
Intersects = true;
}
}
// Ordering is incredibly important here
// We must spill any overlapping registers first THEN claim we are in a syscall without invalidating state at all
// Only spill the registers that intersect with our usage
SpillStaticRegs(TMP1, false, SpillMask);
// Now that we are spilled, store in the state that we are in a syscall
// Still without overwriting registers that matter
// 16bit LoadConstant to be a single instruction
// We must always spill at least one register (x8) so this value always has a bit set
// This gives the signal handler a value to check to see if we are in a syscall at all
LoadConstant(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::r0, SpillMask & 0xFFFF);
str(ARMEmitter::XReg::x0, STATE, offsetof(FEXCore::Core::CpuStateFrame, InSyscallInfo));
// Now that we have claimed to be a syscall we can set up the arguments
const auto EmitSize = CTX->Config.Is64BitMode() ? ARMEmitter::Size::i64Bit : ARMEmitter::Size::i32Bit;
const auto EmitSubSize = CTX->Config.Is64BitMode() ? ARMEmitter::SubRegSize::i64Bit : ARMEmitter::SubRegSize::i32Bit;
if (Intersects) {
for (uint32_t i = 0; i < FEXCore::HLE::SyscallArguments::MAX_ARGS - 1; ++i) {
if (Op->Header.Args[i].IsInvalid()) {
break;
}
auto Reg = GetReg(Op->Header.Args[i]);
if (SpillMask & (1U << Reg.Idx())) {
// In the case of intersection with x4, x5, or x8 then these are currently SRA
// for registers RAX, RDX, and RSP. Which have just been spilled
// Just load back from the context.
auto Correlation = GetX86RegRelationToARMReg(Reg);
LOGMAN_THROW_A_FMT(Correlation != X86State::REG_INVALID, "Invalid register mapping");
ldr(EmitSubSize, RegArgs[i].R(), STATE, offsetof(FEXCore::Core::CpuStateFrame, State.gregs[Correlation]));
} else {
mov(EmitSize, RegArgs[i].R(), Reg);
}
}
} else {
for (uint32_t i = 0; i < FEXCore::HLE::SyscallArguments::MAX_ARGS - 1; ++i) {
if (Op->Header.Args[i].IsInvalid()) {
break;
}
mov(EmitSize, RegArgs[i].R(), GetReg(Op->Header.Args[i]));
}
}
LoadConstant(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::r8, Op->HostSyscallNumber);
svc(0);
// On updated signal mask we can receive a signal RIGHT HERE
if ((Op->Flags & FEXCore::IR::SyscallFlags::NORETURN) != FEXCore::IR::SyscallFlags::NORETURN) {
// Now that we are done in the syscall we need to carefully peel back the state
// First unspill the registers from before
FillStaticRegs(false, SpillMask, ~0U, ARMEmitter::Reg::r8, ARMEmitter::Reg::r1);
// Now the registers we've spilled are back in their original host registers
// We can safely claim we are no longer in a syscall
str(ARMEmitter::XReg::zr, STATE, offsetof(FEXCore::Core::CpuStateFrame, InSyscallInfo));
// Result is now in x0
// Move result to its destination register
mov(EmitSize, GetReg(Node), ARMEmitter::Reg::r0);
} }
} }
@@ -458,7 +355,7 @@ DEF_OP(ValidateCode) {
while (len >= Size) { while (len >= Size) {
LoadData(); LoadData();
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, TMP2); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, TMP2);
cbnz(ARMEmitter::Size::i64Bit, TMP1, &Fail); cbnz_OrRestart(ARMEmitter::Size::i64Bit, TMP1, &Fail);
len -= Size; len -= Size;
Offset += Size; Offset += Size;
} }
@@ -486,10 +383,10 @@ DEF_OP(ValidateCode) {
ARMEmitter::ForwardLabel End; ARMEmitter::ForwardLabel End;
LoadConstant(ARMEmitter::Size::i32Bit, Dst, 0); LoadConstant(ARMEmitter::Size::i32Bit, Dst, 0);
b(&End); b_OrRestart(&End);
Bind(&Fail); BindOrRestart(&Fail);
LoadConstant(ARMEmitter::Size::i32Bit, Dst, 1); LoadConstant(ARMEmitter::Size::i32Bit, Dst, 1);
Bind(&End); BindOrRestart(&End);
} }
DEF_OP(ThreadRemoveCodeEntry) { DEF_OP(ThreadRemoveCodeEntry) {
+56 -54
View File
@@ -11,8 +11,6 @@ desc: Main glue logic of the arm64 splatter backend
$end_info$ $end_info$
*/ */
#include "Common/SoftFloat.h"
#include "Interface/Context/Context.h" #include "Interface/Context/Context.h"
#include "Interface/Core/LookupCache.h" #include "Interface/Core/LookupCache.h"
#include "Interface/Core/Dispatcher/Dispatcher.h" #include "Interface/Core/Dispatcher/Dispatcher.h"
@@ -30,6 +28,7 @@ $end_info$
#include <FEXCore/Utils/CompilerDefs.h> #include <FEXCore/Utils/CompilerDefs.h>
#include <FEXCore/Utils/EnumUtils.h> #include <FEXCore/Utils/EnumUtils.h>
#include <FEXCore/Utils/LogManager.h> #include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/LongJump.h>
#include <FEXCore/Utils/Profiler.h> #include <FEXCore/Utils/Profiler.h>
#include <FEXCore/Utils/Telemetry.h> #include <FEXCore/Utils/Telemetry.h>
#include <FEXCore/Utils/TypeDefines.h> #include <FEXCore/Utils/TypeDefines.h>
@@ -37,7 +36,6 @@ $end_info$
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <limits>
#include <unistd.h> #include <unistd.h>
namespace { namespace {
@@ -495,7 +493,7 @@ void Arm64JITCore::Op_Unhandled(const IR::IROp_Header* IROp, IR::Ref Node) {
} }
} }
static void DirectBlockDelinker(FEXCore::Core::CpuStateFrame* Frame, FEXCore::Context::ExitFunctionLinkData* Record, bool Call) { static void DirectBlockDelinker(FEXCore::Context::ExitFunctionLinkData* Record, bool Call) {
uintptr_t JumpThunkStartAddress = reinterpret_cast<uintptr_t>(Record) - 0x10; uintptr_t JumpThunkStartAddress = reinterpret_cast<uintptr_t>(Record) - 0x10;
uintptr_t CallerAddress = JumpThunkStartAddress + Record->CallerOffset; uintptr_t CallerAddress = JumpThunkStartAddress + Record->CallerOffset;
auto BranchOffset = JumpThunkStartAddress / 4 - CallerAddress / 4; auto BranchOffset = JumpThunkStartAddress / 4 - CallerAddress / 4;
@@ -513,7 +511,7 @@ static void DirectBlockDelinker(FEXCore::Core::CpuStateFrame* Frame, FEXCore::Co
ARMEmitter::Emitter::ClearICache(reinterpret_cast<void*>(CallerAddress), 4); ARMEmitter::Emitter::ClearICache(reinterpret_cast<void*>(CallerAddress), 4);
} }
static void IndirectBlockDelinker(FEXCore::Core::CpuStateFrame* Frame, FEXCore::Context::ExitFunctionLinkData* Record) { static void IndirectBlockDelinker(FEXCore::Context::ExitFunctionLinkData* Record) {
uintptr_t JumpThunkStartAddress = reinterpret_cast<uintptr_t>(Record) - 0x10; uintptr_t JumpThunkStartAddress = reinterpret_cast<uintptr_t>(Record) - 0x10;
uint32_t BranchInst = 0; uint32_t BranchInst = 0;
ARMEmitter::Emitter BranchEmit(reinterpret_cast<uint8_t*>(&BranchInst), 4); ARMEmitter::Emitter BranchEmit(reinterpret_cast<uint8_t*>(&BranchInst), 4);
@@ -540,7 +538,7 @@ uint64_t Arm64JITCore::ExitFunctionLink(FEXCore::Core::CpuStateFrame* Frame, FEX
// Guard the LookupCache lock with the code invalidation mutex, to avoid issues with forking // Guard the LookupCache lock with the code invalidation mutex, to avoid issues with forking
auto lk_inval = auto lk_inval =
GuardSignalDeferringSection<std::shared_lock>(static_cast<Context::ContextImpl*>(Thread->CTX)->CodeInvalidationMutex, Thread); GuardSignalDeferringSection<std::shared_lock>(static_cast<Context::ContextImpl*>(Thread->CTX)->CodeInvalidationMutex, Thread);
HostCode = Thread->LookupCache->FindBlock(GuestRip); HostCode = Thread->LookupCache->FindBlock(Thread, GuestRip);
} }
if (!HostCode) { if (!HostCode) {
// Hold a reference to the code buffer, to avoid linking unmapped code if compilation triggers a recreation. // Hold a reference to the code buffer, to avoid linking unmapped code if compilation triggers a recreation.
@@ -565,7 +563,7 @@ uint64_t Arm64JITCore::ExitFunctionLink(FEXCore::Core::CpuStateFrame* Frame, FEX
auto lk_inval = GuardSignalDeferringSection<std::shared_lock>(static_cast<Context::ContextImpl*>(Thread->CTX)->CodeInvalidationMutex, Thread); auto lk_inval = GuardSignalDeferringSection<std::shared_lock>(static_cast<Context::ContextImpl*>(Thread->CTX)->CodeInvalidationMutex, Thread);
// Lock here is necessary to prevent simultaneous linking and delinking // Lock here is necessary to prevent simultaneous linking and delinking
auto lk = Thread->LookupCache->AcquireLock(); auto lk = Thread->LookupCache->AcquireWriteLock();
// For non-calls, this would extend into the block's code, however that's fine as an out-of-range adr would never // For non-calls, this would extend into the block's code, however that's fine as an out-of-range adr would never
// be generated avoiding any false positives. // be generated avoiding any false positives.
@@ -578,14 +576,17 @@ uint64_t Arm64JITCore::ExitFunctionLink(FEXCore::Core::CpuStateFrame* Frame, FEX
if (KnownCallMarkerInst == ExpectedKnownCallMarkerInst) { if (KnownCallMarkerInst == ExpectedKnownCallMarkerInst) {
BranchEmit.bl(BranchOffset); BranchEmit.bl(BranchOffset);
Thread->LookupCache->AddBlockLink(GuestRip, Record, [](FEXCore::Core::CpuStateFrame* Frame, FEXCore::Context::ExitFunctionLinkData* Record) { Thread->LookupCache->AddBlockLink(
DirectBlockDelinker(Frame, Record, true); GuestRip, Record,
}); [](FEXCore::Context::ExitFunctionLinkData* Record) { DirectBlockDelinker(Record, true); }, lk);
} else { } else {
BranchEmit.b(BranchOffset); BranchEmit.b(BranchOffset);
Thread->LookupCache->AddBlockLink(GuestRip, Record, [](FEXCore::Core::CpuStateFrame* Frame, FEXCore::Context::ExitFunctionLinkData* Record) { Thread->LookupCache->AddBlockLink(
DirectBlockDelinker(Frame, Record, false); GuestRip, Record,
}); [](FEXCore::Context::ExitFunctionLinkData* Record) {
DirectBlockDelinker(Record, false);
},
lk);
} }
std::atomic_ref<uint32_t>(*reinterpret_cast<uint32_t*>(CallerAddress)).store(BranchInst, std::memory_order::relaxed); std::atomic_ref<uint32_t>(*reinterpret_cast<uint32_t*>(CallerAddress)).store(BranchInst, std::memory_order::relaxed);
@@ -604,7 +605,7 @@ uint64_t Arm64JITCore::ExitFunctionLink(FEXCore::Core::CpuStateFrame* Frame, FEX
std::atomic_ref<uint32_t>(*reinterpret_cast<uint32_t*>(JumpThunkStartAddress)).store(LdrInst, std::memory_order::relaxed); std::atomic_ref<uint32_t>(*reinterpret_cast<uint32_t*>(JumpThunkStartAddress)).store(LdrInst, std::memory_order::relaxed);
ARMEmitter::Emitter::ClearICache(reinterpret_cast<void*>(JumpThunkStartAddress), 4); ARMEmitter::Emitter::ClearICache(reinterpret_cast<void*>(JumpThunkStartAddress), 4);
Thread->LookupCache->AddBlockLink(GuestRip, Record, IndirectBlockDelinker); Thread->LookupCache->AddBlockLink(GuestRip, Record, IndirectBlockDelinker, lk);
} }
return HostCode; return HostCode;
@@ -669,15 +670,6 @@ Arm64JITCore::Arm64JITCore(FEXCore::Context::ContextImpl* ctx, FEXCore::Core::In
CurrentCodeBuffer = CodeBuffers.GetLatest(); CurrentCodeBuffer = CodeBuffers.GetLatest();
ThreadState->LookupCache->Shared = CurrentCodeBuffer->LookupCache.get(); ThreadState->LookupCache->Shared = CurrentCodeBuffer->LookupCache.get();
// Setup dynamic dispatch.
if (ParanoidTSO()) {
RT_LoadMemTSO = &Arm64JITCore::Op_ParanoidLoadMemTSO;
RT_StoreMemTSO = &Arm64JITCore::Op_ParanoidStoreMemTSO;
} else {
RT_LoadMemTSO = &Arm64JITCore::Op_LoadMemTSO;
RT_StoreMemTSO = &Arm64JITCore::Op_StoreMemTSO;
}
} }
void Arm64JITCore::EmitDetectionString() { void Arm64JITCore::EmitDetectionString() {
@@ -689,13 +681,13 @@ void Arm64JITCore::EmitDetectionString() {
void Arm64JITCore::ClearCache() { void Arm64JITCore::ClearCache() {
// NOTE: Holding on to the reference here is required to ensure validity of the WriteLock mutex // NOTE: Holding on to the reference here is required to ensure validity of the WriteLock mutex
auto PrevCodeBuffer = CurrentCodeBuffer; auto PrevCodeBuffer = CurrentCodeBuffer;
std::lock_guard lk(PrevCodeBuffer->LookupCache->WriteLock); auto lk = PrevCodeBuffer->LookupCache->AcquireWriteLock();
auto CodeBuffer = GetEmptyCodeBuffer(); auto CodeBuffer = GetEmptyCodeBuffer();
SetBuffer(CodeBuffer->Ptr, CodeBuffer->Size); SetBuffer(CodeBuffer->Ptr, CodeBuffer->Size);
EmitDetectionString(); EmitDetectionString();
ThreadState->LookupCache->ChangeGuestToHostMapping(*PrevCodeBuffer, *CurrentCodeBuffer->LookupCache); ThreadState->LookupCache->ChangeGuestToHostMapping(*PrevCodeBuffer, *CurrentCodeBuffer->LookupCache, lk);
} }
Arm64JITCore::~Arm64JITCore() {} Arm64JITCore::~Arm64JITCore() {}
@@ -748,11 +740,11 @@ void Arm64JITCore::EmitTFCheck() {
// Note that this needs to be before the below suspend checks, as X86 checks this flag immediately after executing an instruction. // Note that this needs to be before the below suspend checks, as X86 checks this flag immediately after executing an instruction.
ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC])); ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC]));
cbz(ARMEmitter::Size::i32Bit, TMP1, &l_TFUnset); (void)cbz(ARMEmitter::Size::i32Bit, TMP1, &l_TFUnset);
// X86 semantically checks TF after executing each instruction, so e.g. setting a context with TF set will execute a single instruction // X86 semantically checks TF after executing each instruction, so e.g. setting a context with TF set will execute a single instruction
// and then raise an exception. However on the FEX side this is simpler to implement by checking at the start of each instruction, handle this by having bit 1 being unset in the flag state indicate that TF is blocked for a single instruction. // and then raise an exception. However on the FEX side this is simpler to implement by checking at the start of each instruction, handle this by having bit 1 being unset in the flag state indicate that TF is blocked for a single instruction.
tbz(TMP1, 1, &l_TFBlocked); (void)tbz(TMP1, 1, &l_TFBlocked);
// Block TF for a single instruction when the frontend jumps to a new context by unsetting bit 1. // Block TF for a single instruction when the frontend jumps to a new context by unsetting bit 1.
ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC])); ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC]));
@@ -775,11 +767,11 @@ void Arm64JITCore::EmitTFCheck() {
ldr(TMP1, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.Common.GuestSignal_SIGTRAP)); ldr(TMP1, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.Common.GuestSignal_SIGTRAP));
br(TMP1); br(TMP1);
Bind(&l_TFBlocked); (void)Bind(&l_TFBlocked);
// If TF was blocked for this instruction, unblock it for the next. // If TF was blocked for this instruction, unblock it for the next.
LoadConstant(ARMEmitter::Size::i32Bit, TMP1, 0b11); LoadConstant(ARMEmitter::Size::i32Bit, TMP1, 0b11);
strb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC])); strb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC]));
Bind(&l_TFUnset); (void)Bind(&l_TFUnset);
} }
void Arm64JITCore::EmitSuspendInterruptCheck() { void Arm64JITCore::EmitSuspendInterruptCheck() {
@@ -797,14 +789,14 @@ void Arm64JITCore::EmitSuspendInterruptCheck() {
ARMEmitter::ForwardLabel l_NoSuspend; ARMEmitter::ForwardLabel l_NoSuspend;
cbz(ARMEmitter::Size::i32Bit, TMP2, &l_NoSuspend); cbz(ARMEmitter::Size::i32Bit, TMP2, &l_NoSuspend);
brk(SuspendMagic); brk(SuspendMagic);
Bind(&l_NoSuspend); (void)Bind(&l_NoSuspend);
#endif #endif
} }
void Arm64JITCore::EmitEntryPoint(ARMEmitter::BackwardLabel& HeaderLabel, bool CheckTF) { void Arm64JITCore::EmitEntryPoint(ARMEmitter::BackwardLabel& HeaderLabel, bool CheckTF) {
// Get the address of the JITCodeHeader and store in to the core state. // Get the address of the JITCodeHeader and store in to the core state.
// Two instruction cost, each 1 cycle. // Two instruction cost, each 1 cycle.
adr(TMP1, &HeaderLabel); adr_OrRestart(TMP1, &HeaderLabel);
str(TMP1, STATE, offsetof(FEXCore::Core::CPUState, InlineJITBlockHeader)); str(TMP1, STATE, offsetof(FEXCore::Core::CPUState, InlineJITBlockHeader));
if (CheckTF) { if (CheckTF) {
@@ -821,21 +813,32 @@ void Arm64JITCore::EmitEntryPoint(ARMEmitter::BackwardLabel& HeaderLabel, bool C
sub(ARMEmitter::Size::i64Bit, ARMEmitter::XReg::rsp, ARMEmitter::XReg::rsp, TMP1, ARMEmitter::ExtendedType::LSL_64, 0); sub(ARMEmitter::Size::i64Bit, ARMEmitter::XReg::rsp, ARMEmitter::XReg::rsp, TMP1, ARMEmitter::ExtendedType::LSL_64, 0);
} }
} }
EmitSuspendInterruptCheck();
} }
CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size, bool SingleInst, const FEXCore::IR::IRListView* IR, CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size, bool SingleInst, const FEXCore::IR::IRListView* IR,
FEXCore::Core::DebugData* DebugData, bool CheckTF) { FEXCore::Core::DebugData* DebugData, bool CheckTF) {
FEXCORE_PROFILE_SCOPED("Arm64::CompileCode"); FEXCORE_PROFILE_SCOPED("Arm64::CompileCode");
JumpTargets.clear();
CallReturnTargets.clear();
PendingJumpThunks.clear();
uint32_t SSACount = IR->GetSSACount();
JumpTargets.resize(IR->GetHeader()->BlockCount, {});
this->Entry = Entry; this->Entry = Entry;
this->DebugData = DebugData; this->DebugData = DebugData;
this->IR = IR; this->IR = IR;
RequiresFarARM64Jumps = false;
switch (static_cast<RestartOptions::Control>(FEXCore::LongJump::SetJump(RestartControl.RestartJump))) {
case RestartOptions::Control::Incoming:
// Nothing
break;
case RestartOptions::Control::EnableFarARM64Jumps: RequiresFarARM64Jumps = true; break;
default: ERROR_AND_DIE_FMT("Unhandled Arm64 restart condition!");
}
uint32_t SSACount = IR->GetSSACount();
JumpTargets.clear();
CallReturnTargets.clear();
PendingJumpThunks.clear();
JumpTargets.resize(IR->GetHeader()->BlockCount, {});
CodeData.EntryPoints.clear(); CodeData.EntryPoints.clear();
// Fairly excessive buffer range to make sure we don't overflow // Fairly excessive buffer range to make sure we don't overflow
@@ -850,7 +853,7 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size
// Put the code header at the start of the data block. // Put the code header at the start of the data block.
ARMEmitter::BackwardLabel JITCodeHeaderLabel {}; ARMEmitter::BackwardLabel JITCodeHeaderLabel {};
Bind(&JITCodeHeaderLabel); (void)Bind(&JITCodeHeaderLabel);
JITCodeHeader* CodeHeader = GetCursorAddress<JITCodeHeader*>(); JITCodeHeader* CodeHeader = GetCursorAddress<JITCodeHeader*>();
CursorIncrement(sizeof(JITCodeHeader)); CursorIncrement(sizeof(JITCodeHeader));
@@ -898,7 +901,7 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size
if (PendingTargetLabel->Backward.Location) { if (PendingTargetLabel->Backward.Location) {
EmitSuspendInterruptCheck(); EmitSuspendInterruptCheck();
} }
b(PendingTargetLabel); b_OrRestart(PendingTargetLabel);
PendingTargetLabel = nullptr; PendingTargetLabel = nullptr;
} }
@@ -908,14 +911,14 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size
const auto IsReturnTarget = CallReturnTargets.try_emplace(Node).first; const auto IsReturnTarget = CallReturnTargets.try_emplace(Node).first;
if (PendingTargetLabel) { if (PendingTargetLabel) {
// If there is a fallthrough branch to this block, skip over the entrypoint code. // If there is a fallthrough branch to this block, skip over the entrypoint code.
b(Target); b_OrRestart(Target);
} else if (PendingCallReturnTargetLabel && PendingCallReturnTargetLabel != &IsReturnTarget->second) { } else if (PendingCallReturnTargetLabel && PendingCallReturnTargetLabel != &IsReturnTarget->second) {
// If we just emitted a call, but the block we're now emitting is not the return block so don't fallthrough. // If we just emitted a call, but the block we're now emitting is not the return block so don't fallthrough.
b(PendingCallReturnTargetLabel); b_OrRestart(PendingCallReturnTargetLabel);
} }
PendingCallReturnTargetLabel = nullptr; PendingCallReturnTargetLabel = nullptr;
Bind(&IsReturnTarget->second); BindOrRestart(&IsReturnTarget->second);
CodeData.EntryPoints.emplace(BlockStartRIP, GetCursorAddress<uint8_t*>()); CodeData.EntryPoints.emplace(BlockStartRIP, GetCursorAddress<uint8_t*>());
DebugData->GuestOpcodes.push_back({BlockIROp->GuestEntryOffset, GetCursorAddress<uint8_t*>() - CodeData.BlockBegin}); DebugData->GuestOpcodes.push_back({BlockIROp->GuestEntryOffset, GetCursorAddress<uint8_t*>() - CodeData.BlockBegin});
@@ -924,18 +927,16 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size
if (PendingCallReturnTargetLabel) { if (PendingCallReturnTargetLabel) {
// If there is still a pending call return target, then the block we're emitting is not the return block so don't fallthrough. // If there is still a pending call return target, then the block we're emitting is not the return block so don't fallthrough.
b(PendingCallReturnTargetLabel); b_OrRestart(PendingCallReturnTargetLabel);
PendingCallReturnTargetLabel = nullptr; PendingCallReturnTargetLabel = nullptr;
} }
PendingTargetLabel = nullptr; PendingTargetLabel = nullptr;
Bind(Target); BindOrRestart(Target);
} }
for (auto [CodeNode, IROp] : IR->GetCode(BlockNode)) { for (auto [CodeNode, IROp] : IR->GetCode(BlockNode)) {
switch (IROp->Op) { switch (IROp->Op) {
#define REGISTER_OP_RT(op, x) \
case FEXCore::IR::IROps::OP_##op: std::invoke(RT_##x, this, IROp, CodeNode); break
#define REGISTER_OP(op, x) \ #define REGISTER_OP(op, x) \
case FEXCore::IR::IROps::OP_##op: Op_##x(IROp, CodeNode); break case FEXCore::IR::IROps::OP_##op: Op_##x(IROp, CodeNode); break
@@ -956,7 +957,7 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size
if (PendingTargetLabel->Backward.Location) { if (PendingTargetLabel->Backward.Location) {
EmitSuspendInterruptCheck(); EmitSuspendInterruptCheck();
} }
b(PendingTargetLabel); b_OrRestart(PendingTargetLabel);
} }
PendingTargetLabel = nullptr; PendingTargetLabel = nullptr;
@@ -967,21 +968,21 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size
ARMEmitter::ForwardLabel l_DoLink; ARMEmitter::ForwardLabel l_DoLink;
uint64_t ThunkAddress = GetCursorAddress<uint64_t>(); uint64_t ThunkAddress = GetCursorAddress<uint64_t>();
Bind(&PendingJumpThunk.Label); BindOrRestart(&PendingJumpThunk.Label);
b(&l_DoLink); b_OrRestart(&l_DoLink);
br(TMP1); br(TMP1);
Bind(&l_DoLink); BindOrRestart(&l_DoLink);
ldr(TMP1, &l_ExitLink); ldr(TMP1, &l_ExitLink);
blr(TMP1); blr(TMP1);
// This is a ExitFunctionLinkData struct // This is a ExitFunctionLinkData struct
Bind(&l_ExitLink); BindOrRestart(&l_ExitLink);
dc64(0); // HostCode dc64(0); // HostCode
dc64(PendingJumpThunk.GuestRIP); // GuestRIP dc64(PendingJumpThunk.GuestRIP); // GuestRIP
dc64(PendingJumpThunk.CallerAddress - ThunkAddress); // CallerOffset dc64(PendingJumpThunk.CallerAddress - ThunkAddress); // CallerOffset
} }
Bind(&l_ExitLink); BindOrRestart(&l_ExitLink);
dc64(ThreadState->CurrentFrame->Pointers.Common.ExitFunctionLinker); dc64(ThreadState->CurrentFrame->Pointers.Common.ExitFunctionLinker);
// CodeSize not including the header or tail data. // CodeSize not including the header or tail data.
@@ -1065,7 +1066,8 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size
"doesn't match up!\n"); "doesn't match up!\n");
if (auto Prev = CheckCodeBufferUpdate()) { if (auto Prev = CheckCodeBufferUpdate()) {
Allocator::VirtualDontNeed(ThreadState->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE); Allocator::VirtualDontNeed(ThreadState->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE);
ThreadState->LookupCache->ChangeGuestToHostMapping(*Prev, *CurrentCodeBuffer->LookupCache); auto lk = ThreadState->LookupCache->AcquireWriteLock();
ThreadState->LookupCache->ChangeGuestToHostMapping(*Prev, *CurrentCodeBuffer->LookupCache, lk);
} }
// NOTE: 16-byte alignment of the new cursor offset must be preserved for block linking records // NOTE: 16-byte alignment of the new cursor offset must be preserved for block linking records
+182 -14
View File
@@ -23,6 +23,7 @@ $end_info$
#include <FEXCore/fextl/memory.h> #include <FEXCore/fextl/memory.h>
#include <FEXCore/fextl/string.h> #include <FEXCore/fextl/string.h>
#include <FEXCore/fextl/vector.h> #include <FEXCore/fextl/vector.h>
#include <FEXCore/Utils/LongJump.h>
#include <CodeEmitter/Emitter.h> #include <CodeEmitter/Emitter.h>
@@ -60,14 +61,25 @@ public:
} }
private: private:
FEX_CONFIG_OPT(ParanoidTSO, PARANOIDTSO);
const bool HostSupportsSVE128 {}; const bool HostSupportsSVE128 {};
const bool HostSupportsSVE256 {}; const bool HostSupportsSVE256 {};
const bool HostSupportsAVX256 {}; const bool HostSupportsAVX256 {};
const bool HostSupportsRPRES {}; const bool HostSupportsRPRES {};
const bool HostSupportsAFP {}; const bool HostSupportsAFP {};
struct RestartOptions {
FEXCore::LongJump::JumpBuf RestartJump;
enum class Control : uint64_t {
Incoming = 0,
EnableFarARM64Jumps = 1,
};
};
// FEXCore makes assumptions in the JIT about certain conditions being true.
// In the rare case when those assumptions are broken, FEX needs to safely restart the JIT.
RestartOptions RestartControl {};
bool RequiresFarARM64Jumps {};
ARMEmitter::BiDirectionalLabel* PendingTargetLabel {}; ARMEmitter::BiDirectionalLabel* PendingTargetLabel {};
ARMEmitter::BiDirectionalLabel* PendingCallReturnTargetLabel {}; ARMEmitter::BiDirectionalLabel* PendingCallReturnTargetLabel {};
FEXCore::Context::ContextImpl* CTX {}; FEXCore::Context::ContextImpl* CTX {};
@@ -331,14 +343,179 @@ private:
void EmitLinkedBranch(uint64_t GuestRIP, bool Call) { void EmitLinkedBranch(uint64_t GuestRIP, bool Call) {
PendingJumpThunks.push_back({GetCursorAddress<uint64_t>(), GuestRIP, {}}); PendingJumpThunks.push_back({GetCursorAddress<uint64_t>(), GuestRIP, {}});
auto& Thunk = PendingJumpThunks.back(); auto& Thunk = PendingJumpThunks.back();
Bind(&Thunk.Label); BindOrRestart(&Thunk.Label);
if (Call) { if (Call) {
bl(&Thunk.Label); bl_OrRestart(&Thunk.Label);
} else { } else {
b(&Thunk.Label); b_OrRestart(&Thunk.Label);
} }
} }
// Restart helpers
template<ARMEmitter::IsLabel T>
void bl_OrRestart(T* Label) {
if (bl(Label) == ARMEmitter::BranchEncodeSucceeded::Success) {
return;
}
// We can support this but currently unnecessary.
ERROR_AND_DIE_FMT("Tried to branch larger than 128MB away!");
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
template<ARMEmitter::IsLabel T>
void b_OrRestart(T* Label) {
if (b(Label) == ARMEmitter::BranchEncodeSucceeded::Success) {
return;
}
// We can support this but currently unnecessary.
ERROR_AND_DIE_FMT("Tried to branch larger than 128MB away!");
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
template<ARMEmitter::IsLabel T>
void b_OrRestart(ARMEmitter::Condition Cond, T* Label) {
if (RequiresFarARM64Jumps) {
ARMEmitter::ForwardLabel Skip {};
// Wrap a manual Cond check around an unconditional branch; this can encode larger offsets
(void)b(InvertCondition(Cond), &Skip);
if (b(Label) == ARMEmitter::BranchEncodeSucceeded::Failure) {
ERROR_AND_DIE_FMT("Tried to branch larger than 128MB away!");
}
(void)Bind(&Skip);
return;
}
if (b(Cond, Label) == ARMEmitter::BranchEncodeSucceeded::Success) {
return;
}
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
template<ARMEmitter::IsLabel T>
void cbz_OrRestart(ARMEmitter::Size s, ARMEmitter::Register rt, T* Label) {
if (RequiresFarARM64Jumps) {
ARMEmitter::ForwardLabel Skip {};
// Wrap a manual Cond check around an unconditional branch; this can encode larger offsets
(void)cbnz(s, rt, &Skip);
if (b(Label) == ARMEmitter::BranchEncodeSucceeded::Failure) {
ERROR_AND_DIE_FMT("Tried to branch larger than 128MB away!");
}
(void)Bind(&Skip);
return;
}
if (cbz(s, rt, Label) == ARMEmitter::BranchEncodeSucceeded::Success) {
return;
}
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
template<ARMEmitter::IsLabel T>
void cbnz_OrRestart(ARMEmitter::Size s, ARMEmitter::Register rt, T* Label) {
if (RequiresFarARM64Jumps) {
ARMEmitter::ForwardLabel Skip {};
// Wrap a manual Cond check around an unconditional branch; this can encode larger offsets
(void)cbz(s, rt, &Skip);
if (b(Label) == ARMEmitter::BranchEncodeSucceeded::Failure) {
ERROR_AND_DIE_FMT("Tried to branch larger than 128MB away!");
}
(void)Bind(&Skip);
return;
}
if (cbnz(s, rt, Label) == ARMEmitter::BranchEncodeSucceeded::Success) {
return;
}
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
template<ARMEmitter::IsLabel T>
void tbz_OrRestart(ARMEmitter::Register rt, uint32_t Bit, T* Label) {
if (RequiresFarARM64Jumps) {
ARMEmitter::ForwardLabel Skip {};
// Wrap a manual Cond check around an unconditional branch; this can encode larger offsets
(void)tbnz(rt, Bit, &Skip);
if (b(Label) == ARMEmitter::BranchEncodeSucceeded::Failure) {
ERROR_AND_DIE_FMT("Tried to branch larger than 128MB away!");
}
(void)Bind(&Skip);
return;
}
if (tbz(rt, Bit, Label) == ARMEmitter::BranchEncodeSucceeded::Success) {
return;
}
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
template<ARMEmitter::IsLabel T>
void tbnz_OrRestart(ARMEmitter::Register rt, uint32_t Bit, T* Label) {
if (RequiresFarARM64Jumps) {
ARMEmitter::ForwardLabel Skip {};
// Wrap a manual Cond check around an unconditional branch; this can encode larger offsets
(void)tbz(rt, Bit, &Skip);
if (b(Label) == ARMEmitter::BranchEncodeSucceeded::Failure) {
ERROR_AND_DIE_FMT("Tried to branch larger than 128MB away!");
}
(void)Bind(&Skip);
return;
}
if (tbnz(rt, Bit, Label) == ARMEmitter::BranchEncodeSucceeded::Success) {
return;
}
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
template<ARMEmitter::IsLabel T>
void adr_OrRestart(ARMEmitter::Register rd, T* Label) {
if (adr(rd, Label) == ARMEmitter::BranchEncodeSucceeded::Success) {
return;
}
// We can support this but currently unnecessary.
ERROR_AND_DIE_FMT("Long ADR currently unsupported!");
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
template<ARMEmitter::IsLabel T>
void adrp_OrRestart(ARMEmitter::Register rd, T* Label) {
if (adrp(rd, Label) == ARMEmitter::BranchEncodeSucceeded::Success) {
return;
}
// We can support this but currently unnecessary.
ERROR_AND_DIE_FMT("Long ADRP currently unsupported!");
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
template<ARMEmitter::IsLabel T>
void BindOrRestart(T* Label) {
if (Bind(Label)) {
return;
}
if (RequiresFarARM64Jumps) {
// This should have been caught before this point.
ERROR_AND_DIE_FMT("Unhandled long bind");
return;
}
FEXCore::LongJump::LongJump(RestartControl.RestartJump, FEXCore::ToUnderlying(RestartOptions::Control::EnableFarARM64Jumps));
}
// This is purely a debugging aid for developers to see if they are in JIT code space when inspecting raw memory // This is purely a debugging aid for developers to see if they are in JIT code space when inspecting raw memory
void EmitDetectionString(); void EmitDetectionString();
IR::RegisterAllocationPass* RAPass {}; IR::RegisterAllocationPass* RAPass {};
@@ -430,17 +607,8 @@ private:
void EmitEntryPoint(ARMEmitter::BackwardLabel& HeaderLabel, bool CheckTF); void EmitEntryPoint(ARMEmitter::BackwardLabel& HeaderLabel, bool CheckTF);
// Runtime selection;
// Load and store TSO memory style
OpType RT_LoadMemTSO;
OpType RT_StoreMemTSO;
#define DEF_OP(x) void Op_##x(IR::IROp_Header const* IROp, IR::Ref Node) #define DEF_OP(x) void Op_##x(IR::IROp_Header const* IROp, IR::Ref Node)
// Dynamic Dispatcher supporting operations
DEF_OP(ParanoidLoadMemTSO);
DEF_OP(ParanoidStoreMemTSO);
///< Unhandled handler ///< Unhandled handler
DEF_OP(Unhandled); DEF_OP(Unhandled);
+47 -218
View File
@@ -912,7 +912,7 @@ DEF_OP(VLoadVectorMasked) {
// If the sign bit is zero then skip the load // If the sign bit is zero then skip the load
ARMEmitter::ForwardLabel Skip {}; ARMEmitter::ForwardLabel Skip {};
tbz(WorkingReg, ElementSizeInBits - 1, &Skip); (void)tbz(WorkingReg, ElementSizeInBits - 1, &Skip);
// Do the gather load for this element into the destination // Do the gather load for this element into the destination
switch (IROp->ElementSize) { switch (IROp->ElementSize) {
case IR::OpSize::i8Bit: ld1<ARMEmitter::SubRegSize::i8Bit>(TempDst.Q(), i, TempMemReg); break; case IR::OpSize::i8Bit: ld1<ARMEmitter::SubRegSize::i8Bit>(TempDst.Q(), i, TempMemReg); break;
@@ -923,7 +923,7 @@ DEF_OP(VLoadVectorMasked) {
default: LOGMAN_MSG_A_FMT("Unhandled {} size: {}", __func__, IROp->ElementSize); return; default: LOGMAN_MSG_A_FMT("Unhandled {} size: {}", __func__, IROp->ElementSize); return;
} }
Bind(&Skip); (void)Bind(&Skip);
if ((i + 1) != NumElements) { if ((i + 1) != NumElements) {
// Handle register rename to save a move. // Handle register rename to save a move.
@@ -1013,7 +1013,7 @@ DEF_OP(VStoreVectorMasked) {
// If the sign bit is zero then skip the load // If the sign bit is zero then skip the load
ARMEmitter::ForwardLabel Skip {}; ARMEmitter::ForwardLabel Skip {};
tbz(WorkingReg, ElementSizeInBits - 1, &Skip); (void)tbz(WorkingReg, ElementSizeInBits - 1, &Skip);
// Do the gather load for this element into the destination // Do the gather load for this element into the destination
switch (IROp->ElementSize) { switch (IROp->ElementSize) {
case IR::OpSize::i8Bit: st1<ARMEmitter::SubRegSize::i8Bit>(RegData.Q(), i, TempMemReg); break; case IR::OpSize::i8Bit: st1<ARMEmitter::SubRegSize::i8Bit>(RegData.Q(), i, TempMemReg); break;
@@ -1024,7 +1024,7 @@ DEF_OP(VStoreVectorMasked) {
default: LOGMAN_MSG_A_FMT("Unhandled {} size: {}", __func__, IROp->ElementSize); return; default: LOGMAN_MSG_A_FMT("Unhandled {} size: {}", __func__, IROp->ElementSize); return;
} }
Bind(&Skip); (void)Bind(&Skip);
if ((i + 1) != NumElements) { if ((i + 1) != NumElements) {
// Handle register rename to save a move. // Handle register rename to save a move.
@@ -1102,7 +1102,7 @@ void Arm64JITCore::Emulate128BitGather(IR::OpSize Size, IR::OpSize ElementSize,
PerformMove(ElementSize, WorkingReg, MaskReg, i); PerformMove(ElementSize, WorkingReg, MaskReg, i);
// Skip if the mask's sign bit isn't set // Skip if the mask's sign bit isn't set
tbz(WorkingReg, ElementSizeInBits - 1, &Skip); (void)tbz(WorkingReg, ElementSizeInBits - 1, &Skip);
// Extract Index Element // Extract Index Element
if ((IndexElement * IR::OpSizeToSize(VectorIndexSize)) >= 16) { if ((IndexElement * IR::OpSizeToSize(VectorIndexSize)) >= 16) {
@@ -1140,7 +1140,7 @@ void Arm64JITCore::Emulate128BitGather(IR::OpSize Size, IR::OpSize ElementSize,
default: LOGMAN_MSG_A_FMT("Unhandled {} size: {}", __func__, ElementSize); FEX_UNREACHABLE; default: LOGMAN_MSG_A_FMT("Unhandled {} size: {}", __func__, ElementSize); FEX_UNREACHABLE;
} }
Bind(&Skip); (void)Bind(&Skip);
} }
if (NeedsDestTmp) { if (NeedsDestTmp) {
@@ -1874,7 +1874,7 @@ DEF_OP(MemSet) {
if (!DirectionIsInline) { if (!DirectionIsInline) {
// Backward or forwards implementation depends on flag // Backward or forwards implementation depends on flag
tbnz(DirectionReg, 1, &BackwardImpl); (void)tbnz(DirectionReg, 1, &BackwardImpl);
} }
auto MemStore = [this](auto Value, uint32_t OpSize, int32_t Size) { auto MemStore = [this](auto Value, uint32_t OpSize, int32_t Size) {
@@ -1922,7 +1922,7 @@ DEF_OP(MemSet) {
ARMEmitter::ForwardLabel DoneInternal {}; ARMEmitter::ForwardLabel DoneInternal {};
// Early exit if zero count. // Early exit if zero count.
cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal); (void)cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal);
if (!IsAtomic) { if (!IsAtomic) {
ARMEmitter::ForwardLabel AgainInternal256Exit {}; ARMEmitter::ForwardLabel AgainInternal256Exit {};
@@ -1939,50 +1939,50 @@ DEF_OP(MemSet) {
// Do this in two parts, to fallback to the byte by byte loop if size < 32, and to the // Do this in two parts, to fallback to the byte by byte loop if size < 32, and to the
// single copy loop if size < 64. // single copy loop if size < 64.
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
tbnz(TMP1, 63, &AgainInternal128Exit); (void)tbnz(TMP1, 63, &AgainInternal128Exit);
// Fill VTMP2 with the set pattern // Fill VTMP2 with the set pattern
dup(SubRegSize, VTMP2.Q(), Value); dup(SubRegSize, VTMP2.Q(), Value);
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
tbnz(TMP1, 63, &AgainInternal256Exit); (void)tbnz(TMP1, 63, &AgainInternal256Exit);
Bind(&AgainInternal256); (void)Bind(&AgainInternal256);
stp<ARMEmitter::IndexType::POST>(VTMP2.Q(), VTMP2.Q(), TMP2, 32 * Direction); stp<ARMEmitter::IndexType::POST>(VTMP2.Q(), VTMP2.Q(), TMP2, 32 * Direction);
stp<ARMEmitter::IndexType::POST>(VTMP2.Q(), VTMP2.Q(), TMP2, 32 * Direction); stp<ARMEmitter::IndexType::POST>(VTMP2.Q(), VTMP2.Q(), TMP2, 32 * Direction);
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 64 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 64 / Size);
tbz(TMP1, 63, &AgainInternal256); (void)tbz(TMP1, 63, &AgainInternal256);
Bind(&AgainInternal256Exit); (void)Bind(&AgainInternal256Exit);
add(ARMEmitter::Size::i64Bit, TMP1, TMP1, 64 / Size); add(ARMEmitter::Size::i64Bit, TMP1, TMP1, 64 / Size);
cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal); (void)cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal);
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
tbnz(TMP1, 63, &AgainInternal128Exit); (void)tbnz(TMP1, 63, &AgainInternal128Exit);
Bind(&AgainInternal128); (void)Bind(&AgainInternal128);
stp<ARMEmitter::IndexType::POST>(VTMP2.Q(), VTMP2.Q(), TMP2, 32 * Direction); stp<ARMEmitter::IndexType::POST>(VTMP2.Q(), VTMP2.Q(), TMP2, 32 * Direction);
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
tbz(TMP1, 63, &AgainInternal128); (void)tbz(TMP1, 63, &AgainInternal128);
Bind(&AgainInternal128Exit); (void)Bind(&AgainInternal128Exit);
add(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); add(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal); (void)cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal);
if (Direction == -1) { if (Direction == -1) {
add(ARMEmitter::Size::i64Bit, TMP2, TMP2, 32 - Size); add(ARMEmitter::Size::i64Bit, TMP2, TMP2, 32 - Size);
} }
} }
Bind(&AgainInternal); (void)Bind(&AgainInternal);
if (IsAtomic) { if (IsAtomic) {
MemStoreTSO(Value, OpSize, SizeDirection); MemStoreTSO(Value, OpSize, SizeDirection);
} else { } else {
MemStore(Value, OpSize, SizeDirection); MemStore(Value, OpSize, SizeDirection);
} }
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 1); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 1);
cbnz(ARMEmitter::Size::i64Bit, TMP1, &AgainInternal); (void)cbnz(ARMEmitter::Size::i64Bit, TMP1, &AgainInternal);
Bind(&DoneInternal); (void)Bind(&DoneInternal);
if (SizeDirection >= 0) { if (SizeDirection >= 0) {
switch (OpSize) { switch (OpSize) {
@@ -2012,12 +2012,12 @@ DEF_OP(MemSet) {
EmitMemset(Direction); EmitMemset(Direction);
if (Direction == 1) { if (Direction == 1) {
b(&Done); (void)b(&Done);
Bind(&BackwardImpl); (void)Bind(&BackwardImpl);
} }
} }
Bind(&Done); (void)Bind(&Done);
// Destination already set to the final pointer. // Destination already set to the final pointer.
} }
} }
@@ -2067,7 +2067,7 @@ DEF_OP(MemCpy) {
if (!DirectionIsInline) { if (!DirectionIsInline) {
// Backward or forwards implementation depends on flag // Backward or forwards implementation depends on flag
tbnz(DirectionReg, 1, &BackwardImpl); (void)tbnz(DirectionReg, 1, &BackwardImpl);
} }
auto MemCpy = [this](uint32_t OpSize, int32_t Size) { auto MemCpy = [this](uint32_t OpSize, int32_t Size) {
@@ -2164,7 +2164,7 @@ DEF_OP(MemCpy) {
ARMEmitter::ForwardLabel DoneInternal {}; ARMEmitter::ForwardLabel DoneInternal {};
// Early exit if zero count. // Early exit if zero count.
cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal); (void)cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal);
if (!IsAtomic) { if (!IsAtomic) {
ARMEmitter::ForwardLabel AbsPos {}; ARMEmitter::ForwardLabel AbsPos {};
@@ -2174,11 +2174,11 @@ DEF_OP(MemCpy) {
ARMEmitter::BackwardLabel AgainInternal256 {}; ARMEmitter::BackwardLabel AgainInternal256 {};
sub(ARMEmitter::Size::i64Bit, TMP4, TMP2, TMP3); sub(ARMEmitter::Size::i64Bit, TMP4, TMP2, TMP3);
tbz(TMP4, 63, &AbsPos); (void)tbz(TMP4, 63, &AbsPos);
neg(ARMEmitter::Size::i64Bit, TMP4, TMP4); neg(ARMEmitter::Size::i64Bit, TMP4, TMP4);
Bind(&AbsPos); (void)Bind(&AbsPos);
sub(ARMEmitter::Size::i64Bit, TMP4, TMP4, 32); sub(ARMEmitter::Size::i64Bit, TMP4, TMP4, 32);
tbnz(TMP4, 63, &AgainInternal); (void)tbnz(TMP4, 63, &AgainInternal);
if (Direction == -1) { if (Direction == -1) {
sub(ARMEmitter::Size::i64Bit, TMP2, TMP2, 32 - Size); sub(ARMEmitter::Size::i64Bit, TMP2, TMP2, 32 - Size);
@@ -2190,30 +2190,30 @@ DEF_OP(MemCpy) {
// Do this in two parts, to fallback to the byte by byte loop if size < 32, and to the // Do this in two parts, to fallback to the byte by byte loop if size < 32, and to the
// single copy loop if size < 64. // single copy loop if size < 64.
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
tbnz(TMP1, 63, &AgainInternal128Exit); (void)tbnz(TMP1, 63, &AgainInternal128Exit);
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
tbnz(TMP1, 63, &AgainInternal256Exit); (void)tbnz(TMP1, 63, &AgainInternal256Exit);
Bind(&AgainInternal256); (void)Bind(&AgainInternal256);
MemCpy(32, 32 * Direction); MemCpy(32, 32 * Direction);
MemCpy(32, 32 * Direction); MemCpy(32, 32 * Direction);
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 64 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 64 / Size);
tbz(TMP1, 63, &AgainInternal256); (void)tbz(TMP1, 63, &AgainInternal256);
Bind(&AgainInternal256Exit); (void)Bind(&AgainInternal256Exit);
add(ARMEmitter::Size::i64Bit, TMP1, TMP1, 64 / Size); add(ARMEmitter::Size::i64Bit, TMP1, TMP1, 64 / Size);
cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal); (void)cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal);
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
tbnz(TMP1, 63, &AgainInternal128Exit); (void)tbnz(TMP1, 63, &AgainInternal128Exit);
Bind(&AgainInternal128); (void)Bind(&AgainInternal128);
MemCpy(32, 32 * Direction); MemCpy(32, 32 * Direction);
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
tbz(TMP1, 63, &AgainInternal128); (void)tbz(TMP1, 63, &AgainInternal128);
Bind(&AgainInternal128Exit); (void)Bind(&AgainInternal128Exit);
add(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size); add(ARMEmitter::Size::i64Bit, TMP1, TMP1, 32 / Size);
cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal); (void)cbz(ARMEmitter::Size::i64Bit, TMP1, &DoneInternal);
if (Direction == -1) { if (Direction == -1) {
add(ARMEmitter::Size::i64Bit, TMP2, TMP2, 32 - Size); add(ARMEmitter::Size::i64Bit, TMP2, TMP2, 32 - Size);
@@ -2221,16 +2221,16 @@ DEF_OP(MemCpy) {
} }
} }
Bind(&AgainInternal); (void)Bind(&AgainInternal);
if (IsAtomic) { if (IsAtomic) {
MemCpyTSO(OpSize, SizeDirection); MemCpyTSO(OpSize, SizeDirection);
} else { } else {
MemCpy(OpSize, SizeDirection); MemCpy(OpSize, SizeDirection);
} }
sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 1); sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, 1);
cbnz(ARMEmitter::Size::i64Bit, TMP1, &AgainInternal); (void)cbnz(ARMEmitter::Size::i64Bit, TMP1, &AgainInternal);
Bind(&DoneInternal); (void)Bind(&DoneInternal);
// Needs to use temporaries just in case of overwrite // Needs to use temporaries just in case of overwrite
mov(TMP1, MemRegDest.X()); mov(TMP1, MemRegDest.X());
@@ -2288,186 +2288,15 @@ DEF_OP(MemCpy) {
for (int32_t Direction : {1, -1}) { for (int32_t Direction : {1, -1}) {
EmitMemcpy(Direction); EmitMemcpy(Direction);
if (Direction == 1) { if (Direction == 1) {
b(&Done); (void)b(&Done);
Bind(&BackwardImpl); (void)Bind(&BackwardImpl);
} }
} }
Bind(&Done); (void)Bind(&Done);
// Destination already set to the final pointer. // Destination already set to the final pointer.
} }
} }
DEF_OP(ParanoidLoadMemTSO) {
const auto Op = IROp->C<IR::IROp_LoadMemTSO>();
const auto OpSize = IROp->Size;
auto MemReg = GetReg(Op->Addr);
if (CTX->HostFeatures.SupportsTSOImm9 && Op->Class == IR::RegClass::GPR) {
const auto Dst = GetReg(Node);
uint64_t Offset = 0;
if (!Op->Offset.IsInvalid()) {
if (!IsInlineConstant(Op->Offset, &Offset)) {
MemReg = ApplyMemOperand(OpSize, MemReg, TMP4, Op->Offset, Op->OffsetType, Op->OffsetScale);
}
}
if (OpSize == IR::OpSize::i8Bit) {
// 8bit load is always aligned to natural alignment
const auto Dst = GetReg(Node);
ldapurb(Dst, MemReg, Offset);
} else {
switch (OpSize) {
case IR::OpSize::i16Bit: ldapurh(Dst, MemReg, Offset); break;
case IR::OpSize::i32Bit: ldapur(Dst.W(), MemReg, Offset); break;
case IR::OpSize::i64Bit: ldapur(Dst.X(), MemReg, Offset); break;
default: LOGMAN_MSG_A_FMT("Unhandled ParanoidLoadMemTSO size: {}", OpSize); break;
}
}
} else if (CTX->HostFeatures.SupportsRCPC && Op->Class == IR::RegClass::GPR) {
const auto Dst = GetReg(Node);
MemReg = ApplyMemOperand(OpSize, MemReg, TMP4, Op->Offset, Op->OffsetType, Op->OffsetScale);
if (OpSize == IR::OpSize::i8Bit) {
// 8bit load is always aligned to natural alignment
ldaprb(Dst.W(), MemReg);
} else {
switch (OpSize) {
case IR::OpSize::i16Bit: ldaprh(Dst.W(), MemReg); break;
case IR::OpSize::i32Bit: ldapr(Dst.W(), MemReg); break;
case IR::OpSize::i64Bit: ldapr(Dst.X(), MemReg); break;
default: LOGMAN_MSG_A_FMT("Unhandled ParanoidLoadMemTSO size: {}", OpSize); break;
}
}
} else if (Op->Class == IR::RegClass::GPR) {
const auto Dst = GetReg(Node);
MemReg = ApplyMemOperand(OpSize, MemReg, TMP4, Op->Offset, Op->OffsetType, Op->OffsetScale);
switch (OpSize) {
case IR::OpSize::i8Bit: ldarb(Dst, MemReg); break;
case IR::OpSize::i16Bit: ldarh(Dst, MemReg); break;
case IR::OpSize::i32Bit: ldar(Dst.W(), MemReg); break;
case IR::OpSize::i64Bit: ldar(Dst.X(), MemReg); break;
default: LOGMAN_MSG_A_FMT("Unhandled ParanoidLoadMemTSO size: {}", OpSize); break;
}
} else {
const auto Dst = GetVReg(Node);
MemReg = ApplyMemOperand(OpSize, MemReg, TMP4, Op->Offset, Op->OffsetType, Op->OffsetScale);
switch (OpSize) {
case IR::OpSize::i8Bit:
ldarb(TMP1, MemReg);
fmov(ARMEmitter::Size::i32Bit, Dst.S(), TMP1.W());
break;
case IR::OpSize::i16Bit:
ldarh(TMP1, MemReg);
fmov(ARMEmitter::Size::i32Bit, Dst.S(), TMP1.W());
break;
case IR::OpSize::i32Bit:
ldar(TMP1.W(), MemReg);
fmov(ARMEmitter::Size::i32Bit, Dst.S(), TMP1.W());
break;
case IR::OpSize::i64Bit:
ldar(TMP1, MemReg);
fmov(ARMEmitter::Size::i64Bit, Dst.D(), TMP1);
break;
case IR::OpSize::i128Bit:
ldaxp(ARMEmitter::Size::i64Bit, TMP1, TMP2, MemReg);
clrex();
ins(ARMEmitter::SubRegSize::i64Bit, Dst, 0, TMP1);
ins(ARMEmitter::SubRegSize::i64Bit, Dst, 1, TMP2);
break;
case IR::OpSize::i256Bit:
LOGMAN_THROW_A_FMT(HostSupportsSVE256, "Need SVE256 support in order to use {} with 256-bit operation", __func__);
dmb(ARMEmitter::BarrierScope::ISH);
ld1b<ARMEmitter::SubRegSize::i8Bit>(Dst.Z(), PRED_TMP_32B.Zeroing(), MemReg);
dmb(ARMEmitter::BarrierScope::ISH);
break;
default: LOGMAN_MSG_A_FMT("Unhandled ParanoidLoadMemTSO size: {}", OpSize); break;
}
}
}
DEF_OP(ParanoidStoreMemTSO) {
const auto Op = IROp->C<IR::IROp_StoreMemTSO>();
const auto OpSize = IROp->Size;
auto MemReg = GetReg(Op->Addr);
if (CTX->HostFeatures.SupportsTSOImm9 && Op->Class == IR::RegClass::GPR) {
const auto Src = GetZeroableReg(Op->Value);
uint64_t Offset = 0;
if (!Op->Offset.IsInvalid()) {
if (!IsInlineConstant(Op->Offset, &Offset)) {
MemReg = ApplyMemOperand(OpSize, MemReg, TMP1, Op->Offset, Op->OffsetType, Op->OffsetScale);
}
}
if (OpSize == IR::OpSize::i8Bit) {
// 8bit load is always aligned to natural alignment
stlurb(Src, MemReg, Offset);
} else {
switch (OpSize) {
case IR::OpSize::i16Bit: stlurh(Src, MemReg, Offset); break;
case IR::OpSize::i32Bit: stlur(Src.W(), MemReg, Offset); break;
case IR::OpSize::i64Bit: stlur(Src.X(), MemReg, Offset); break;
default: LOGMAN_MSG_A_FMT("Unhandled ParanoidStoreMemTSO size: {}", OpSize); break;
}
}
} else if (Op->Class == IR::RegClass::GPR) {
const auto Src = GetZeroableReg(Op->Value);
MemReg = ApplyMemOperand(OpSize, MemReg, TMP1, Op->Offset, Op->OffsetType, Op->OffsetScale);
switch (OpSize) {
case IR::OpSize::i8Bit: stlrb(Src, MemReg); break;
case IR::OpSize::i16Bit: stlrh(Src, MemReg); break;
case IR::OpSize::i32Bit: stlr(Src.W(), MemReg); break;
case IR::OpSize::i64Bit: stlr(Src.X(), MemReg); break;
default: LOGMAN_MSG_A_FMT("Unhandled ParanoidStoreMemTSO size: {}", OpSize); break;
}
} else {
const auto Src = GetVReg(Op->Value);
MemReg = ApplyMemOperand(OpSize, MemReg, TMP4, Op->Offset, Op->OffsetType, Op->OffsetScale);
switch (OpSize) {
case IR::OpSize::i8Bit:
umov<ARMEmitter::SubRegSize::i8Bit>(TMP1, Src, 0);
stlrb(TMP1, MemReg);
break;
case IR::OpSize::i16Bit:
umov<ARMEmitter::SubRegSize::i16Bit>(TMP1, Src, 0);
stlrh(TMP1, MemReg);
break;
case IR::OpSize::i32Bit:
umov<ARMEmitter::SubRegSize::i32Bit>(TMP1, Src, 0);
stlr(TMP1.W(), MemReg);
break;
case IR::OpSize::i64Bit:
umov<ARMEmitter::SubRegSize::i64Bit>(TMP1, Src, 0);
stlr(TMP1, MemReg);
break;
case IR::OpSize::i128Bit: {
// Move vector to GPRs
umov<ARMEmitter::SubRegSize::i64Bit>(TMP1, Src, 0);
umov<ARMEmitter::SubRegSize::i64Bit>(TMP2, Src, 1);
ARMEmitter::BackwardLabel B;
Bind(&B);
// ldaxp must not have both the destination registers be the same
ldaxp(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::zr, TMP3, MemReg); // <- Can hit SIGBUS. Overwritten with DMB
stlxp(ARMEmitter::Size::i64Bit, TMP3, TMP1, TMP2, MemReg); // <- Can also hit SIGBUS
cbnz(ARMEmitter::Size::i64Bit, TMP3, &B); // < Overwritten with DMB
break;
}
case IR::OpSize::i256Bit: {
LOGMAN_THROW_A_FMT(HostSupportsSVE256, "Need SVE256 support in order to use {} with 256-bit operation", __func__);
dmb(ARMEmitter::BarrierScope::ISH);
st1b<ARMEmitter::SubRegSize::i8Bit>(Src.Z(), PRED_TMP_32B, MemReg, 0);
dmb(ARMEmitter::BarrierScope::ISH);
break;
}
default: LOGMAN_MSG_A_FMT("Unhandled ParanoidStoreMemTSO size: {}", OpSize); break;
}
}
}
DEF_OP(CacheLineClear) { DEF_OP(CacheLineClear) {
if (!CTX->HostFeatures.SupportsCacheMaintenanceOps) { if (!CTX->HostFeatures.SupportsCacheMaintenanceOps) {
dmb(ARMEmitter::BarrierScope::SY); dmb(ARMEmitter::BarrierScope::SY);
+23 -15
View File
@@ -15,7 +15,7 @@ $end_info$
namespace FEXCore { namespace FEXCore {
GuestToHostMap::GuestToHostMap() GuestToHostMap::GuestToHostMap()
: BlockLinks_mbr {fextl::pmr::get_default_resource()} { : BlockLinks_mbr {"FEXMem_BlockLinks"} {
BlockLinks_pma = fextl::make_unique<std::pmr::polymorphic_allocator<std::byte>>(&BlockLinks_mbr); BlockLinks_pma = fextl::make_unique<std::pmr::polymorphic_allocator<std::byte>>(&BlockLinks_mbr);
// Setup our PMR map. // Setup our PMR map.
BlockLinks = BlockLinks_pma->new_object<BlockLinksMapType>(); BlockLinks = BlockLinks_pma->new_object<BlockLinksMapType>();
@@ -24,7 +24,7 @@ GuestToHostMap::GuestToHostMap()
LookupCache::LookupCache(FEXCore::Context::ContextImpl* CTX) LookupCache::LookupCache(FEXCore::Context::ContextImpl* CTX)
: ctx {CTX} { : ctx {CTX} {
TotalCacheSize = ctx->Config.VirtualMemSize / 4096 * 8 + CODE_SIZE + L1_SIZE; TotalCacheSize = ctx->Config.VirtualMemSize / FEXCore::Utils::FEX_PAGE_SIZE * 8 + CODE_SIZE + MAX_L1_SIZE;
// Block cache ends up looking like this // Block cache ends up looking like this
// PageMemoryMap[VirtualMemoryRegion >> 12] // PageMemoryMap[VirtualMemoryRegion >> 12]
@@ -39,6 +39,8 @@ LookupCache::LookupCache(FEXCore::Context::ContextImpl* CTX)
// We need one pointer per page of virtual memory // We need one pointer per page of virtual memory
// At 64GB of virtual memory this will allocate 128MB of virtual memory space // At 64GB of virtual memory this will allocate 128MB of virtual memory space
PagePointer = reinterpret_cast<uintptr_t>(FEXCore::Allocator::VirtualAlloc(TotalCacheSize, false, false)); PagePointer = reinterpret_cast<uintptr_t>(FEXCore::Allocator::VirtualAlloc(TotalCacheSize, false, false));
FEXCore::Allocator::VirtualName("FEXMem_Lookup", reinterpret_cast<void*>(PagePointer),
ctx->Config.VirtualMemSize / FEXCore::Utils::FEX_PAGE_SIZE * 8 + CODE_SIZE);
CTX->SyscallHandler->MarkOvercommitRange(PagePointer, TotalCacheSize); CTX->SyscallHandler->MarkOvercommitRange(PagePointer, TotalCacheSize);
// Allocate our memory backing our pages // Allocate our memory backing our pages
@@ -46,14 +48,24 @@ LookupCache::LookupCache(FEXCore::Context::ContextImpl* CTX)
// XXX: We can drop down to 16KB if we store 4byte offsets from the code base // XXX: We can drop down to 16KB if we store 4byte offsets from the code base
// We currently limit to 128MB of real memory for caching for the total cache size. // We currently limit to 128MB of real memory for caching for the total cache size.
// Can end up being inefficient if we compile a small number of blocks per page // Can end up being inefficient if we compile a small number of blocks per page
PageMemory = PagePointer + ctx->Config.VirtualMemSize / 4096 * 8; PageMemory = PagePointer + ctx->Config.VirtualMemSize / FEXCore::Utils::FEX_PAGE_SIZE * 8;
LOGMAN_THROW_A_FMT(PageMemory != -1ULL, "Failed to allocate page memory"); LOGMAN_THROW_A_FMT(PageMemory != -1ULL, "Failed to allocate page memory");
// L1 Cache // L1 Cache
L1Pointer = PageMemory + CODE_SIZE; L1Pointer = PageMemory + CODE_SIZE;
FEXCore::Allocator::VirtualName("FEXMem_Lookup_L1", reinterpret_cast<void*>(L1Pointer), MAX_L1_SIZE);
LOGMAN_THROW_A_FMT(L1Pointer != -1ULL, "Failed to allocate L1Pointer"); LOGMAN_THROW_A_FMT(L1Pointer != -1ULL, "Failed to allocate L1Pointer");
VirtualMemSize = ctx->Config.VirtualMemSize; VirtualMemSize = ctx->Config.VirtualMemSize;
if (DynamicL1Cache()) {
// Start at minimum size when dynamic.
L1PointerMask = MIN_L1_ENTRIES - 1;
} else {
// Start at maximum instead.
L1PointerMask = MAX_L1_ENTRIES - 1;
}
} }
LookupCache::~LookupCache() { LookupCache::~LookupCache() {
@@ -64,31 +76,27 @@ LookupCache::~LookupCache() {
// These will get freed when their memory allocators are deallocated. // These will get freed when their memory allocators are deallocated.
} }
void LookupCache::ClearL2Cache() { void LookupCache::ClearL2Cache(const FEXCore::LookupCacheWriteLockToken& lk) {
auto lk = Shared->AcquireLock();
// Clear out the page memory // Clear out the page memory
// PagePointer and PageMemory are sequential with each other. Clear both at once. // PagePointer and PageMemory are sequential with each other. Clear both at once.
FEXCore::Allocator::VirtualDontNeed(reinterpret_cast<void*>(PagePointer), ctx->Config.VirtualMemSize / 4096 * 8 + CODE_SIZE, false); FEXCore::Allocator::VirtualDontNeed(reinterpret_cast<void*>(PagePointer),
ctx->Config.VirtualMemSize / FEXCore::Utils::FEX_PAGE_SIZE * 8 + CODE_SIZE, false);
AllocateOffset = 0; AllocateOffset = 0;
} }
void LookupCache::ClearThreadLocalCaches() { void LookupCache::ClearThreadLocalCaches(const LookupCacheWriteLockToken&) {
auto lk = Shared->AcquireLock();
// Clear L1 and L2 by clearing the full cache. // Clear L1 and L2 by clearing the full cache.
FEXCore::Allocator::VirtualDontNeed(reinterpret_cast<void*>(PagePointer), TotalCacheSize, false); FEXCore::Allocator::VirtualDontNeed(reinterpret_cast<void*>(PagePointer), TotalCacheSize, false);
CachedCodePages.clear();
} }
void LookupCache::ClearCache() { void LookupCache::ClearCache(const LookupCacheWriteLockToken& lk) {
auto lk = Shared->AcquireLock();
// Clear L1 and L2 by clearing the full cache. // Clear L1 and L2 by clearing the full cache.
FEXCore::Allocator::VirtualDontNeed(reinterpret_cast<void*>(PagePointer), TotalCacheSize, false); ClearThreadLocalCaches(lk);
Shared->ClearCache(lk); Shared->ClearCache(lk);
} }
void GuestToHostMap::ClearCache(const LockToken&) { void GuestToHostMap::ClearCache(const LookupCacheWriteLockToken&) {
// Allocate a new pointer from the BlockLinks pma again. // Allocate a new pointer from the BlockLinks pma again.
BlockLinks = BlockLinks_pma->new_object<BlockLinksMapType>(); BlockLinks = BlockLinks_pma->new_object<BlockLinksMapType>();
// All code is gone, clear the block list // All code is gone, clear the block list
+245 -121
View File
@@ -2,30 +2,36 @@
#pragma once #pragma once
#include "Interface/Context/Context.h" #include "Interface/Context/Context.h"
#include <FEXCore/Utils/LogManager.h> #include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/SHMStats.h>
#include <FEXCore/fextl/map.h> #include <FEXCore/fextl/map.h>
#include <FEXCore/fextl/memory_resource.h> #include <FEXCore/fextl/memory_resource.h>
#include <FEXCore/fextl/robin_map.h> #include <FEXCore/fextl/robin_map.h>
#include <FEXCore/fextl/vector.h> #include <FEXCore/fextl/vector.h>
#include <FEXCore/fextl/unordered_set.h>
#include <FEXCore/fextl/memory_resource.h> #include <FEXCore/fextl/memory_resource.h>
#include <cstdint> #include <cstdint>
#include <functional>
#include <stddef.h> #include <stddef.h>
#include <utility> #include <utility>
#include <mutex> #include <mutex>
namespace FEXCore { namespace FEXCore {
struct GuestToHostMap { struct LookupCacheWriteLockToken {
std::recursive_mutex WriteLock; private:
// Only constructible by GuestToHostMap
friend struct GuestToHostMap;
LookupCacheWriteLockToken(std::mutex& Mutex)
: Lock {Mutex} {}
std::lock_guard<std::mutex> Lock;
};
struct LockToken { struct GuestToHostMap {
std::lock_guard<std::recursive_mutex> Lock; std::mutex WriteLock;
};
[[nodiscard]] [[nodiscard]]
LockToken AcquireLock() { LookupCacheWriteLockToken AcquireWriteLock() {
return LockToken {std::lock_guard {WriteLock}}; return LookupCacheWriteLockToken {WriteLock};
} }
struct BlockLinkTag { struct BlockLinkTag {
@@ -49,53 +55,72 @@ struct GuestToHostMap {
// walking each block member and destructing objects. // walking each block member and destructing objects.
// //
// This makes `BlockLinks` look like a raw pointer that could memory leak, but since it is backed by the MBR, it won't. // This makes `BlockLinks` look like a raw pointer that could memory leak, but since it is backed by the MBR, it won't.
std::pmr::monotonic_buffer_resource BlockLinks_mbr; fextl::pmr::named_monotonic_page_buffer_resource BlockLinks_mbr;
using BlockLinksMapType = std::pmr::map<BlockLinkTag, FEXCore::Context::BlockDelinkerFunc>; using BlockLinksMapType = std::pmr::map<BlockLinkTag, FEXCore::Context::BlockDelinkerFunc>;
fextl::unique_ptr<std::pmr::polymorphic_allocator<std::byte>> BlockLinks_pma; fextl::unique_ptr<std::pmr::polymorphic_allocator<std::byte>> BlockLinks_pma;
BlockLinksMapType* BlockLinks; BlockLinksMapType* BlockLinks;
fextl::robin_map<uint64_t, uint64_t> BlockList; struct BlockEntry {
uint64_t HostCode;
fextl::vector<uint64_t> CodePages;
};
fextl::robin_map<uint64_t, BlockEntry> BlockList;
fextl::map<uint64_t, fextl::vector<uint64_t>> CodePages; fextl::map<uint64_t, fextl::vector<uint64_t>> CodePages;
GuestToHostMap(); GuestToHostMap();
// Adds to Guest -> Host code mapping // Adds to Guest -> Host code mapping
void AddBlockMapping(uint64_t Address, void* HostCode, const LockToken&) { const BlockEntry& AddBlockMapping(uint64_t Address, const fextl::vector<uint64_t>& CodePages, void* HostCode, const LookupCacheWriteLockToken&) {
// This may replace an existing mapping // This may replace an existing mapping
// NOTE: Generally no previous entry should exist, however there is one exception: // NOTE: Generally no previous entry should exist, however there is one exception:
// If the backend updates the active thread's CodeBuffer, the new associated LookupCache // If the backend updates the active thread's CodeBuffer, the new associated LookupCache
// may already contain the block address. Since is comparatively rare, we'll just leak // may already contain the block address. Since is comparatively rare, we'll just leak
// one of the two blocks in this case. // one of the two blocks in this case.
BlockList[Address] = (uintptr_t)HostCode; return BlockList.insert_or_assign(Address, BlockEntry {(uintptr_t)HostCode, CodePages}).first->second;
} }
std::optional<uintptr_t> FindBlock(uint64_t Address, const LockToken&) { const BlockEntry* FindBlock(uint64_t Address, const LookupCacheWriteLockToken&) {
auto HostCode = BlockList.find(Address); auto HostCode = BlockList.find(Address);
if (HostCode == BlockList.end()) { if (HostCode == BlockList.end()) {
return std::nullopt; return nullptr;
} }
return HostCode->second; return &HostCode->second;
} }
bool Erase(FEXCore::Core::CpuStateFrame* Frame, uint64_t Address, const LockToken&) { bool Erase(uint64_t Address, const LookupCacheWriteLockToken&) {
// Sever any links to this block // Sever any links to this block
auto lower = BlockLinks->lower_bound({Address, nullptr}); auto lower = BlockLinks->lower_bound({Address, nullptr});
auto upper = BlockLinks->upper_bound({Address, reinterpret_cast<FEXCore::Context::ExitFunctionLinkData*>(UINTPTR_MAX)}); auto upper = BlockLinks->upper_bound({Address, reinterpret_cast<FEXCore::Context::ExitFunctionLinkData*>(UINTPTR_MAX)});
for (auto it = lower; it != upper; it = BlockLinks->erase(it)) { for (auto it = lower; it != upper; it = BlockLinks->erase(it)) {
it->second(Frame, it->first.HostLink); it->second(it->first.HostLink);
} }
// Remove from BlockList // Remove from BlockList
return BlockList.erase(Address) != 0; return BlockList.erase(Address) != 0;
} }
void InvalidateRange(uint64_t Start, uint64_t Length) {
auto lk = AcquireWriteLock();
auto lower = CodePages.lower_bound(Start >> 12);
auto upper = CodePages.upper_bound((Start + Length - 1) >> 12);
for (auto it = lower; it != upper; it++) {
for (const auto& Entry : it->second) {
Erase(Entry, lk);
}
}
CodePages.erase(lower, upper);
}
void AddBlockLink(uint64_t GuestDestination, FEXCore::Context::ExitFunctionLinkData* HostLink, void AddBlockLink(uint64_t GuestDestination, FEXCore::Context::ExitFunctionLinkData* HostLink,
const FEXCore::Context::BlockDelinkerFunc& delinker, const LockToken&) { const FEXCore::Context::BlockDelinkerFunc& delinker, const LookupCacheWriteLockToken&) {
BlockLinks->insert({{GuestDestination, HostLink}, delinker}); BlockLinks->insert({{GuestDestination, HostLink}, delinker});
} }
bool AddBlockExecutableRange(const fextl::set<uint64_t>& Addresses, uint64_t Start, uint64_t Length, const LockToken&) { bool AddBlockExecutableRange(const fextl::set<uint64_t>& Addresses, uint64_t Start, uint64_t Length, const LookupCacheWriteLockToken&) {
bool rv = false; bool rv = false;
for (auto CurrentPage = Start >> 12, EndPage = (Start + Length - 1) >> 12; CurrentPage <= EndPage; CurrentPage++) { for (auto CurrentPage = Start >> 12, EndPage = (Start + Length - 1) >> 12; CurrentPage <= EndPage; CurrentPage++) {
@@ -107,7 +132,7 @@ struct GuestToHostMap {
return rv; return rv;
} }
void ClearCache(const LockToken&); void ClearCache(const LookupCacheWriteLockToken&);
}; };
class LookupCache { class LookupCache {
@@ -122,122 +147,198 @@ public:
// Swaps out the underlying GuestToHostMap and clears all associated caches. // Swaps out the underlying GuestToHostMap and clears all associated caches.
// This interface requires the previous CodeBuffer to be provided despite not using it. This ensures the shared write lock is still valid. // This interface requires the previous CodeBuffer to be provided despite not using it. This ensures the shared write lock is still valid.
void ChangeGuestToHostMapping([[maybe_unused]] CPU::CodeBuffer& Prev, GuestToHostMap& NewMap) { void ChangeGuestToHostMapping([[maybe_unused]] CPU::CodeBuffer& Prev, GuestToHostMap& NewMap, const LookupCacheWriteLockToken& lk) {
ClearThreadLocalCaches(); ClearThreadLocalCaches(lk);
Shared = &NewMap; Shared = &NewMap;
} }
uintptr_t FindBlock(uint64_t Address) { uintptr_t FindBlock(FEXCore::Core::InternalThreadState* Thread, uint64_t Address) {
// Try L1, no lock needed // Try L1, no lock needed
auto& L1Entry = reinterpret_cast<LookupCacheEntry*>(L1Pointer)[Address & L1_ENTRIES_MASK]; auto& L1Entry = reinterpret_cast<LookupCacheEntry*>(L1Pointer)[Address & L1PointerMask];
if (L1Entry.GuestCode == Address) { if (L1Entry.GuestCode == Address) {
return L1Entry.HostCode; return L1Entry.HostCode;
} }
// L2 and L3 need to be locked // L2 and L3 need to be locked
auto lk = Shared->AcquireLock(); uintptr_t HostPtr {};
{
std::optional<FEXCore::SHMStats::AccumulationBlock<uint64_t>> LockTime(
Thread->ThreadStats ? &Thread->ThreadStats->AccumulatedCacheReadLockTime : nullptr);
auto lk = Shared->AcquireWriteLock();
LockTime.reset();
// Try L2 if (!DisableL2Cache()) {
const auto PageIndex = (Address & (VirtualMemSize - 1)) >> 12; // Try L2
const auto PageOffset = Address & (0x0FFF); const auto PageIndex = (Address & (VirtualMemSize - 1)) >> 12;
const auto PageOffset = Address & (0x0FFF);
const auto Pointers = reinterpret_cast<uintptr_t*>(PagePointer); const auto Pointers = reinterpret_cast<uintptr_t*>(PagePointer);
auto LocalPagePointer = Pointers[PageIndex]; auto LocalPagePointer = Pointers[PageIndex];
// Do we a page pointer for this address? // Do we a page pointer for this address?
if (LocalPagePointer) { if (LocalPagePointer) {
// Find there pointer for the address in the blocks // Find there pointer for the address in the blocks
auto BlockPointers = reinterpret_cast<LookupCacheEntry*>(LocalPagePointer); auto BlockPointers = reinterpret_cast<LookupCacheEntry*>(LocalPagePointer);
if (BlockPointers[PageOffset].GuestCode == Address) { if (BlockPointers[PageOffset].GuestCode == Address) {
L1Entry.GuestCode = Address; L1Entry.GuestCode = Address;
L1Entry.HostCode = BlockPointers[PageOffset].HostCode; L1Entry.HostCode = BlockPointers[PageOffset].HostCode;
return L1Entry.HostCode; HostPtr = L1Entry.HostCode;
}
}
}
if (!HostPtr) {
// Try L3
auto Entry = Shared->FindBlock(Address, lk);
if (Entry) {
CacheBlockMapping(Address, *Entry, false, lk);
HostPtr = Entry->HostCode;
}
} }
} }
// Try L3 if (HostPtr && DynamicL1Cache()) {
auto HostCode = Shared->FindBlock(Address, lk); UpdateDynamicL1Stats(Thread);
if (HostCode) {
CacheBlockMapping(Address, HostCode.value());
return HostCode.value();
} }
// Failed to find FEXCORE_PROFILE_INSTANT_INCREMENT(Thread, AccumulatedCacheMissCount, 1);
return 0;
return HostPtr;
}
void UpdateDynamicL1Stats(FEXCore::Core::InternalThreadState* Thread) {
// If host pointer was found in L2 or L3, then add it to the counter.
// Keeping track not L1 misses, but specifically L2/L3 hits.
++L2L3CacheHits;
const auto CurrentTime = std::chrono::system_clock::now();
const auto Period = CurrentTime - LastPeriod;
if (Period >= SamplePeriod) {
// If larger than the sample period then check if we need to increase L1 cache size.
const double AveragePerSecond = static_cast<double>(L2L3CacheHits) /
static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(Period).count()) * 1000.0;
if (AveragePerSecond >= DynamicL1CacheIncreaseCountHeuristic()) {
if (CurrentL1Entries < MAX_L1_ENTRIES) {
CurrentL1Entries <<= 1;
L1PointerMask = CurrentL1Entries - 1;
// Update the thread's L1 pointer mask to increase how much cache it uses.
// Since we're in C-code, this is safe to update here.
Thread->CurrentFrame->State.L1Mask = GetScaledL1PointerMask();
}
} else if (AveragePerSecond < DynamicL1CacheDecreaseCountHeuristic()) {
if (CurrentL1Entries > MIN_L1_ENTRIES) {
CurrentL1Entries >>= 1;
L1PointerMask = CurrentL1Entries - 1;
// Madvise the entries that we are dropping. Gives the memory back to the OS.
LookupCacheEntry* FirstZeroL1Entry = &reinterpret_cast<LookupCacheEntry*>(L1Pointer)[CurrentL1Entries];
size_t ZeroMemorySize = (MAX_L1_ENTRIES - CurrentL1Entries) * sizeof(LookupCacheEntry);
FEXCore::Allocator::VirtualDontNeed(FirstZeroL1Entry, ZeroMemorySize, false);
// Update the thread's L1 pointer mask to increase how much cache it uses.
// Since we're in C-code, this is safe to update here.
Thread->CurrentFrame->State.L1Mask = GetScaledL1PointerMask();
}
}
// Update Last period to start again.
LastPeriod = CurrentTime;
L2L3CacheHits = 0;
}
} }
GuestToHostMap* Shared = nullptr; GuestToHostMap* Shared = nullptr;
// Appends a list of Block {Address} to CodePages [Start, Start + Length) // Appends a list of Block {Address} to CodePages [Start, Start + Length)
// Returns true if new pages are marked as containing code // Returns true if new pages are marked as containing code
bool AddBlockExecutableRange(const fextl::set<uint64_t>& Addresses, uint64_t Start, uint64_t Length) { bool AddBlockExecutableRange(FEXCore::Core::InternalThreadState* Thread, const fextl::set<uint64_t>& Addresses, uint64_t Start, uint64_t Length) {
auto lk = Shared->AcquireLock(); std::optional<FEXCore::SHMStats::AccumulationBlock<uint64_t>> LockTime(
Thread->ThreadStats ? &Thread->ThreadStats->AccumulatedCacheWriteLockTime : nullptr);
auto lk = Shared->AcquireWriteLock();
LockTime.reset();
return Shared->AddBlockExecutableRange(Addresses, Start, Length, lk); return Shared->AddBlockExecutableRange(Addresses, Start, Length, lk);
} }
// Adds to Guest -> Host code mapping // Adds to Guest -> Host code mapping
void AddBlockMapping(uint64_t Address, void* HostCode) { void AddBlockMapping(FEXCore::Core::InternalThreadState* Thread, uint64_t Address, const fextl::vector<uint64_t>& CodePages, void* HostCode) {
auto lk = Shared->AcquireLock(); std::optional<FEXCore::SHMStats::AccumulationBlock<uint64_t>> LockTime(
Thread->ThreadStats ? &Thread->ThreadStats->AccumulatedCacheWriteLockTime : nullptr);
auto lk = Shared->AcquireWriteLock();
LockTime.reset();
Shared->AddBlockMapping(Address, HostCode, lk); const auto& Entry = Shared->AddBlockMapping(Address, CodePages, HostCode, lk);
// There is no need to update L1 or L2, they will get updated on first lookup // There is no need to update L1 or L2, they will get updated on first lookup
// However, adding to L1 here increases performance // However, adding to L1 here increases performance
auto& L1Entry = reinterpret_cast<LookupCacheEntry*>(L1Pointer)[Address & L1_ENTRIES_MASK]; CacheBlockMapping(Address, Entry, true, lk);
L1Entry.GuestCode = Address;
L1Entry.HostCode = (uintptr_t)HostCode;
} }
// NOTE: It's the caller's responsibility to call Erase() for all other // Invalidates L1/L2 for a given guest block
// GuestToHostMaps that share the same LookupCache. Otherwise, the void InvalidateCache(uint64_t Address, const LookupCacheWriteLockToken& lk) {
// L1/L2 caches will contain stale references to deallocated memory.
bool Erase(FEXCore::Core::CpuStateFrame* Frame, uint64_t Address) {
auto lk = Shared->AcquireLock();
bool ErasedAny = Shared->Erase(Frame, Address, lk);
// Do L1 // Do L1
auto& L1Entry = reinterpret_cast<LookupCacheEntry*>(L1Pointer)[Address & L1_ENTRIES_MASK]; auto& L1Entry = reinterpret_cast<LookupCacheEntry*>(L1Pointer)[Address & L1PointerMask];
if (L1Entry.GuestCode == Address) { if (L1Entry.GuestCode == Address) {
L1Entry.GuestCode = 0; L1Entry.GuestCode = 0;
ErasedAny = true;
// Leave L1Entry.HostCode as is, so that concurrent lookups won't read a null pointer // Leave L1Entry.HostCode as is, so that concurrent lookups won't read a null pointer
// This is a soft guarantee for cross thread invalidation, as atomics are not used // This is a soft guarantee for cross thread invalidation, as atomics are not used
// and it hasn't been thoroughly tested // and it hasn't been thoroughly tested
} }
// Do full map if (!DisableL2Cache()) {
Address = Address & (VirtualMemSize - 1); // Do full map
uint64_t PageOffset = Address & (0x0FFF); Address = Address & (VirtualMemSize - 1);
Address >>= 12; uint64_t PageOffset = Address & (0x0FFF);
Address >>= 12;
uintptr_t* Pointers = reinterpret_cast<uintptr_t*>(PagePointer); uintptr_t* Pointers = reinterpret_cast<uintptr_t*>(PagePointer);
uint64_t LocalPagePointer = Pointers[Address]; uint64_t LocalPagePointer = Pointers[Address];
if (!LocalPagePointer) { if (!LocalPagePointer) {
// Page for this code didn't even exist, nothing to do // Page for this code didn't even exist, nothing to do
return ErasedAny; return;
}
// Page exists, just set the offset to zero
auto BlockPointers = reinterpret_cast<LookupCacheEntry*>(LocalPagePointer);
BlockPointers[PageOffset].GuestCode = 0;
BlockPointers[PageOffset].HostCode = 0;
} }
// Page exists, just set the offset to zero
auto BlockPointers = reinterpret_cast<LookupCacheEntry*>(LocalPagePointer);
BlockPointers[PageOffset].GuestCode = 0;
BlockPointers[PageOffset].HostCode = 0;
return true;
} }
void AddBlockLink(uint64_t GuestDestination, FEXCore::Context::ExitFunctionLinkData* HostLink, const FEXCore::Context::BlockDelinkerFunc& delinker) { // Invalidates all L1/L2 entries for all guest block that intersect the given range
auto lk = Shared->AcquireLock(); bool InvalidateCacheRange(uint64_t Start, uint64_t Length) {
auto lk = Shared->AcquireWriteLock();
auto lower = CachedCodePages.lower_bound(Start >> 12);
auto upper = CachedCodePages.upper_bound((Start + Length - 1) >> 12);
for (auto it = lower; it != upper; it++) {
for (const auto& Entry : it->second) {
InvalidateCache(Entry, lk);
}
}
CachedCodePages.erase(lower, upper);
return upper != lower;
}
void AddBlockLink(uint64_t GuestDestination, FEXCore::Context::ExitFunctionLinkData* HostLink,
const FEXCore::Context::BlockDelinkerFunc& delinker, const LookupCacheWriteLockToken& lk) {
Shared->AddBlockLink(GuestDestination, HostLink, delinker, lk); Shared->AddBlockLink(GuestDestination, HostLink, delinker, lk);
} }
void ClearCache(); void ClearCache(const LookupCacheWriteLockToken&);
void ClearL2Cache(); void ClearL2Cache(const LookupCacheWriteLockToken&);
void ClearThreadLocalCaches(); void ClearThreadLocalCaches(const LookupCacheWriteLockToken&);
uintptr_t GetL1Pointer() const { uintptr_t GetL1Pointer() const {
return L1Pointer; return L1Pointer;
} }
uintptr_t GetScaledL1PointerMask() const {
return L1PointerMask << FEXCore::ilog2(sizeof(LookupCache::LookupCacheEntry));
}
uintptr_t GetPagePointer() const { uintptr_t GetPagePointer() const {
return PagePointer; return PagePointer;
} }
@@ -245,9 +346,6 @@ public:
return VirtualMemSize; return VirtualMemSize;
} }
constexpr static size_t L1_ENTRIES = 1 * 1024 * 1024; // Must be a power of 2
constexpr static size_t L1_ENTRIES_MASK = L1_ENTRIES - 1;
// This needs to be taken before reads or writes to L2, L3, CodePages, // This needs to be taken before reads or writes to L2, L3, CodePages,
// and before writes to L1. Concurrent access from a thread that this LookupCache doesn't belong to // and before writes to L1. Concurrent access from a thread that this LookupCache doesn't belong to
// may only happen during cross thread invalidation (::Erase). // may only happen during cross thread invalidation (::Erase).
@@ -255,45 +353,52 @@ public:
// Some care is taken so that L1 lookups can be done without locks, and even tearing is unlikely to lead to a crash. // Some care is taken so that L1 lookups can be done without locks, and even tearing is unlikely to lead to a crash.
// This approach has not been fully vetted yet. // This approach has not been fully vetted yet.
// Also note that L1 lookups might be inlined in the JIT Dispatcher and/or block ends. // Also note that L1 lookups might be inlined in the JIT Dispatcher and/or block ends.
auto AcquireLock() { auto AcquireWriteLock() {
return Shared->AcquireLock(); return Shared->AcquireWriteLock();
} }
private: private:
void CacheBlockMapping(uint64_t Address, uintptr_t HostCode) { void CacheBlockMapping(uint64_t Address, const GuestToHostMap::BlockEntry& Entry, bool L1Only, const LookupCacheWriteLockToken& lk) {
// Do L1 for (const auto& CodePage : Entry.CodePages) {
auto& L1Entry = reinterpret_cast<LookupCacheEntry*>(L1Pointer)[Address & L1_ENTRIES_MASK]; CachedCodePages[CodePage >> 12].insert(Address);
L1Entry.GuestCode = Address;
L1Entry.HostCode = HostCode;
// Do ful map
auto FullAddress = Address;
Address = Address & (VirtualMemSize - 1);
uint64_t PageOffset = Address & (0x0FFF);
Address >>= 12;
uintptr_t* Pointers = reinterpret_cast<uintptr_t*>(PagePointer);
uint64_t LocalPagePointer = Pointers[Address];
if (!LocalPagePointer) {
// We don't have a page pointer for this address
// Allocate one now if we can
uintptr_t NewPageBacking = AllocateBackingForPage();
if (!NewPageBacking) {
// Couldn't allocate, clear L2 and retry
ClearL2Cache();
CacheBlockMapping(Address, HostCode);
return;
}
Pointers[Address] = NewPageBacking;
LocalPagePointer = NewPageBacking;
} }
// Add the new pointer to the page block // Do L1
auto BlockPointers = reinterpret_cast<LookupCacheEntry*>(LocalPagePointer); auto& L1Entry = reinterpret_cast<LookupCacheEntry*>(L1Pointer)[Address & L1PointerMask];
L1Entry.GuestCode = Address;
L1Entry.HostCode = Entry.HostCode;
// This silently replaces existing mappings if (!DisableL2Cache() && !L1Only) {
BlockPointers[PageOffset].GuestCode = FullAddress; // Do ful map
BlockPointers[PageOffset].HostCode = HostCode; auto FullAddress = Address;
Address = Address & (VirtualMemSize - 1);
uint64_t PageOffset = Address & (0x0FFF);
Address >>= 12;
uintptr_t* Pointers = reinterpret_cast<uintptr_t*>(PagePointer);
uint64_t LocalPagePointer = Pointers[Address];
if (!LocalPagePointer) {
// We don't have a page pointer for this address
// Allocate one now if we can
uintptr_t NewPageBacking = AllocateBackingForPage();
if (!NewPageBacking) {
// Couldn't allocate, clear L2 and retry
ClearL2Cache(lk);
CacheBlockMapping(Address, Entry, false, lk);
return;
}
Pointers[Address] = NewPageBacking;
LocalPagePointer = NewPageBacking;
}
// Add the new pointer to the page block
auto BlockPointers = reinterpret_cast<LookupCacheEntry*>(LocalPagePointer);
// This silently replaces existing mappings
BlockPointers[PageOffset].GuestCode = FullAddress;
BlockPointers[PageOffset].HostCode = Entry.HostCode;
}
} }
uintptr_t AllocateBackingForPage() { uintptr_t AllocateBackingForPage() {
@@ -310,19 +415,38 @@ private:
return PageMemory + NewBase; return PageMemory + NewBase;
} }
// Maps from a page index to all blocks in the page that have at some point been fetched into L1/L2
fextl::map<uint64_t, fextl::unordered_set<uint64_t>> CachedCodePages;
uintptr_t PagePointer; uintptr_t PagePointer;
uintptr_t PageMemory; uintptr_t PageMemory;
uintptr_t L1Pointer; uintptr_t L1Pointer;
uintptr_t L1PointerMask;
size_t TotalCacheSize; size_t TotalCacheSize;
// Start with 8k entries in L1 to give 128KB of L1 cache to each thread.
// Max out at 1 million entries to give each thread 16MB of L1 cache maximum.
constexpr static size_t MIN_L1_ENTRIES = 8 * 1024; // Must be a power of 2
constexpr static size_t MAX_L1_ENTRIES = 1 * 1024 * 1024; // Must be a power of 2
constexpr static size_t CODE_SIZE = 128 * 1024 * 1024; constexpr static size_t CODE_SIZE = 128 * 1024 * 1024;
constexpr static size_t SIZE_PER_PAGE = 4096 * sizeof(LookupCacheEntry); constexpr static size_t SIZE_PER_PAGE = FEXCore::Utils::FEX_PAGE_SIZE * sizeof(LookupCacheEntry);
constexpr static size_t L1_SIZE = L1_ENTRIES * sizeof(LookupCacheEntry); constexpr static size_t MAX_L1_SIZE = MAX_L1_ENTRIES * sizeof(LookupCacheEntry);
size_t AllocateOffset {}; size_t AllocateOffset {};
FEXCore::Context::ContextImpl* ctx; FEXCore::Context::ContextImpl* ctx;
uint64_t VirtualMemSize {}; uint64_t VirtualMemSize {};
size_t CurrentL1Entries = MIN_L1_ENTRIES;
uint64_t L2L3CacheHits {};
std::chrono::time_point<std::chrono::system_clock> LastPeriod {};
constexpr static std::chrono::seconds SamplePeriod {1};
FEX_CONFIG_OPT(DynamicL1CacheIncreaseCountHeuristic, DYNAMICL1CACHEINCREASECOUNTHEURISTIC);
FEX_CONFIG_OPT(DynamicL1CacheDecreaseCountHeuristic, DYNAMICL1CACHEDECREASECOUNTHEURISTIC);
FEX_CONFIG_OPT(DynamicL1Cache, DYNAMICL1CACHE);
FEX_CONFIG_OPT(DisableL2Cache, DISABLEL2CACHE);
}; };
} // namespace FEXCore } // namespace FEXCore
@@ -28,7 +28,6 @@ $end_info$
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <tuple>
namespace FEXCore::IR { namespace FEXCore::IR {
@@ -51,8 +50,6 @@ void OpDispatchBuilder::SyscallOp(OpcodeArgs, bool IsSyscallInst) {
FEXCore::X86State::REG_RSI, FEXCore::X86State::REG_RDI, FEXCore::X86State::REG_RBP, FEXCore::X86State::REG_RSI, FEXCore::X86State::REG_RDI, FEXCore::X86State::REG_RBP,
}; };
SyscallFlags DefaultSyscallFlags = FEXCore::IR::SyscallFlags::DEFAULT;
const auto OSABI = CTX->SyscallHandler->GetOSABI(); const auto OSABI = CTX->SyscallHandler->GetOSABI();
if (OSABI == FEXCore::HLE::SyscallOSABI::OS_LINUX64) { if (OSABI == FEXCore::HLE::SyscallOSABI::OS_LINUX64) {
NumArguments = GPRIndexes_64.size(); NumArguments = GPRIndexes_64.size();
@@ -64,7 +61,6 @@ void OpDispatchBuilder::SyscallOp(OpcodeArgs, bool IsSyscallInst) {
// All registers will be spilled before the syscall and filled afterwards so no JIT-side argument handling is necessary. // All registers will be spilled before the syscall and filled afterwards so no JIT-side argument handling is necessary.
NumArguments = 0; NumArguments = 0;
GPRIndexes = nullptr; GPRIndexes = nullptr;
DefaultSyscallFlags = FEXCore::IR::SyscallFlags::NORETURNEDRESULT;
} else { } else {
ERROR_AND_DIE_FMT("Unhandled OSABI syscall"); ERROR_AND_DIE_FMT("Unhandled OSABI syscall");
} }
@@ -98,9 +94,10 @@ void OpDispatchBuilder::SyscallOp(OpcodeArgs, bool IsSyscallInst) {
} }
FlushRegisterCache(); FlushRegisterCache();
auto SyscallOp = _Syscall(Arguments[0], Arguments[1], Arguments[2], Arguments[3], Arguments[4], Arguments[5], Arguments[6], DefaultSyscallFlags); auto SyscallOp = _Syscall(Arguments[0], Arguments[1], Arguments[2], Arguments[3], Arguments[4], Arguments[5], Arguments[6]);
if ((DefaultSyscallFlags & FEXCore::IR::SyscallFlags::NORETURNEDRESULT) != FEXCore::IR::SyscallFlags::NORETURNEDRESULT) { // Generic ABI doesn't store result in RAX.
if (OSABI != FEXCore::HLE::SyscallOSABI::OS_GENERIC) {
StoreGPRRegister(X86State::REG_RAX, SyscallOp); StoreGPRRegister(X86State::REG_RAX, SyscallOp);
} }
@@ -153,12 +150,6 @@ void OpDispatchBuilder::NOPOp(OpcodeArgs) {}
void OpDispatchBuilder::RETOp(OpcodeArgs) { void OpDispatchBuilder::RETOp(OpcodeArgs) {
const auto GPRSize = GetGPROpSize(); const auto GPRSize = GetGPROpSize();
// ABI Optimization: Flags don't survive calls or rets
if (CTX->Config.ABILocalFlags) {
_InvalidateFlags(~0UL); // all flags
InvalidatePF_AF();
}
Ref SP = _RMWHandle(LoadGPRRegister(X86State::REG_RSP)); Ref SP = _RMWHandle(LoadGPRRegister(X86State::REG_RSP));
Ref NewRIP = Pop(GPRSize, SP); Ref NewRIP = Pop(GPRSize, SP);
@@ -522,12 +513,6 @@ void OpDispatchBuilder::CALLOp(OpcodeArgs) {
BlockSetRIP = true; BlockSetRIP = true;
// ABI Optimization: Flags don't survive calls or rets
if (CTX->Config.ABILocalFlags) {
_InvalidateFlags(~0UL); // all flags
InvalidatePF_AF();
}
// Call instruction only uses up to 32-bit signed displacement // Call instruction only uses up to 32-bit signed displacement
int64_t TargetOffset = Op->Src[0].Literal(); int64_t TargetOffset = Op->Src[0].Literal();
@@ -3230,7 +3215,11 @@ void OpDispatchBuilder::STOSOp(OpcodeArgs) {
Ref Dest = MakeSegmentAddress(X86State::REG_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); Ref Dest = MakeSegmentAddress(X86State::REG_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
// Store to memory where RDI points // Store to memory where RDI points
_StoreMemGPRAutoTSO(Size, Dest, Src, Size); if (CTX->IsMemcpyAtomicTSOEnabled()) {
_StoreMemGPRAutoTSO(Size, Dest, Src, Size);
} else {
_StoreMem(RegClass::GPR, Size, Src, Dest, Invalid(), OpSize::i8Bit, MemOffsetType::SXTX, 1);
}
// Offset the pointer // Offset the pointer
Ref TailDest = LoadGPRRegister(X86State::REG_RDI); Ref TailDest = LoadGPRRegister(X86State::REG_RDI);
@@ -3298,10 +3287,15 @@ void OpDispatchBuilder::MOVSOp(OpcodeArgs) {
Ref RSI = MakeSegmentAddress(X86State::REG_RSI, Op->Flags, X86Tables::DecodeFlags::FLAG_DS_PREFIX); Ref RSI = MakeSegmentAddress(X86State::REG_RSI, Op->Flags, X86Tables::DecodeFlags::FLAG_DS_PREFIX);
Ref RDI = MakeSegmentAddress(X86State::REG_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true); Ref RDI = MakeSegmentAddress(X86State::REG_RDI, 0, X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
auto Src = _LoadMemGPRAutoTSO(Size, RSI, Size); if (CTX->IsMemcpyAtomicTSOEnabled()) {
auto Src = _LoadMemGPRAutoTSO(Size, RSI, Size);
// Store to memory where RDI points // Store to memory where RDI points
_StoreMemGPRAutoTSO(Size, RDI, Src, Size); _StoreMemGPRAutoTSO(Size, RDI, Src, Size);
} else {
auto Src = _LoadMem(RegClass::GPR, Size, RSI, Invalid(), OpSize::i8Bit, MemOffsetType::SXTX, 1);
_StoreMem(RegClass::GPR, Size, Src, RDI, Invalid(), OpSize::i8Bit, MemOffsetType::SXTX, 1);
}
auto PtrDir = LoadDir(IR::OpSizeToSize(Size)); auto PtrDir = LoadDir(IR::OpSizeToSize(Size));
RSI = Add(OpSize::i64Bit, RSI, PtrDir); RSI = Add(OpSize::i64Bit, RSI, PtrDir);
@@ -1330,7 +1330,6 @@ protected:
private: private:
FEX_CONFIG_OPT(ReducedPrecisionMode, X87REDUCEDPRECISION); FEX_CONFIG_OPT(ReducedPrecisionMode, X87REDUCEDPRECISION);
FEX_CONFIG_OPT(StrictReducedPrecisionMode, X87STRICTREDUCEDPRECISION);
struct JumpTargetInfo { struct JumpTargetInfo {
Ref BlockEntry; Ref BlockEntry;
@@ -2528,7 +2527,7 @@ private:
const bool AtomicTSO = IsTSOEnabled(Class) && !A.NonTSO; const bool AtomicTSO = IsTSOEnabled(Class) && !A.NonTSO;
// Use ldp if possible, otherwise fallback on two loads. // Use ldp if possible, otherwise fallback on two loads.
if (!AtomicTSO && !A.Segment && Size >= OpSize::i32Bit & Size <= OpSize::i128Bit) { if (!AtomicTSO && !A.Segment && Size >= OpSize::i32Bit && Size <= OpSize::i128Bit) {
const auto B = SelectPairAddressMode(A, Size); const auto B = SelectPairAddressMode(A, Size);
return LoadMemPair(Class, Size, B.Base, B.Offset); return LoadMemPair(Class, Size, B.Base, B.Offset);
} }
@@ -2567,7 +2566,7 @@ private:
const bool AtomicTSO = IsTSOEnabled(Class) && !A.NonTSO; const bool AtomicTSO = IsTSOEnabled(Class) && !A.NonTSO;
// Use stp if possible, otherwise fallback on two stores. // Use stp if possible, otherwise fallback on two stores.
if (!AtomicTSO && !A.Segment && Size >= OpSize::i32Bit & Size <= OpSize::i128Bit) { if (!AtomicTSO && !A.Segment && Size >= OpSize::i32Bit && Size <= OpSize::i128Bit) {
const auto B = SelectPairAddressMode(A, Size); const auto B = SelectPairAddressMode(A, Size);
_StoreMemPair(Class, Size, Value1, Value2, B.Base, B.Offset); _StoreMemPair(Class, Size, Value1, Value2, B.Base, B.Offset);
} else { } else {
@@ -1956,7 +1956,7 @@ void OpDispatchBuilder::AVX128_VFMAImpl(OpcodeArgs, IROps IROp, uint8_t Src1Idx,
} }
void OpDispatchBuilder::AVX128_VFMAScalarImpl(OpcodeArgs, IROps IROp, uint8_t Src1Idx, uint8_t Src2Idx, uint8_t AddendIdx) { void OpDispatchBuilder::AVX128_VFMAScalarImpl(OpcodeArgs, IROps IROp, uint8_t Src1Idx, uint8_t Src2Idx, uint8_t AddendIdx) {
const auto SrcSize = OpSizeFromSrc(Op); const OpSize ElementSize = Op->Flags & X86Tables::DecodeFlags::FLAG_OPTION_AVX_W ? OpSize::i64Bit : OpSize::i32Bit;
auto Dest = AVX128_LoadSource_WithOpSize(Op, Op->Dest, Op->Flags, false).Low; auto Dest = AVX128_LoadSource_WithOpSize(Op, Op->Dest, Op->Flags, false).Low;
auto Src1 = AVX128_LoadSource_WithOpSize(Op, Op->Src[0], Op->Flags, false).Low; auto Src1 = AVX128_LoadSource_WithOpSize(Op, Op->Src[0], Op->Flags, false).Low;
@@ -1964,13 +1964,13 @@ void OpDispatchBuilder::AVX128_VFMAScalarImpl(OpcodeArgs, IROps IROp, uint8_t Sr
if (Op->Src[1].IsGPR()) { if (Op->Src[1].IsGPR()) {
Src2 = AVX128_LoadSource_WithOpSize(Op, Op->Src[1], Op->Flags, false).Low; Src2 = AVX128_LoadSource_WithOpSize(Op, Op->Src[1], Op->Flags, false).Low;
} else { } else {
Src2 = LoadSourceFPR_WithOpSize(Op, Op->Src[1], SrcSize, Op->Flags); Src2 = LoadSourceFPR_WithOpSize(Op, Op->Src[1], ElementSize, Op->Flags);
} }
Ref Sources[3] = {Dest, Src1, Src2}; Ref Sources[3] = {Dest, Src1, Src2};
DeriveOp(Result_Low, IROp, DeriveOp(Result_Low, IROp,
_VFMLAScalarInsert(OpSize::i128Bit, SrcSize, Dest, Sources[Src1Idx - 1], Sources[Src2Idx - 1], Sources[AddendIdx - 1])); _VFMLAScalarInsert(OpSize::i128Bit, ElementSize, Dest, Sources[Src1Idx - 1], Sources[Src2Idx - 1], Sources[AddendIdx - 1]));
AVX128_StoreResult_WithOpSize(Op, Op->Dest, AVX128_Zext(Result_Low)); AVX128_StoreResult_WithOpSize(Op, Op->Dest, AVX128_Zext(Result_Low));
} }
@@ -145,7 +145,7 @@ constexpr DispatchTableEntry OpDispatch_TwoByteOpTable[] = {
#ifndef _WIN32 #ifndef _WIN32
// FEX reserved instructions // FEX reserved instructions
{0x37, 1, &OpDispatchBuilder::CallbackReturnOp}, {0x3E, 1, &OpDispatchBuilder::CallbackReturnOp},
{0x3F, 1, &OpDispatchBuilder::ThunkOp}, {0x3F, 1, &OpDispatchBuilder::ThunkOp},
#endif #endif
}; };
@@ -28,7 +28,7 @@ X86GeneratedCode::X86GeneratedCode() {
CodePtr = AllocateGuestCodeSpace(CODE_SIZE); CodePtr = AllocateGuestCodeSpace(CODE_SIZE);
constexpr std::array<uint8_t, 2> SignalReturnCode = { constexpr std::array<uint8_t, 2> SignalReturnCode = {
0x0F, 0x37, // CALLBACKRET FEX Instruction 0x0F, 0x3E, // CALLBACKRET FEX Instruction
}; };
CallbackReturn = reinterpret_cast<uint64_t>(CodePtr); CallbackReturn = reinterpret_cast<uint64_t>(CodePtr);
@@ -51,7 +51,9 @@ void* X86GeneratedCode::AllocateGuestCodeSpace(size_t Size) {
if (Is64BitMode()) { if (Is64BitMode()) {
// 64bit mode can have its sigret handler anywhere // 64bit mode can have its sigret handler anywhere
return FEXCore::Allocator::VirtualAlloc(Size); auto Result = FEXCore::Allocator::VirtualAlloc(Size);
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(Result), Size);
return Result;
} }
// First 64bit page // First 64bit page
@@ -100,10 +100,11 @@ constexpr std::array<X86InstInfo, MAX_SECOND_TABLE_SIZE> SecondBaseOps = []() co
{0x34, 1, X86InstInfo{"SYSENTER", TYPE_INST, FLAGS_NO_OVERLAY, 0}}, {0x34, 1, X86InstInfo{"SYSENTER", TYPE_INST, FLAGS_NO_OVERLAY, 0}},
{0x35, 1, X86InstInfo{"SYSEXIT", TYPE_INST, FLAGS_NO_OVERLAY, 0}}, {0x35, 1, X86InstInfo{"SYSEXIT", TYPE_INST, FLAGS_NO_OVERLAY, 0}},
{0x36, 1, X86InstInfo{"", TYPE_INVALID, FLAGS_NO_OVERLAY, 0}}, {0x36, 1, X86InstInfo{"", TYPE_INVALID, FLAGS_NO_OVERLAY, 0}},
{0x37, 1, X86InstInfo{"GETSEC", TYPE_INVALID, FLAGS_NO_OVERLAY, 0}},
{0x38, 1, X86InstInfo{"", TYPE_0F38_TABLE, FLAGS_NO_OVERLAY, 0}}, {0x38, 1, X86InstInfo{"", TYPE_0F38_TABLE, FLAGS_NO_OVERLAY, 0}},
{0x39, 1, X86InstInfo{"", TYPE_INVALID, FLAGS_NO_OVERLAY, 0}}, {0x39, 1, X86InstInfo{"", TYPE_INVALID, FLAGS_NO_OVERLAY, 0}},
{0x3A, 1, X86InstInfo{"", TYPE_0F3A_TABLE, FLAGS_NO_OVERLAY, 0}}, {0x3A, 1, X86InstInfo{"", TYPE_0F3A_TABLE, FLAGS_NO_OVERLAY, 0}},
{0x3B, 4, X86InstInfo{"", TYPE_INVALID, FLAGS_NO_OVERLAY, 0}}, {0x3B, 3, X86InstInfo{"", TYPE_INVALID, FLAGS_NO_OVERLAY, 0}},
{0x40, 1, X86InstInfo{"CMOVO", TYPE_INST, FLAGS_MODRM | FLAGS_NO_OVERLAY, 0}}, {0x40, 1, X86InstInfo{"CMOVO", TYPE_INST, FLAGS_MODRM | FLAGS_NO_OVERLAY, 0}},
{0x41, 1, X86InstInfo{"CMOVNO", TYPE_INST, FLAGS_MODRM | FLAGS_NO_OVERLAY, 0}}, {0x41, 1, X86InstInfo{"CMOVNO", TYPE_INST, FLAGS_MODRM | FLAGS_NO_OVERLAY, 0}},
@@ -299,7 +300,7 @@ constexpr std::array<X86InstInfo, MAX_SECOND_TABLE_SIZE> SecondBaseOps = []() co
// FEX reserved instructions // FEX reserved instructions
// Unused x86 encoding instruction. // Unused x86 encoding instruction.
{0x37, 1, X86InstInfo{"CALLBACKRET", TYPE_INST, FLAGS_BLOCK_END | FLAGS_NO_OVERLAY | FLAGS_SETS_RIP, 0}}, {0x3E, 1, X86InstInfo{"CALLBACKRET", TYPE_INST, FLAGS_BLOCK_END | FLAGS_NO_OVERLAY | FLAGS_SETS_RIP, 0}},
// This was originally used by VIA to jump to its alternative instruction set. Used for OP_THUNK // This was originally used by VIA to jump to its alternative instruction set. Used for OP_THUNK
{0x3F, 1, X86InstInfo{"ALTINST", TYPE_INST, FLAGS_BLOCK_END | FLAGS_NO_OVERLAY | FLAGS_SETS_RIP, 0}}, {0x3F, 1, X86InstInfo{"ALTINST", TYPE_INST, FLAGS_BLOCK_END | FLAGS_NO_OVERLAY | FLAGS_SETS_RIP, 0}},
+3 -18
View File
@@ -138,7 +138,6 @@
"FenceType": "FenceType", "FenceType": "FenceType",
"RegisterClass": "RegClass", "RegisterClass": "RegClass",
"CondClass": "CondClass", "CondClass": "CondClass",
"SyscallFlags": "FEXCore::IR::SyscallFlags",
"SHA256Sum": "SHA256Sum", "SHA256Sum": "SHA256Sum",
"MemOffsetType": "MemOffsetType", "MemOffsetType": "MemOffsetType",
"BreakDefinition": "BreakDefinition", "BreakDefinition": "BreakDefinition",
@@ -314,25 +313,13 @@
"CallbackReturn": { "CallbackReturn": {
"HasSideEffects": true "HasSideEffects": true
}, },
"GPR = Syscall GPR:$SyscallID, GPR:$Arg0, GPR:$Arg1, GPR:$Arg2, GPR:$Arg3, GPR:$Arg4, GPR:$Arg5, SyscallFlags:$Flags": { "GPR = Syscall GPR:$SyscallID, GPR:$Arg0, GPR:$Arg1, GPR:$Arg2, GPR:$Arg3, GPR:$Arg4, GPR:$Arg5": {
"HasSideEffects": true, "HasSideEffects": true,
"Desc": ["Dispatches a guest syscall through to the SyscallHandler class" "Desc": ["Dispatches a guest syscall through to the SyscallHandler class"
], ],
"DestSize": "OpSize::i64Bit" "DestSize": "OpSize::i64Bit"
}, },
"GPR = InlineSyscall GPR:$Arg0, GPR:$Arg1, GPR:$Arg2, GPR:$Arg3, GPR:$Arg4, GPR:$Arg5, i32:$HostSyscallNumber, SyscallFlags:$Flags": {
"HasSideEffects": true,
"Desc": ["Dispatches a guest syscall directly to the host syscall interface,",
"bypassing the SyscallHandler class used by Syscall.",
"This has significantly less overhead than Syscall, which needs to save JIT state first.",
"Can only be used for syscalls that match across architecture,",
"such as gettid (matches on x86/x86-64/Arm64)."
],
"DestSize": "OpSize::i64Bit"
},
"Thunk GPR:$ArgPtr, SHA256Sum:$ThunkNameHash": { "Thunk GPR:$ArgPtr, SHA256Sum:$ThunkNameHash": {
"HasSideEffects": true "HasSideEffects": true
}, },
@@ -578,8 +565,7 @@
"Desc": ["Does a x86 TSO compatible load from memory. Offset must be Invalid()." "Desc": ["Does a x86 TSO compatible load from memory. Offset must be Invalid()."
], ],
"Inline": ["", "Memtso"], "Inline": ["", "Memtso"],
"DestSize": "Size", "DestSize": "Size"
"DynamicDispatch": true
}, },
"StoreMemTSO RegisterClass:$Class, OpSize:#Size, SSA:$Value, GPR:$Addr, GPR:$Offset, OpSize:$Align, MemOffsetType:$OffsetType, u8:$OffsetScale": { "StoreMemTSO RegisterClass:$Class, OpSize:#Size, SSA:$Value, GPR:$Addr, GPR:$Offset, OpSize:$Align, MemOffsetType:$OffsetType, u8:$OffsetScale": {
@@ -587,8 +573,7 @@
], ],
"Inline": ["Zero", "", "Memtso"], "Inline": ["Zero", "", "Memtso"],
"HasSideEffects": true, "HasSideEffects": true,
"DestSize": "Size", "DestSize": "Size"
"DynamicDispatch": true
}, },
"FPR = VLoadVectorMasked OpSize:#RegisterSize, OpSize:#ElementSize, FPR:$Mask, GPR:$Addr, GPR:$Offset, MemOffsetType:$OffsetType, u8:$OffsetScale": { "FPR = VLoadVectorMasked OpSize:#RegisterSize, OpSize:#ElementSize, FPR:$Mask, GPR:$Addr, GPR:$Offset, MemOffsetType:$OffsetType, u8:$OffsetScale": {
-14
View File
@@ -149,20 +149,6 @@ static void PrintArg(fextl::stringstream* out, const IRListView*, RoundMode Arg)
}(); }();
} }
static void PrintArg(fextl::stringstream* out, const IRListView*, SyscallFlags Arg) {
*out << [Arg] {
switch (Arg) {
case SyscallFlags::DEFAULT: return "Default";
case SyscallFlags::OPTIMIZETHROUGH: return "Optimize Through";
case SyscallFlags::NOSYNCSTATEONENTRY: return "No Sync State on Entry";
case SyscallFlags::NORETURN: return "No Return";
case SyscallFlags::NOSIDEEFFECTS: return "No Side Effects";
case SyscallFlags::NORETURNEDRESULT: return "No Returned Result";
}
return "<Unknown Syscall Flags>";
}();
}
static void PrintArg(fextl::stringstream* out, const IRListView*, NamedVectorConstant Arg) { static void PrintArg(fextl::stringstream* out, const IRListView*, NamedVectorConstant Arg) {
*out << [Arg] { *out << [Arg] {
// clang-format off // clang-format off
+1 -1
View File
@@ -39,7 +39,7 @@ public:
} }
protected: protected:
PassManager* Manager; PassManager* Manager {};
}; };
class PassManager final { class PassManager final {
@@ -63,9 +63,9 @@ public:
private: private:
RegisterClassData Classes[IR::NumClasses]; RegisterClassData Classes[IR::NumClasses];
IREmitter* IREmit; IREmitter* IREmit {};
IRListView* IR; IRListView* IR {};
const FEXCore::CPUIDEmu* CPUID; const FEXCore::CPUIDEmu* CPUID {};
// Map of nodes to their preferred register, to coalesce load/store reg. // Map of nodes to their preferred register, to coalesce load/store reg.
fextl::vector<PhysicalRegister> PreferredReg; fextl::vector<PhysicalRegister> PreferredReg;
@@ -83,7 +83,7 @@ private:
fextl::vector<bool> Seen; fextl::vector<bool> Seen;
// SourcesNextUses is read backwards, this tracks the index // SourcesNextUses is read backwards, this tracks the index
int64_t SourceIndex; int64_t SourceIndex {};
bool Rematerializable(IROp_Header* IROp) { bool Rematerializable(IROp_Header* IROp) {
return IROp->Op == OP_CONSTANT; return IROp->Op == OP_CONSTANT;
@@ -110,7 +110,7 @@ private:
// block, so we don't need to size the block up-front. // block, so we don't need to size the block up-front.
fextl::vector<uint32_t> NextUses; fextl::vector<uint32_t> NextUses;
bool AnySpilled; bool AnySpilled {};
bool IsValidArg(OrderedNodeWrapper Arg) { bool IsValidArg(OrderedNodeWrapper Arg) {
if (Arg.IsInvalid()) { if (Arg.IsInvalid()) {
@@ -19,7 +19,7 @@ public:
virtual void AddRegisters(RegClass Class, uint32_t RegisterCount) = 0; virtual void AddRegisters(RegClass Class, uint32_t RegisterCount) = 0;
// Number of GPRs usable for pairs at start of GPR set. Must be even. // Number of GPRs usable for pairs at start of GPR set. Must be even.
uint32_t PairRegs; uint32_t PairRegs {};
}; };
} // namespace FEXCore::IR } // namespace FEXCore::IR
@@ -158,9 +158,7 @@ public:
: Features(Features) : Features(Features)
, GPROpSize(GPROpSize) { , GPROpSize(GPROpSize) {
FEX_CONFIG_OPT(ReducedPrecision, X87REDUCEDPRECISION); FEX_CONFIG_OPT(ReducedPrecision, X87REDUCEDPRECISION);
FEX_CONFIG_OPT(StrictReducedPrecision, X87STRICTREDUCEDPRECISION);
ReducedPrecisionMode = ReducedPrecision; ReducedPrecisionMode = ReducedPrecision;
StrictReducedPrecisionMode = StrictReducedPrecision;
} }
void Run(IREmitter* Emit) override; void Run(IREmitter* Emit) override;
@@ -168,12 +166,10 @@ private:
const FEXCore::HostFeatures& Features; const FEXCore::HostFeatures& Features;
const OpSize GPROpSize; const OpSize GPROpSize;
bool ReducedPrecisionMode; bool ReducedPrecisionMode;
bool StrictReducedPrecisionMode;
FEX_CONFIG_OPT(DisableVixlIndirectCalls, DISABLE_VIXL_INDIRECT_RUNTIME_CALLS); FEX_CONFIG_OPT(DisableVixlIndirectCalls, DISABLE_VIXL_INDIRECT_RUNTIME_CALLS);
// Helpers // Helpers
Ref RotateRight8(uint32_t V, Ref Amount); Ref RotateRight8(uint32_t V, Ref Amount);
Ref SilenceNaN(Ref Value);
void F80SplitStore_Helper(const IROp_StoreStackMem* Op, Ref StackNode) { void F80SplitStore_Helper(const IROp_StoreStackMem* Op, Ref StackNode) {
Ref AddrNode = IR->GetNode(Op->Addr); Ref AddrNode = IR->GetNode(Op->Addr);
@@ -208,9 +204,6 @@ private:
case OpSize::i32Bit: case OpSize::i32Bit:
case OpSize::i64Bit: { case OpSize::i64Bit: {
StackNode = IREmit->_F80CVT(Op->StoreSize, StackNode); StackNode = IREmit->_F80CVT(Op->StoreSize, StackNode);
if (!ReducedPrecisionMode || StrictReducedPrecisionMode) {
StackNode = SilenceNaN(StackNode);
}
IREmit->_StoreMemFPR(Op->StoreSize, StackNode, AddrNode, Offset, Align, OffsetType, OffsetScale); IREmit->_StoreMemFPR(Op->StoreSize, StackNode, AddrNode, Offset, Align, OffsetType, OffsetScale);
break; break;
} }
@@ -242,10 +235,6 @@ private:
MemOffsetType OffsetType = Op->OffsetType; MemOffsetType OffsetType = Op->OffsetType;
uint8_t OffsetScale = Op->OffsetScale; uint8_t OffsetScale = Op->OffsetScale;
if ((!ReducedPrecisionMode || StrictReducedPrecisionMode) && Op->StoreSize != OpSize::f80Bit) {
StackNode = SilenceNaN(StackNode);
}
switch (Op->StoreSize) { switch (Op->StoreSize) {
case OpSize::i32Bit: { case OpSize::i32Bit: {
StackNode = IREmit->_Float_FToF(OpSize::i32Bit, OpSize::i64Bit, StackNode); StackNode = IREmit->_Float_FToF(OpSize::i32Bit, OpSize::i64Bit, StackNode);
@@ -499,15 +488,6 @@ inline Ref X87StackOptimization::RotateRight8(uint32_t V, Ref Amount) {
return IREmit->_Lshr(OpSize::i32Bit, GetConstant(V | (V << 8)), Amount); return IREmit->_Lshr(OpSize::i32Bit, GetConstant(V | (V << 8)), Amount);
} }
inline Ref X87StackOptimization::SilenceNaN(Ref Value) {
Ref GPRValue = IREmit->_VExtractToGPR(OpSize::i64Bit, OpSize::i64Bit, Value, 0);
IREmit->_FCmp(OpSize::i64Bit, Value, Value); // Comparison with itself should set VS if nan
Ref QuietNaNGPR = IREmit->_Or(OpSize::i64Bit, GPRValue, IREmit->_Constant(0x0008000000000000ULL));
Ref SilencedValue = IREmit->_VCastFromGPR(OpSize::i64Bit, OpSize::i64Bit, QuietNaNGPR);
return IREmit->_NZCVSelectV(OpSize::i64Bit, CondClass::VS, SilencedValue, Value);
}
inline std::optional<X87StackOptimization::StackMemberInfo> X87StackOptimization::MigrateToSlowPath_IfInvalid(uint8_t Offset) { inline std::optional<X87StackOptimization::StackMemberInfo> X87StackOptimization::MigrateToSlowPath_IfInvalid(uint8_t Offset) {
const auto& [Valid, StackMember] = StackData.top(Offset); const auto& [Valid, StackMember] = StackData.top(Offset);
MigrateToSlowPathIf(Valid != StackSlot::VALID); MigrateToSlowPathIf(Valid != StackSlot::VALID);
+9 -4
View File
@@ -53,10 +53,15 @@ void* FEX_mmap(void* addr, size_t length, int prot, int flags, int fd, off_t off
} }
if (flags & MAP_ANONYMOUS) { if (flags & MAP_ANONYMOUS) {
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, Result, length, "FEXMem"); VirtualName("FEXMem", Result, length);
} }
return Result; return Result;
} }
void VirtualName(const char* Name, void* Ptr, size_t Size) {
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, Ptr, Size, Name);
}
int FEX_munmap(void* addr, size_t length) { int FEX_munmap(void* addr, size_t length) {
int Result = Alloc64->Munmap(addr, length); int Result = Alloc64->Munmap(addr, length);
@@ -88,7 +93,7 @@ void* DisableSBRKAllocations() {
// calls won't allocate any memory through that. // calls won't allocate any memory through that.
void* AlignedBRK = reinterpret_cast<void*>(FEXCore::AlignUp(reinterpret_cast<uintptr_t>(StartingSBRK), FEXCore::Utils::FEX_PAGE_SIZE)); void* AlignedBRK = reinterpret_cast<void*>(FEXCore::AlignUp(reinterpret_cast<uintptr_t>(StartingSBRK), FEXCore::Utils::FEX_PAGE_SIZE));
void* AfterBRK = void* AfterBRK =
mmap(AlignedBRK, FEXCore::Utils::FEX_PAGE_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE | MAP_NORESERVE, -1, 0); ::mmap(AlignedBRK, FEXCore::Utils::FEX_PAGE_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE | MAP_NORESERVE, -1, 0);
if (AfterBRK == INVALID_PTR) { if (AfterBRK == INVALID_PTR) {
// Couldn't allocate the page after the aligned brk? This should never happen. // Couldn't allocate the page after the aligned brk? This should never happen.
// FEXCore::LogMan isn't configured yet so we just need to print the message. // FEXCore::LogMan isn't configured yet so we just need to print the message.
@@ -289,7 +294,7 @@ fextl::vector<MemoryRegion> StealMemoryRegion(uintptr_t Begin, uintptr_t End) {
--StackRegionIt; --StackRegionIt;
auto Alloc = auto Alloc =
mmap(StackRegionIt->Ptr, StackRegionIt->Size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE | MAP_FIXED, -1, 0); ::mmap(StackRegionIt->Ptr, StackRegionIt->Size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE | MAP_FIXED, -1, 0);
LogMan::Throw::AFmt(Alloc != MAP_FAILED, "mmap({},{:x}) failed", fmt::ptr(StackRegionIt->Ptr), StackRegionIt->Size); LogMan::Throw::AFmt(Alloc != MAP_FAILED, "mmap({},{:x}) failed", fmt::ptr(StackRegionIt->Ptr), StackRegionIt->Size);
LogMan::Throw::AFmt(Alloc == StackRegionIt->Ptr, "mmap returned {} instead of {}", Alloc, fmt::ptr(StackRegionIt->Ptr)); LogMan::Throw::AFmt(Alloc == StackRegionIt->Ptr, "mmap returned {} instead of {}", Alloc, fmt::ptr(StackRegionIt->Ptr));
@@ -300,7 +305,7 @@ fextl::vector<MemoryRegion> StealMemoryRegion(uintptr_t Begin, uintptr_t End) {
// Block remaining memory gaps // Block remaining memory gaps
for (auto RegionIt = Regions.begin(); RegionIt != Regions.end(); ++RegionIt) { for (auto RegionIt = Regions.begin(); RegionIt != Regions.end(); ++RegionIt) {
auto Alloc = mmap(RegionIt->Ptr, RegionIt->Size, PROT_NONE, MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE | MAP_FIXED_NOREPLACE, -1, 0); auto Alloc = ::mmap(RegionIt->Ptr, RegionIt->Size, PROT_NONE, MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE | MAP_FIXED_NOREPLACE, -1, 0);
LogMan::Throw::AFmt(Alloc != MAP_FAILED, "mmap({},{:x}) failed", fmt::ptr(RegionIt->Ptr), RegionIt->Size); LogMan::Throw::AFmt(Alloc != MAP_FAILED, "mmap({},{:x}) failed", fmt::ptr(RegionIt->Ptr), RegionIt->Size);
LogMan::Throw::AFmt(Alloc == RegionIt->Ptr, "mmap returned {} instead of {}", Alloc, fmt::ptr(RegionIt->Ptr)); LogMan::Throw::AFmt(Alloc == RegionIt->Ptr, "mmap returned {} instead of {}", Alloc, fmt::ptr(RegionIt->Ptr));
@@ -98,7 +98,7 @@ private:
// Align UsedPages so it pads to the next page. // Align UsedPages so it pads to the next page.
// Necessary to take advantage of madvise zero page pooling. // Necessary to take advantage of madvise zero page pooling.
using FlexBitElementType = uint64_t; using FlexBitElementType = uint64_t;
alignas(4096) FEXCore::FlexBitSet<FlexBitElementType> UsedPages; alignas(FEXCore::Utils::FEX_PAGE_SIZE) FEXCore::FlexBitSet<FlexBitElementType> UsedPages;
// This returns the size of the LiveVMARegion in addition to the flex set that tracks the used data // This returns the size of the LiveVMARegion in addition to the flex set that tracks the used data
// The LiveVMARegion lives at the start of the VMA region which means on initialization we need to set that // The LiveVMARegion lives at the start of the VMA region which means on initialization we need to set that
@@ -140,7 +140,7 @@ private:
} }
}; };
static_assert(sizeof(LiveVMARegion) == 4096, "Needs to be the size of a page"); static_assert(sizeof(LiveVMARegion) == FEXCore::Utils::FEX_PAGE_SIZE, "Needs to be the size of a page");
static_assert(std::is_trivially_copyable<LiveVMARegion>::value, "Needs to be trivially copyable"); static_assert(std::is_trivially_copyable<LiveVMARegion>::value, "Needs to be trivially copyable");
static_assert(offsetof(LiveVMARegion, UsedPages) == sizeof(LiveVMARegion), "FlexBitSet needs to be at the end"); static_assert(offsetof(LiveVMARegion, UsedPages) == sizeof(LiveVMARegion), "FlexBitSet needs to be at the end");
@@ -168,6 +168,7 @@ private:
LOGMAN_THROW_A_FMT(Res != -1, "Couldn't mprotect region: {} '{}' Likely occurs when running out of memory or Maximum VMAs", errno, LOGMAN_THROW_A_FMT(Res != -1, "Couldn't mprotect region: {} '{}' Likely occurs when running out of memory or Maximum VMAs", errno,
strerror(errno)); strerror(errno));
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(ReservedRegion->Base), SizePlusManagedData);
LiveVMARegion* LiveRange = new (reinterpret_cast<void*>(ReservedRegion->Base)) LiveVMARegion(); LiveVMARegion* LiveRange = new (reinterpret_cast<void*>(ReservedRegion->Base)) LiveVMARegion();
// Copy over the reserved data // Copy over the reserved data
@@ -473,7 +474,7 @@ int OSAllocator_64Bit::Munmap(void* addr, size_t length) {
::mmap(addr, length, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); ::mmap(addr, length, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
} }
(*it)->FreeSpace += FreedPages * 4096; (*it)->FreeSpace += FreedPages * FEXCore::Utils::FEX_PAGE_SIZE;
// Set the last allocated page to the minimum of last page allocation or this slab // Set the last allocated page to the minimum of last page allocation or this slab
// This will let us more quickly fill holes // This will let us more quickly fill holes
@@ -505,6 +506,8 @@ void OSAllocator_64Bit::AllocateMemoryRegions(fextl::vector<FEXCore::Allocator::
// This enables the kernel to use transparent large pages in the allocator which can reduce memory pressure // This enables the kernel to use transparent large pages in the allocator which can reduce memory pressure
::madvise(it.Ptr, ObjectAllocSize, MADV_HUGEPAGE); ::madvise(it.Ptr, ObjectAllocSize, MADV_HUGEPAGE);
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(it.Ptr), ObjectAllocSize);
ObjectAlloc = new (it.Ptr) Alloc::ForwardOnlyIntrusiveArenaAllocator(it.Ptr, ObjectAllocSize); ObjectAlloc = new (it.Ptr) Alloc::ForwardOnlyIntrusiveArenaAllocator(it.Ptr, ObjectAllocSize);
ReservedRegions = ObjectAlloc->new_construct(ReservedRegions, ObjectAlloc); ReservedRegions = ObjectAlloc->new_construct(ReservedRegions, ObjectAlloc);
LiveRegions = ObjectAlloc->new_construct(LiveRegions, ObjectAlloc); LiveRegions = ObjectAlloc->new_construct(LiveRegions, ObjectAlloc);
@@ -602,6 +605,8 @@ fextl::unique_ptr<T> make_alloc_unique(FEXCore::Allocator::MemoryRegion& Base, A
ERROR_AND_DIE_FMT("Couldn't allocate memory region"); ERROR_AND_DIE_FMT("Couldn't allocate memory region");
} }
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(ptr), MinPage);
// Remove the page from the base region. // Remove the page from the base region.
// Could be zero after this. // Could be zero after this.
Base.Size -= MinPage; Base.Size -= MinPage;
+102 -122
View File
@@ -144,33 +144,33 @@ static __uint128_t LoadAcquire128(uint64_t Addr) {
} }
static uint64_t LoadAcquire64(uint64_t Addr) { static uint64_t LoadAcquire64(uint64_t Addr) {
std::atomic<uint64_t>* Atom = reinterpret_cast<std::atomic<uint64_t>*>(Addr); auto Atom = std::atomic_ref<uint64_t>(*reinterpret_cast<uint64_t*>(Addr));
return Atom->load(std::memory_order_acquire); return Atom.load(std::memory_order_acquire);
} }
static bool StoreCAS64(uint64_t& Expected, uint64_t Val, uint64_t Addr) { static bool StoreCAS64(uint64_t& Expected, uint64_t Val, uint64_t Addr) {
std::atomic<uint64_t>* Atom = reinterpret_cast<std::atomic<uint64_t>*>(Addr); auto Atom = std::atomic_ref<uint64_t>(*reinterpret_cast<uint64_t*>(Addr));
return Atom->compare_exchange_strong(Expected, Val); return Atom.compare_exchange_strong(Expected, Val);
} }
static uint32_t LoadAcquire32(uint64_t Addr) { static uint32_t LoadAcquire32(uint64_t Addr) {
std::atomic<uint32_t>* Atom = reinterpret_cast<std::atomic<uint32_t>*>(Addr); auto Atom = std::atomic_ref<uint32_t>(*reinterpret_cast<uint32_t*>(Addr));
return Atom->load(std::memory_order_acquire); return Atom.load(std::memory_order_acquire);
} }
static bool StoreCAS32(uint32_t& Expected, uint32_t Val, uint64_t Addr) { static bool StoreCAS32(uint32_t& Expected, uint32_t Val, uint64_t Addr) {
std::atomic<uint32_t>* Atom = reinterpret_cast<std::atomic<uint32_t>*>(Addr); auto Atom = std::atomic_ref<uint32_t>(*reinterpret_cast<uint32_t*>(Addr));
return Atom->compare_exchange_strong(Expected, Val); return Atom.compare_exchange_strong(Expected, Val);
} }
static uint8_t LoadAcquire8(uint64_t Addr) { static uint8_t LoadAcquire8(uint64_t Addr) {
std::atomic<uint8_t>* Atom = reinterpret_cast<std::atomic<uint8_t>*>(Addr); auto Atom = std::atomic_ref<uint8_t>(*reinterpret_cast<uint8_t*>(Addr));
return Atom->load(std::memory_order_acquire); return Atom.load(std::memory_order_acquire);
} }
static bool StoreCAS8(uint8_t& Expected, uint8_t Val, uint64_t Addr) { static bool StoreCAS8(uint8_t& Expected, uint8_t Val, uint64_t Addr) {
std::atomic<uint8_t>* Atom = reinterpret_cast<std::atomic<uint8_t>*>(Addr); auto Atom = std::atomic_ref<uint8_t>(*reinterpret_cast<uint8_t*>(Addr));
return Atom->compare_exchange_strong(Expected, Val); return Atom.compare_exchange_strong(Expected, Val);
} }
static uint16_t DoLoad16(uint64_t Addr) { static uint16_t DoLoad16(uint64_t Addr) {
@@ -211,8 +211,8 @@ static uint16_t DoLoad16(uint64_t Addr) {
uint64_t Alignment = Addr & AlignmentMask; uint64_t Alignment = Addr & AlignmentMask;
Addr &= ~AlignmentMask; Addr &= ~AlignmentMask;
std::atomic<uint64_t>* Atomic = reinterpret_cast<std::atomic<uint64_t>*>(Addr); auto Atomic = std::atomic_ref<uint64_t>(*reinterpret_cast<uint64_t*>(Addr));
uint64_t TmpResult = Atomic->load(); uint64_t TmpResult = Atomic.load();
// Zexts the result // Zexts the result
uint16_t Result = TmpResult >> (Alignment * 8); uint16_t Result = TmpResult >> (Alignment * 8);
@@ -224,8 +224,8 @@ static uint16_t DoLoad16(uint64_t Addr) {
uint64_t Alignment = Addr & AlignmentMask; uint64_t Alignment = Addr & AlignmentMask;
Addr &= ~AlignmentMask; Addr &= ~AlignmentMask;
std::atomic<uint32_t>* Atomic = reinterpret_cast<std::atomic<uint32_t>*>(Addr); auto Atomic = std::atomic_ref<uint32_t>(*reinterpret_cast<uint32_t*>(Addr));
uint32_t TmpResult = Atomic->load(); uint32_t TmpResult = Atomic.load();
// Zexts the result // Zexts the result
uint16_t Result = TmpResult >> (Alignment * 8); uint16_t Result = TmpResult >> (Alignment * 8);
@@ -272,8 +272,8 @@ static uint32_t DoLoad32(uint64_t Addr) {
uint64_t Alignment = Addr & AlignmentMask; uint64_t Alignment = Addr & AlignmentMask;
Addr &= ~AlignmentMask; Addr &= ~AlignmentMask;
std::atomic<uint64_t>* Atomic = reinterpret_cast<std::atomic<uint64_t>*>(Addr); auto Atomic = std::atomic_ref<uint64_t>(*reinterpret_cast<uint64_t*>(Addr));
uint64_t TmpResult = Atomic->load(); uint64_t TmpResult = Atomic.load();
return TmpResult >> (Alignment * 8); return TmpResult >> (Alignment * 8);
} }
@@ -465,7 +465,7 @@ static bool RunCASPAL(uint64_t* GPRs, uint32_t Size, uint32_t DesiredReg1, uint3
// Fits within a 16byte region // Fits within a 16byte region
uint64_t Alignment = Addr & 0b1111; uint64_t Alignment = Addr & 0b1111;
Addr &= ~0b1111ULL; Addr &= ~0b1111ULL;
std::atomic<__uint128_t>* Atomic128 = reinterpret_cast<std::atomic<__uint128_t>*>(Addr); auto Atomic128 = std::atomic_ref<__uint128_t>(*reinterpret_cast<__uint128_t*>(Addr));
__uint128_t Mask = ~0ULL; __uint128_t Mask = ~0ULL;
Mask <<= Alignment * 8; Mask <<= Alignment * 8;
@@ -480,7 +480,7 @@ static bool RunCASPAL(uint64_t* GPRs, uint32_t Size, uint32_t DesiredReg1, uint3
Expected <<= Alignment * 8; Expected <<= Alignment * 8;
while (1) { while (1) {
TmpExpected = Atomic128->load(); TmpExpected = Atomic128.load();
// Set up expected // Set up expected
TmpExpected &= NegMask; TmpExpected &= NegMask;
@@ -491,7 +491,7 @@ static bool RunCASPAL(uint64_t* GPRs, uint32_t Size, uint32_t DesiredReg1, uint3
TmpDesired &= NegMask; TmpDesired &= NegMask;
TmpDesired |= Desired; TmpDesired |= Desired;
bool CASResult = Atomic128->compare_exchange_strong(TmpExpected, TmpDesired); bool CASResult = Atomic128.compare_exchange_strong(TmpExpected, TmpDesired);
if (CASResult) { if (CASResult) {
// Successful, so we are done // Successful, so we are done
return true; return true;
@@ -617,36 +617,6 @@ static uint64_t HandleCASPAL_ARMv8(uint32_t Instr, uintptr_t ProgramCounter, uin
} }
} }
static bool HandleAtomicVectorStore(uint32_t Instr, uintptr_t ProgramCounter) {
uint32_t* PC = (uint32_t*)ProgramCounter;
uint32_t Size = (Instr >> 30) & 1;
uint32_t DataReg = Instr & 0x1F;
if (Size == 1) {
// 64-bit pair happens on paranoid vector stores
// [0] ldaxp(xzr, TMP3, MemSrc); // <- Can hit SIGBUS. Overwritten with DMB
// [1] stlxp(TMP3, TMP1, TMP2, MemSrc); // <- Can also hit SIGBUS
// [2] cbnz(TMP3, &B); // < Overwritten with DMB
if (DataReg == 31) {
uint32_t NextInstr = PC[1];
uint32_t AddrReg = (NextInstr >> 5) & 0x1F;
DataReg = NextInstr & 0x1F;
uint32_t DataReg2 = (NextInstr >> 10) & 0x1F;
uint32_t STP = (0b10 << 30) | (0b101001000000000 << 15) | (DataReg2 << 10) | (AddrReg << 5) | DataReg;
PC[0] = DMB;
PC[1] = STP;
PC[2] = DMB;
// Back up one instruction and have another go
ClearICache(&PC[0], 12);
return true;
}
}
return false;
}
template<typename T> template<typename T>
using CASExpectedFn = T (*)(T Src, T Expected); using CASExpectedFn = T (*)(T Src, T Expected);
template<typename T> template<typename T>
@@ -740,7 +710,7 @@ static uint16_t DoCAS16(uint16_t DesiredSrc, uint16_t ExpectedSrc, uint64_t Addr
// Fits within a 16byte region // Fits within a 16byte region
uint64_t Alignment = Addr & 0b1111; uint64_t Alignment = Addr & 0b1111;
Addr &= ~0b1111ULL; Addr &= ~0b1111ULL;
std::atomic<__uint128_t>* Atomic128 = reinterpret_cast<std::atomic<__uint128_t>*>(Addr); auto Atomic128 = std::atomic_ref<__uint128_t>(*reinterpret_cast<__uint128_t*>(Addr));
__uint128_t Mask = 0xFFFF; __uint128_t Mask = 0xFFFF;
Mask <<= Alignment * 8; Mask <<= Alignment * 8;
@@ -749,7 +719,7 @@ static uint16_t DoCAS16(uint16_t DesiredSrc, uint16_t ExpectedSrc, uint64_t Addr
__uint128_t TmpDesired {}; __uint128_t TmpDesired {};
while (1) { while (1) {
TmpExpected = Atomic128->load(); TmpExpected = Atomic128.load();
__uint128_t Desired = DesiredFunction(TmpExpected >> (Alignment * 8), DesiredSrc); __uint128_t Desired = DesiredFunction(TmpExpected >> (Alignment * 8), DesiredSrc);
Desired <<= Alignment * 8; Desired <<= Alignment * 8;
@@ -766,7 +736,7 @@ static uint16_t DoCAS16(uint16_t DesiredSrc, uint16_t ExpectedSrc, uint64_t Addr
TmpDesired &= NegMask; TmpDesired &= NegMask;
TmpDesired |= Desired; TmpDesired |= Desired;
bool CASResult = Atomic128->compare_exchange_strong(TmpExpected, TmpDesired); bool CASResult = Atomic128.compare_exchange_strong(TmpExpected, TmpDesired);
if (CASResult) { if (CASResult) {
// Successful, so we are done // Successful, so we are done
return Expected >> (Alignment * 8); return Expected >> (Alignment * 8);
@@ -810,9 +780,9 @@ static uint16_t DoCAS16(uint16_t DesiredSrc, uint16_t ExpectedSrc, uint64_t Addr
uint64_t TmpExpected {}; uint64_t TmpExpected {};
uint64_t TmpDesired {}; uint64_t TmpDesired {};
std::atomic<uint64_t>* Atomic = reinterpret_cast<std::atomic<uint64_t>*>(Addr); auto Atomic = std::atomic_ref<uint64_t>(*reinterpret_cast<uint64_t*>(Addr));
while (1) { while (1) {
TmpExpected = Atomic->load(); TmpExpected = Atomic.load();
uint64_t Desired = DesiredFunction(TmpExpected >> (Alignment * 8), DesiredSrc); uint64_t Desired = DesiredFunction(TmpExpected >> (Alignment * 8), DesiredSrc);
Desired <<= Alignment * 8; Desired <<= Alignment * 8;
@@ -829,7 +799,7 @@ static uint16_t DoCAS16(uint16_t DesiredSrc, uint16_t ExpectedSrc, uint64_t Addr
TmpDesired &= NegMask; TmpDesired &= NegMask;
TmpDesired |= Desired; TmpDesired |= Desired;
bool CASResult = Atomic->compare_exchange_strong(TmpExpected, TmpDesired); bool CASResult = Atomic.compare_exchange_strong(TmpExpected, TmpDesired);
if (CASResult) { if (CASResult) {
// Successful, so we are done // Successful, so we are done
return Expected >> (Alignment * 8); return Expected >> (Alignment * 8);
@@ -873,9 +843,9 @@ static uint16_t DoCAS16(uint16_t DesiredSrc, uint16_t ExpectedSrc, uint64_t Addr
uint32_t TmpExpected {}; uint32_t TmpExpected {};
uint32_t TmpDesired {}; uint32_t TmpDesired {};
std::atomic<uint32_t>* Atomic = reinterpret_cast<std::atomic<uint32_t>*>(Addr); auto Atomic = std::atomic_ref<uint32_t>(*reinterpret_cast<uint32_t*>(Addr));
while (1) { while (1) {
TmpExpected = Atomic->load(); TmpExpected = Atomic.load();
uint32_t Desired = DesiredFunction(TmpExpected >> (Alignment * 8), DesiredSrc); uint32_t Desired = DesiredFunction(TmpExpected >> (Alignment * 8), DesiredSrc);
@@ -893,7 +863,7 @@ static uint16_t DoCAS16(uint16_t DesiredSrc, uint16_t ExpectedSrc, uint64_t Addr
TmpDesired &= NegMask; TmpDesired &= NegMask;
TmpDesired |= Desired; TmpDesired |= Desired;
bool CASResult = Atomic->compare_exchange_strong(TmpExpected, TmpDesired); bool CASResult = Atomic.compare_exchange_strong(TmpExpected, TmpDesired);
if (CASResult) { if (CASResult) {
// Successful, so we are done // Successful, so we are done
return Expected >> (Alignment * 8); return Expected >> (Alignment * 8);
@@ -1040,7 +1010,7 @@ static uint32_t DoCAS32(uint32_t DesiredSrc, uint32_t ExpectedSrc, uint64_t Addr
// Fits within a 16byte region // Fits within a 16byte region
uint64_t Alignment = Addr & 0b1111; uint64_t Alignment = Addr & 0b1111;
Addr &= ~0b1111ULL; Addr &= ~0b1111ULL;
std::atomic<__uint128_t>* Atomic128 = reinterpret_cast<std::atomic<__uint128_t>*>(Addr); auto Atomic128 = std::atomic_ref<__uint128_t>(*reinterpret_cast<__uint128_t*>(Addr));
__uint128_t Mask = ~0U; __uint128_t Mask = ~0U;
Mask <<= Alignment * 8; Mask <<= Alignment * 8;
@@ -1049,7 +1019,7 @@ static uint32_t DoCAS32(uint32_t DesiredSrc, uint32_t ExpectedSrc, uint64_t Addr
__uint128_t TmpDesired {}; __uint128_t TmpDesired {};
while (1) { while (1) {
__uint128_t TmpActual = Atomic128->load(); __uint128_t TmpActual = Atomic128.load();
__uint128_t Desired = DesiredFunction(TmpActual >> (Alignment * 8), DesiredSrc); __uint128_t Desired = DesiredFunction(TmpActual >> (Alignment * 8), DesiredSrc);
__uint128_t Expected = ExpectedFunction(TmpActual >> (Alignment * 8), ExpectedSrc); __uint128_t Expected = ExpectedFunction(TmpActual >> (Alignment * 8), ExpectedSrc);
@@ -1064,7 +1034,7 @@ static uint32_t DoCAS32(uint32_t DesiredSrc, uint32_t ExpectedSrc, uint64_t Addr
TmpDesired &= NegMask; TmpDesired &= NegMask;
TmpDesired |= Desired << (Alignment * 8); TmpDesired |= Desired << (Alignment * 8);
bool CASResult = Atomic128->compare_exchange_strong(TmpExpected, TmpDesired); bool CASResult = Atomic128.compare_exchange_strong(TmpExpected, TmpDesired);
if (CASResult) { if (CASResult) {
// Stored successfully // Stored successfully
return Expected; return Expected;
@@ -1108,9 +1078,9 @@ static uint32_t DoCAS32(uint32_t DesiredSrc, uint32_t ExpectedSrc, uint64_t Addr
uint64_t TmpExpected {}; uint64_t TmpExpected {};
uint64_t TmpDesired {}; uint64_t TmpDesired {};
std::atomic<uint64_t>* Atomic = reinterpret_cast<std::atomic<uint64_t>*>(Addr); auto Atomic = std::atomic_ref<uint64_t>(*reinterpret_cast<uint64_t*>(Addr));
while (1) { while (1) {
uint64_t TmpActual = Atomic->load(); uint64_t TmpActual = Atomic.load();
uint64_t Desired = DesiredFunction(TmpActual >> (Alignment * 8), DesiredSrc); uint64_t Desired = DesiredFunction(TmpActual >> (Alignment * 8), DesiredSrc);
uint64_t Expected = ExpectedFunction(TmpActual >> (Alignment * 8), ExpectedSrc); uint64_t Expected = ExpectedFunction(TmpActual >> (Alignment * 8), ExpectedSrc);
@@ -1125,7 +1095,7 @@ static uint32_t DoCAS32(uint32_t DesiredSrc, uint32_t ExpectedSrc, uint64_t Addr
TmpDesired &= NegMask; TmpDesired &= NegMask;
TmpDesired |= Desired << (Alignment * 8); TmpDesired |= Desired << (Alignment * 8);
bool CASResult = Atomic->compare_exchange_strong(TmpExpected, TmpDesired); bool CASResult = Atomic.compare_exchange_strong(TmpExpected, TmpDesired);
if (CASResult) { if (CASResult) {
// Stored successfully // Stored successfully
return Expected; return Expected;
@@ -1270,7 +1240,7 @@ static uint64_t DoCAS64(uint64_t DesiredSrc, uint64_t ExpectedSrc, uint64_t Addr
// Fits within a 16byte region // Fits within a 16byte region
uint64_t Alignment = Addr & AlignmentMask; uint64_t Alignment = Addr & AlignmentMask;
Addr &= ~AlignmentMask; Addr &= ~AlignmentMask;
std::atomic<__uint128_t>* Atomic128 = reinterpret_cast<std::atomic<__uint128_t>*>(Addr); auto Atomic128 = std::atomic_ref<__uint128_t>(*reinterpret_cast<__uint128_t*>(Addr));
__uint128_t Mask = ~0ULL; __uint128_t Mask = ~0ULL;
Mask <<= Alignment * 8; Mask <<= Alignment * 8;
@@ -1279,7 +1249,7 @@ static uint64_t DoCAS64(uint64_t DesiredSrc, uint64_t ExpectedSrc, uint64_t Addr
__uint128_t TmpDesired {}; __uint128_t TmpDesired {};
while (1) { while (1) {
__uint128_t TmpActual = Atomic128->load(); __uint128_t TmpActual = Atomic128.load();
__uint128_t Desired = DesiredFunction(TmpActual >> (Alignment * 8), DesiredSrc); __uint128_t Desired = DesiredFunction(TmpActual >> (Alignment * 8), DesiredSrc);
__uint128_t Expected = ExpectedFunction(TmpActual >> (Alignment * 8), ExpectedSrc); __uint128_t Expected = ExpectedFunction(TmpActual >> (Alignment * 8), ExpectedSrc);
@@ -1294,7 +1264,7 @@ static uint64_t DoCAS64(uint64_t DesiredSrc, uint64_t ExpectedSrc, uint64_t Addr
TmpDesired &= NegMask; TmpDesired &= NegMask;
TmpDesired |= Desired << (Alignment * 8); TmpDesired |= Desired << (Alignment * 8);
bool CASResult = Atomic128->compare_exchange_strong(TmpExpected, TmpDesired); bool CASResult = Atomic128.compare_exchange_strong(TmpExpected, TmpDesired);
if (CASResult) { if (CASResult) {
// Stored successfully // Stored successfully
return Expected; return Expected;
@@ -1326,9 +1296,7 @@ static uint64_t DoCAS64(uint64_t DesiredSrc, uint64_t ExpectedSrc, uint64_t Addr
} }
} }
static bool RunCASAL(uint64_t* GPRs, uint32_t Size, uint32_t DesiredReg, uint32_t ExpectedReg, uint32_t AddressReg, uint32_t* StrictSplitLockMutex) { static std::optional<uint64_t> DoCAS(uint32_t Size, uint64_t Desired, uint64_t Expected, uint64_t Addr, uint32_t* StrictSplitLockMutex) {
uint64_t Addr = GPRs[AddressReg];
// Cross-cacheline CAS doesn't work on ARM // Cross-cacheline CAS doesn't work on ARM
// It isn't even guaranteed to work on x86 // It isn't even guaranteed to work on x86
// Intel will do a "split lock" which locks the full bus // Intel will do a "split lock" which locks the full bus
@@ -1341,7 +1309,7 @@ static bool RunCASAL(uint64_t* GPRs, uint32_t Size, uint32_t DesiredReg, uint32_
// Only need to handle 16, 32, 64 // Only need to handle 16, 32, 64
if (Size == 2) { if (Size == 2) {
auto Res = DoCAS16<false>( auto Res = DoCAS16<false>(
GPRs[DesiredReg], GPRs[ExpectedReg], Addr, Desired, Expected, Addr,
[](uint16_t, uint16_t Expected) -> uint16_t { [](uint16_t, uint16_t Expected) -> uint16_t {
// Expected is just Expected // Expected is just Expected
return Expected; return Expected;
@@ -1351,16 +1319,10 @@ static bool RunCASAL(uint64_t* GPRs, uint32_t Size, uint32_t DesiredReg, uint32_
return Desired; return Desired;
}, },
StrictSplitLockMutex); StrictSplitLockMutex);
return Res;
// Regardless of pass or fail
// We set the result register if it isn't a zero register
if (ExpectedReg != 31) {
GPRs[ExpectedReg] = Res;
}
return true;
} else if (Size == 4) { } else if (Size == 4) {
auto Res = DoCAS32<false>( auto Res = DoCAS32<false>(
GPRs[DesiredReg], GPRs[ExpectedReg], Addr, Desired, Expected, Addr,
[](uint32_t, uint32_t Expected) -> uint32_t { [](uint32_t, uint32_t Expected) -> uint32_t {
// Expected is just Expected // Expected is just Expected
return Expected; return Expected;
@@ -1370,16 +1332,10 @@ static bool RunCASAL(uint64_t* GPRs, uint32_t Size, uint32_t DesiredReg, uint32_
return Desired; return Desired;
}, },
StrictSplitLockMutex); StrictSplitLockMutex);
return Res;
// Regardless of pass or fail
// We set the result register if it isn't a zero register
if (ExpectedReg != 31) {
GPRs[ExpectedReg] = Res;
}
return true;
} else if (Size == 8) { } else if (Size == 8) {
auto Res = DoCAS64<false>( auto Res = DoCAS64<false>(
GPRs[DesiredReg], GPRs[ExpectedReg], Addr, Desired, Expected, Addr,
[](uint64_t, uint64_t Expected) -> uint64_t { [](uint64_t, uint64_t Expected) -> uint64_t {
// Expected is just Expected // Expected is just Expected
return Expected; return Expected;
@@ -1389,16 +1345,24 @@ static bool RunCASAL(uint64_t* GPRs, uint32_t Size, uint32_t DesiredReg, uint32_
return Desired; return Desired;
}, },
StrictSplitLockMutex); StrictSplitLockMutex);
return Res;
// Regardless of pass or fail
// We set the result register if it isn't a zero register
if (ExpectedReg != 31) {
GPRs[ExpectedReg] = Res;
}
return true;
} }
return false; return std::nullopt;
}
static bool RunCASAL(uint64_t* GPRs, uint32_t Size, uint32_t DesiredReg, uint32_t ExpectedReg, uint32_t AddressReg, uint32_t* StrictSplitLockMutex) {
std::optional<uint64_t> Res = DoCAS(Size, GPRs[DesiredReg], GPRs[ExpectedReg], GPRs[AddressReg], StrictSplitLockMutex);
if (!Res.has_value()) {
return false;
}
// Regardless of pass or fail
// We set the result register if it isn't a zero register
if (ExpectedReg != 31) {
GPRs[ExpectedReg] = *Res;
}
return true;
} }
static bool HandleCASAL(uint64_t* GPRs, uint32_t Instr, uint32_t* StrictSplitLockMutex) { static bool HandleCASAL(uint64_t* GPRs, uint32_t Instr, uint32_t* StrictSplitLockMutex) {
@@ -1560,38 +1524,43 @@ static bool HandleAtomicMemOp(uint32_t Instr, uint64_t* GPRs, uint32_t* StrictSp
return false; return false;
} }
static bool HandleAtomicLoad(uint32_t Instr, uint64_t* GPRs, int64_t Offset) { static bool HandleAtomicLoad(uint32_t Instr, uint64_t* GPRs, int64_t Offset, Core::UnalignedExclusiveStore* Store = nullptr) {
uint32_t Size = 1 << (Instr >> 30); uint32_t Size = 1 << (Instr >> 30);
uint32_t ResultReg = Instr & 0b11111; uint32_t ResultReg = Instr & 0b11111;
uint32_t AddressReg = (Instr >> 5) & 0b11111; uint32_t AddressReg = (Instr >> 5) & 0b11111;
uint64_t Addr = GPRs[AddressReg] + Offset; uint64_t Addr = GPRs[AddressReg] + Offset;
uint64_t Res;
if (Size == 2) { if (Size == 2) {
auto Res = DoLoad16(Addr); Res = DoLoad16(Addr);
// We set the result register if it isn't a zero register // We set the result register if it isn't a zero register
if (ResultReg != 31) { if (ResultReg != 31) {
GPRs[ResultReg] = Res; GPRs[ResultReg] = Res;
} }
return true;
} else if (Size == 4) { } else if (Size == 4) {
auto Res = DoLoad32(Addr); Res = DoLoad32(Addr);
// We set the result register if it isn't a zero register // We set the result register if it isn't a zero register
if (ResultReg != 31) { if (ResultReg != 31) {
GPRs[ResultReg] = Res; GPRs[ResultReg] = Res;
} }
return true;
} else if (Size == 8) { } else if (Size == 8) {
auto Res = DoLoad64(Addr); Res = DoLoad64(Addr);
// We set the result register if it isn't a zero register // We set the result register if it isn't a zero register
if (ResultReg != 31) { if (ResultReg != 31) {
GPRs[ResultReg] = Res; GPRs[ResultReg] = Res;
} }
return true; } else {
return false;
} }
return false; if (Store) {
Store->Addr = Addr;
Store->Store = Res;
Store->Size = Size;
}
return true;
} }
static bool HandleAtomicStore(uint32_t Instr, uint64_t* GPRs, int64_t Offset, uint32_t* StrictSplitLockMutex) { static bool HandleAtomicStore(uint32_t Instr, uint64_t* GPRs, int64_t Offset, uint32_t* StrictSplitLockMutex) {
@@ -1952,8 +1921,8 @@ static uint64_t HandleAtomicLoadstoreExclusive(uintptr_t ProgramCounter, uint64_
} }
[[nodiscard]] [[nodiscard]]
std::optional<int32_t> std::optional<int32_t> HandleUnalignedAccess(FEXCore::Core::InternalThreadState* Thread, UnalignedHandlerType HandleType,
HandleUnalignedAccess(FEXCore::Core::InternalThreadState* Thread, UnalignedHandlerType HandleType, uintptr_t ProgramCounter, uint64_t* GPRs) { uintptr_t ProgramCounter, uint64_t* GPRs, bool IsJIT) {
#ifdef _M_ARM_64 #ifdef _M_ARM_64
constexpr bool is_arm64 = true; constexpr bool is_arm64 = true;
#else #else
@@ -1977,8 +1946,7 @@ HandleUnalignedAccess(FEXCore::Core::InternalThreadState* Thread, UnalignedHandl
auto CTX = static_cast<Context::ContextImpl*>(Thread->CTX); auto CTX = static_cast<Context::ContextImpl*>(Thread->CTX);
uint32_t* StrictSplitLockMutex {CTX->Config.StrictInProcessSplitLocks ? &CTX->StrictSplitLockMutex : nullptr}; uint32_t* StrictSplitLockMutex {CTX->Config.StrictInProcessSplitLocks ? &CTX->StrictSplitLockMutex : nullptr};
// ParanoidTSO path doesn't modify any code. if (!IsJIT) [[unlikely]] {
if (HandleType == UnalignedHandlerType::Paranoid) [[unlikely]] {
if ((Instr & LDAXR_MASK) == LDAR_INST || // LDAR* if ((Instr & LDAXR_MASK) == LDAR_INST || // LDAR*
(Instr & LDAXR_MASK) == LDAPR_INST) { // LDAPR* (Instr & LDAXR_MASK) == LDAPR_INST) { // LDAPR*
if (ArchHelpers::Arm64::HandleAtomicLoad(Instr, GPRs, 0)) { if (ArchHelpers::Arm64::HandleAtomicLoad(Instr, GPRs, 0)) {
@@ -2016,7 +1984,29 @@ HandleUnalignedAccess(FEXCore::Core::InternalThreadState* Thread, UnalignedHandl
LogMan::Msg::EFmt("Unhandled JIT SIGBUS LDLUR*: PC: 0x{:x} Instruction: 0x{:08x}\n", ProgramCounter, PC[0]); LogMan::Msg::EFmt("Unhandled JIT SIGBUS LDLUR*: PC: 0x{:x} Instruction: 0x{:08x}\n", ProgramCounter, PC[0]);
return std::nullopt; return std::nullopt;
} }
} else if ((Instr & ArchHelpers::Arm64::LDAXR_MASK) == ArchHelpers::Arm64::LDAXR_INST) { // LDAXR*
if (ArchHelpers::Arm64::HandleAtomicLoad(Instr, GPRs, 0, &Thread->ExclusiveStore)) {
return 4;
}
} else if ((Instr & ArchHelpers::Arm64::STLXR_MASK) == ArchHelpers::Arm64::STLXR_INST) { // STLXR*
uint32_t StatusReg = Instr << 11 >> 27;
// // Emulate exclusive store by validating the address and value against the last unaligned LDAXR*.
if (GPRs[AddrReg] != Thread->ExclusiveStore.Addr || Size > Thread->ExclusiveStore.Size) {
if (StatusReg != 31) {
GPRs[StatusReg] = 1;
}
return 4;
}
if (std::optional<uint64_t> Prev =
DoCAS(Size, DataReg == 31 ? 0 : GPRs[DataReg], Thread->ExclusiveStore.Store, GPRs[AddrReg], StrictSplitLockMutex)) {
if (StatusReg != 31) {
GPRs[StatusReg] = !!memcmp(&Thread->ExclusiveStore.Store, &*Prev, Size);
}
Thread->ExclusiveStore.Size = 0;
return 4;
}
} }
return 0;
} }
const auto Frame = Thread->CurrentFrame; const auto Frame = Thread->CurrentFrame;
@@ -2068,6 +2058,9 @@ HandleUnalignedAccess(FEXCore::Core::InternalThreadState* Thread, UnalignedHandl
if (BytesToSkip) { if (BytesToSkip) {
// Skip this instruction now // Skip this instruction now
return BytesToSkip; return BytesToSkip;
} else {
LogMan::Msg::EFmt("Unhandled JIT SIGBUS CASPAL: PC: 0x{:x} Instruction: 0x{:08x}\n", ProgramCounter, PC[0]);
return std::nullopt;
} }
} }
@@ -2131,19 +2124,6 @@ HandleUnalignedAccess(FEXCore::Core::InternalThreadState* Thread, UnalignedHandl
ClearICache(&PC[-1], 8); ClearICache(&PC[-1], 8);
// Back up one instruction and have another go // Back up one instruction and have another go
return -4; return -4;
} else if ((Instr & ArchHelpers::Arm64::LDAXP_MASK) == ArchHelpers::Arm64::LDAXP_INST) { // LDAXP
/// This is handling the case of paranoid ARMv8.0-a atomic stores.
/// This backpatches the ldaxp+stlxp+cbnz if the previous `HandleCASPAL_ARMv8` didn't handle the case.
if (ArchHelpers::Arm64::HandleAtomicVectorStore(Instr, ProgramCounter)) {
return 0;
} else {
LogMan::Msg::EFmt("Unhandled JIT SIGBUS LDAXP: PC: 0x{:x} Instruction: 0x{:08x}\n", ProgramCounter, PC[0]);
return std::nullopt;
}
} else if ((Instr & ArchHelpers::Arm64::STLXP_MASK) == ArchHelpers::Arm64::STLXP_INST) { // STLXP
// Should not trigger - middle of an LDAXP/STAXP pair.
LogMan::Msg::EFmt("Unhandled JIT SIGBUS STLXP: PC: 0x{:x} Instruction: 0x{:08x}\n", ProgramCounter, PC[0]);
return std::nullopt;
} }
// Check if another thread backpatched this instruction before this thread got here // Check if another thread backpatched this instruction before this thread got here
+119
View File
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT
#include <FEXCore/Utils/LongJump.h>
namespace FEXCore::LongJump {
#if defined(_M_ARM_64)
[[nodiscard]]
FEX_DEFAULT_VISIBILITY FEX_NAKED uint64_t SetJump(JumpBuf& Buffer) {
__asm volatile(R"(
// x0 contains the jumpbuffer
stp x19, x20, [x0, #( 0 * 8)];
stp x21, x22, [x0, #( 2 * 8)];
stp x23, x24, [x0, #( 4 * 8)];
stp x25, x26, [x0, #( 6 * 8)];
stp x27, x28, [x0, #( 8 * 8)];
stp x29, x30, [x0, #(10 * 8)];
// FPRs
stp d8, d9, [x0, #(12 * 8)];
stp d10, d11, [x0, #(14 * 8)];
stp d12, d13, [x0, #(16 * 8)];
stp d14, d15, [x0, #(18 * 8)];
// Move SP in to a temporary to store.
mov x1, sp;
str x1, [x0, #(20 * 8)];
// Return zero to signify this is the SetJump.
mov x0, #0;
ret;
)" ::
: "memory");
}
[[noreturn]]
FEX_DEFAULT_VISIBILITY FEX_NAKED void LongJump(JumpBuf& Buffer, uint64_t Value) {
__asm volatile(R"(
// x0 contains the jumpbuffer
ldp x19, x20, [x0, #( 0 * 8)];
ldp x21, x22, [x0, #( 2 * 8)];
ldp x23, x24, [x0, #( 4 * 8)];
ldp x25, x26, [x0, #( 6 * 8)];
ldp x27, x28, [x0, #( 8 * 8)];
ldp x29, x30, [x0, #(10 * 8)];
// FPRs
ldp d8, d9, [x0, #(12 * 8)];
ldp d10, d11, [x0, #(14 * 8)];
ldp d12, d13, [x0, #(16 * 8)];
ldp d14, d15, [x0, #(18 * 8)];
// Load SP in to temporary then move
ldr x0, [x0, #(20 * 8)];
mov sp, x0;
// Move value in to result register
mov x0, x1;
ret;
)" ::
: "memory");
}
#else
[[nodiscard]]
FEX_DEFAULT_VISIBILITY FEX_NAKED uint64_t SetJump(JumpBuf& Buffer) {
__asm volatile(R"(
.intel_syntax noprefix;
// rdi contains the jumpbuffer
mov [rdi + (0 * 8)], rbx;
mov [rdi + (1 * 8)], rsp;
mov [rdi + (2 * 8)], rbp;
mov [rdi + (3 * 8)], r12;
mov [rdi + (4 * 8)], r13;
mov [rdi + (5 * 8)], r14;
mov [rdi + (6 * 8)], r15;
// Return address is on the stack, load it and store
mov rsi, [rsp];
mov [rdi + (7 * 8)], rsi;
// Return zero to signify this is the SetJump.
mov rax, 0;
ret;
.att_syntax prefix;
)" ::
: "memory");
}
[[noreturn]]
FEX_DEFAULT_VISIBILITY FEX_NAKED void LongJump(JumpBuf& Buffer, uint64_t Value) {
__asm volatile(R"(
.intel_syntax noprefix;
// rdi contains the jumpbuffer
mov rbx, [rdi + (0 * 8)];
mov rsp, [rdi + (1 * 8)];
mov rbp, [rdi + (2 * 8)];
mov r12, [rdi + (3 * 8)];
mov r13, [rdi + (4 * 8)];
mov r14, [rdi + (5 * 8)];
mov r15, [rdi + (6 * 8)];
// Move value in to result register
mov rax, rsi;
// Pop the dead return address off the stack
pop rsi;
// Load the original return address from the jumpbuffer
mov rsi, [rdi + (7 * 8)];
// Return using a jump
jmp rsi;
.att_syntax prefix;
)" ::
: "memory");
}
#endif
} // namespace FEXCore::LongJump
+5
View File
@@ -9,6 +9,7 @@
#include <FEXCore/Utils/LogManager.h> #include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/Profiler.h> #include <FEXCore/Utils/Profiler.h>
#include <FEXCore/Config/Config.h>
#include <FEXCore/fextl/fmt.h> #include <FEXCore/fextl/fmt.h>
#include <FEXCore/fextl/string.h> #include <FEXCore/fextl/string.h>
@@ -70,6 +71,10 @@ static std::array<const char*, 2> TraceFSDirectories {
}; };
void Init() { void Init() {
FEX_CONFIG_OPT(EnableGpuvisProfiling, ENABLEGPUVISPROFILING);
if (!EnableGpuvisProfiling()) {
return;
}
for (auto Path : TraceFSDirectories) { for (auto Path : TraceFSDirectories) {
#ifdef _WIN32 #ifdef _WIN32
constexpr auto flags = O_WRONLY; constexpr auto flags = O_WRONLY;
+17 -17
View File
@@ -125,8 +125,8 @@ static inline uint64_t WFELoadAtomic(uint64_t* Futex) {
template<typename T, typename TT = T> template<typename T, typename TT = T>
static inline void Wait(T* Futex, TT ExpectedValue) { static inline void Wait(T* Futex, TT ExpectedValue) {
std::atomic<T>* AtomicFutex = reinterpret_cast<std::atomic<T>*>(Futex); auto AtomicFutex = std::atomic_ref<T>(*Futex);
T Result = AtomicFutex->load(); T Result = AtomicFutex.load();
// Early exit if possible. // Early exit if possible.
if (Result == ExpectedValue) { if (Result == ExpectedValue) {
@@ -149,9 +149,9 @@ template void Wait<uint64_t>(uint64_t*, uint64_t);
template<typename T, typename TT> template<typename T, typename TT>
static inline bool Wait(T* Futex, TT ExpectedValue, const std::chrono::nanoseconds& Timeout) { static inline bool Wait(T* Futex, TT ExpectedValue, const std::chrono::nanoseconds& Timeout) {
std::atomic<T>* AtomicFutex = reinterpret_cast<std::atomic<T>*>(Futex); auto AtomicFutex = std::atomic_ref<T>(*Futex);
T Result = AtomicFutex->load(); T Result = AtomicFutex.load();
// Early exit if possible. // Early exit if possible.
if (Result == ExpectedValue) { if (Result == ExpectedValue) {
@@ -187,8 +187,8 @@ template bool Wait<uint64_t>(uint64_t*, uint64_t, const std::chrono::nanoseconds
#else #else
template<typename T, typename TT> template<typename T, typename TT>
static inline void Wait(T* Futex, TT ExpectedValue) { static inline void Wait(T* Futex, TT ExpectedValue) {
std::atomic<T>* AtomicFutex = reinterpret_cast<std::atomic<T>*>(Futex); auto AtomicFutex = std::atomic_ref<T>(*Futex);
T Result = AtomicFutex->load(); T Result = AtomicFutex.load();
// Early exit if possible. // Early exit if possible.
if (Result == ExpectedValue) { if (Result == ExpectedValue) {
@@ -196,15 +196,15 @@ static inline void Wait(T* Futex, TT ExpectedValue) {
} }
do { do {
Result = AtomicFutex->load(); Result = AtomicFutex.load();
} while (Result != ExpectedValue); } while (Result != ExpectedValue);
} }
template<typename T, typename TT> template<typename T, typename TT>
static inline bool Wait(T* Futex, TT ExpectedValue, const std::chrono::nanoseconds& Timeout) { static inline bool Wait(T* Futex, TT ExpectedValue, const std::chrono::nanoseconds& Timeout) {
std::atomic<T>* AtomicFutex = reinterpret_cast<std::atomic<T>*>(Futex); auto AtomicFutex = std::atomic_ref<T>(*Futex);
T Result = AtomicFutex->load(); T Result = AtomicFutex.load();
// Early exit if possible. // Early exit if possible.
if (Result == ExpectedValue) { if (Result == ExpectedValue) {
@@ -214,7 +214,7 @@ static inline bool Wait(T* Futex, TT ExpectedValue, const std::chrono::nanosecon
const auto Begin = std::chrono::high_resolution_clock::now(); const auto Begin = std::chrono::high_resolution_clock::now();
do { do {
Result = AtomicFutex->load(); Result = AtomicFutex.load();
const auto CurrentCycleCounter = std::chrono::high_resolution_clock::now(); const auto CurrentCycleCounter = std::chrono::high_resolution_clock::now();
if ((CurrentCycleCounter - Begin) >= Timeout) { if ((CurrentCycleCounter - Begin) >= Timeout) {
@@ -230,12 +230,12 @@ static inline bool Wait(T* Futex, TT ExpectedValue, const std::chrono::nanosecon
template<typename T> template<typename T>
static inline void lock(T* Futex) { static inline void lock(T* Futex) {
std::atomic<T>* AtomicFutex = reinterpret_cast<std::atomic<T>*>(Futex); auto AtomicFutex = std::atomic_ref<T>(*Futex);
T Expected {}; T Expected {};
T Desired {1}; T Desired {1};
// Try to CAS immediately. // Try to CAS immediately.
if (AtomicFutex->compare_exchange_strong(Expected, Desired)) { if (AtomicFutex.compare_exchange_strong(Expected, Desired)) {
return; return;
} }
@@ -243,17 +243,17 @@ static inline void lock(T* Futex) {
// Wait until the futex is unlocked. // Wait until the futex is unlocked.
Wait(Futex, 0); Wait(Futex, 0);
Expected = 0; Expected = 0;
} while (!AtomicFutex->compare_exchange_strong(Expected, Desired)); } while (!AtomicFutex.compare_exchange_strong(Expected, Desired));
} }
template<typename T> template<typename T>
static inline bool try_lock(T* Futex) { static inline bool try_lock(T* Futex) {
std::atomic<T>* AtomicFutex = reinterpret_cast<std::atomic<T>*>(Futex); auto AtomicFutex = std::atomic_ref<T>(*Futex);
T Expected {}; T Expected {};
T Desired {1}; T Desired {1};
// Try to CAS immediately. // Try to CAS immediately.
if (AtomicFutex->compare_exchange_strong(Expected, Desired)) { if (AtomicFutex.compare_exchange_strong(Expected, Desired)) {
return true; return true;
} }
@@ -262,8 +262,8 @@ static inline bool try_lock(T* Futex) {
template<typename T> template<typename T>
static inline void unlock(T* Futex) { static inline void unlock(T* Futex) {
std::atomic<T>* AtomicFutex = reinterpret_cast<std::atomic<T>*>(Futex); auto AtomicFutex = std::atomic_ref<T>(*Futex);
AtomicFutex->store(0); AtomicFutex.store(0);
} }
#undef SPINLOOP_8BIT #undef SPINLOOP_8BIT
+3 -5
View File
@@ -42,9 +42,6 @@ enum OperatingMode {
using CodeRangeInvalidationFn = std::function<void(uint64_t start, uint64_t Length)>; using CodeRangeInvalidationFn = std::function<void(uint64_t start, uint64_t Length)>;
// Nested vector of guest block entrypoints
using InvalidatedEntryAccumulator = fextl::vector<fextl::vector<uint64_t>>;
using CustomIREntrypointHandler = std::function<void(uintptr_t Entrypoint, IR::IREmitter*)>; using CustomIREntrypointHandler = std::function<void(uintptr_t Entrypoint, IR::IREmitter*)>;
using ExitHandler = std::function<void(Core::InternalThreadState* Thread)>; using ExitHandler = std::function<void(Core::InternalThreadState* Thread)>;
@@ -141,8 +138,9 @@ public:
virtual AbstractCodeCache& GetCodeCache() = 0; virtual AbstractCodeCache& GetCodeCache() = 0;
FEX_DEFAULT_VISIBILITY virtual void ClearCodeCache(FEXCore::Core::InternalThreadState* Thread, bool NewCodeBuffer = true) = 0; FEX_DEFAULT_VISIBILITY virtual void ClearCodeCache(FEXCore::Core::InternalThreadState* Thread, bool NewCodeBuffer = true) = 0;
FEX_DEFAULT_VISIBILITY virtual void InvalidateGuestCodeRange( FEX_DEFAULT_VISIBILITY virtual void InvalidateCodeBuffersCodeRange(uint64_t Start, uint64_t Length) = 0;
FEXCore::Core::InternalThreadState* Thread, InvalidatedEntryAccumulator& Accumulator, uint64_t Start, uint64_t Length) = 0; FEX_DEFAULT_VISIBILITY virtual void
InvalidateThreadCachedCodeRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) = 0;
FEX_DEFAULT_VISIBILITY virtual FEXCore::ForkableSharedMutex& GetCodeInvalidationMutex() = 0; FEX_DEFAULT_VISIBILITY virtual FEXCore::ForkableSharedMutex& GetCodeInvalidationMutex() = 0;
FEX_DEFAULT_VISIBILITY virtual void FEX_DEFAULT_VISIBILITY virtual void
+6 -3
View File
@@ -104,6 +104,10 @@ struct CPUState {
uint64_t avx_high[16][2]; uint64_t avx_high[16][2];
uint64_t gregs[16] {}; uint64_t gregs[16] {};
uint64_t L1Pointer {};
uint64_t L1Mask {};
uint64_t callret_sp {};
uint64_t _pad1 {};
XMMRegs xmm {}; XMMRegs xmm {};
// Raw segment register indexes // Raw segment register indexes
@@ -116,8 +120,6 @@ struct CPUState {
uint64_t gs_cached {}; uint64_t gs_cached {};
uint64_t fs_cached {}; uint64_t fs_cached {};
uint8_t flags[48] {}; uint8_t flags[48] {};
uint64_t callret_sp {};
uint64_t _pad1 {};
uint64_t mm[8][2] {}; uint64_t mm[8][2] {};
// 32bit x86 state // 32bit x86 state
@@ -247,6 +249,8 @@ static_assert(offsetof(CPUState, xmm) % 32 == 0, "xmm needs to be 256-bit aligne
static_assert(offsetof(CPUState, mm) % 16 == 0, "mm needs to be 128-bit aligned!"); static_assert(offsetof(CPUState, mm) % 16 == 0, "mm needs to be 128-bit aligned!");
static_assert(offsetof(CPUState, gregs[15]) <= 504, "gregs maximum offset must be <= 504 for ldp/stp to work"); static_assert(offsetof(CPUState, gregs[15]) <= 504, "gregs maximum offset must be <= 504 for ldp/stp to work");
static_assert(offsetof(CPUState, DeferredSignalRefCount) % 8 == 0, "Needs to be 8-byte aligned"); static_assert(offsetof(CPUState, DeferredSignalRefCount) % 8 == 0, "Needs to be 8-byte aligned");
static_assert(offsetof(CPUState, L1Pointer) <= 504, "This needs to be <= 504 for ldp");
static_assert(offsetof(CPUState, L1Mask) == (offsetof(CPUState, L1Pointer) + 8), "These two variables are paired");
struct InternalThreadState; struct InternalThreadState;
@@ -355,7 +359,6 @@ struct JITPointers {
uint64_t GuestSignal_SIGSEGV {}; uint64_t GuestSignal_SIGSEGV {};
uint64_t SignalReturnHandler {}; uint64_t SignalReturnHandler {};
uint64_t SignalReturnHandlerRT {}; uint64_t SignalReturnHandlerRT {};
uint64_t L1Pointer {};
uint64_t L2Pointer {}; uint64_t L2Pointer {};
/** @} */ /** @} */
@@ -80,7 +80,14 @@ private:
static_assert(!std::is_move_constructible_v<NonMovableUniquePtr<int>>); static_assert(!std::is_move_constructible_v<NonMovableUniquePtr<int>>);
static_assert(!std::is_move_assignable_v<NonMovableUniquePtr<int>>); static_assert(!std::is_move_assignable_v<NonMovableUniquePtr<int>>);
struct InternalThreadState : public FEXCore::Allocator::FEXAllocOperators { // Store used for unaligned LDAXR*/STLXR* emulation.
struct UnalignedExclusiveStore {
uint64_t Addr;
uint64_t Store;
uint8_t Size;
};
struct alignas(FEXCore::Utils::FEX_PAGE_SIZE) InternalThreadState : public FEXCore::Allocator::FEXAllocOperators {
FEXCore::Core::CpuStateFrame* const CurrentFrame = &BaseFrameState; FEXCore::Core::CpuStateFrame* const CurrentFrame = &BaseFrameState;
FEXCore::Context::Context* const CTX; FEXCore::Context::Context* const CTX;
@@ -101,6 +108,8 @@ struct InternalThreadState : public FEXCore::Allocator::FEXAllocOperators {
// This pointer is owned by the frontend. // This pointer is owned by the frontend.
FEXCore::SHMStats::ThreadStats* ThreadStats {}; FEXCore::SHMStats::ThreadStats* ThreadStats {};
UnalignedExclusiveStore ExclusiveStore;
///< Data pointer for exclusive use by the frontend ///< Data pointer for exclusive use by the frontend
void* FrontendPtr; void* FrontendPtr;
@@ -116,8 +125,9 @@ struct InternalThreadState : public FEXCore::Allocator::FEXAllocOperators {
alignas(FEXCore::Utils::FEX_PAGE_SIZE) uint8_t InterruptFaultPage[FEXCore::Utils::FEX_PAGE_SIZE]; alignas(FEXCore::Utils::FEX_PAGE_SIZE) uint8_t InterruptFaultPage[FEXCore::Utils::FEX_PAGE_SIZE];
}; };
static_assert(std::is_standard_layout_v<FEXCore::Core::InternalThreadState>); static_assert(std::is_standard_layout_v<FEXCore::Core::InternalThreadState>);
static_assert( static_assert((offsetof(FEXCore::Core::InternalThreadState, InterruptFaultPage) - offsetof(FEXCore::Core::InternalThreadState, BaseFrameState)) <
(offsetof(FEXCore::Core::InternalThreadState, InterruptFaultPage) - offsetof(FEXCore::Core::InternalThreadState, BaseFrameState)) < 4096, FEXCore::Utils::FEX_PAGE_SIZE,
"Fault page is outside of immediate range from CPU state"); "Fault page is outside of immediate range from CPU state");
static_assert(sizeof(FEXCore::Core::InternalThreadState) == (FEXCore::Utils::FEX_PAGE_SIZE * 2));
} // namespace FEXCore::Core } // namespace FEXCore::Core
@@ -54,10 +54,6 @@ public:
virtual ~SyscallHandler() = default; virtual ~SyscallHandler() = default;
virtual uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) = 0; virtual uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) = 0;
virtual SyscallABI GetSyscallABI(uint64_t Syscall) = 0;
virtual FEXCore::IR::SyscallFlags GetSyscallFlags(uint64_t Syscall) const {
return FEXCore::IR::SyscallFlags::DEFAULT;
}
SyscallOSABI GetOSABI() const { SyscallOSABI GetOSABI() const {
return OSABI; return OSABI;
-20
View File
@@ -8,26 +8,6 @@
namespace FEXCore::IR { namespace FEXCore::IR {
enum class SyscallFlags : uint8_t {
DEFAULT = 0,
// Syscalldoesn't care about CPUState being serialized up to the syscall instruction.
// Means dead code elimination can optimize through a syscall operation.
OPTIMIZETHROUGH = 1 << 0,
// Syscall only reads the passed in arguments. Doesn't read CPUState.
NOSYNCSTATEONENTRY = 1 << 1,
// Syscall doesn't return. Code generation after syscall return can be removed.
NORETURN = 1 << 2,
// Syscall doesn't have any side-effects, so if the result isn't used then it can be removed.
NOSIDEEFFECTS = 1 << 3,
// Syscall doesn't return a result.
// Means the resulting register shouldn't be written (Usually RAX).
// Usually used with !NOSYNCSTATEONENTRY, so the syscall can modify CPU state entirely.
// Then on return FEXCore picks up the new state.
NORETURNEDRESULT = 1 << 4,
};
FEX_DEF_NUM_OPS(SyscallFlags)
// This enum of named vector constants are linked to an array in CPUBackend.cpp. // This enum of named vector constants are linked to an array in CPUBackend.cpp.
// This is used with the IROp `LoadNamedVectorConstant` to load a vector constant // This is used with the IROp `LoadNamedVectorConstant` to load a vector constant
// that would otherwise be costly to materialize. // that would otherwise be costly to materialize.
@@ -82,12 +82,15 @@ inline bool VirtualProtect(void* Ptr, size_t Size, ProtectOptions options) {
return ::VirtualProtect(Ptr, Size, prot, nullptr) == 0; return ::VirtualProtect(Ptr, Size, prot, nullptr) == 0;
} }
inline void VirtualName(const char*, void*, size_t) {}
#else #else
using MMAP_Hook = void* (*)(void*, size_t, int, int, int, off_t); using MMAP_Hook = void* (*)(void*, size_t, int, int, int, off_t);
using MUNMAP_Hook = int (*)(void*, size_t); using MUNMAP_Hook = int (*)(void*, size_t);
FEX_DEFAULT_VISIBILITY extern MMAP_Hook mmap; FEX_DEFAULT_VISIBILITY extern MMAP_Hook mmap;
FEX_DEFAULT_VISIBILITY extern MUNMAP_Hook munmap; FEX_DEFAULT_VISIBILITY extern MUNMAP_Hook munmap;
FEX_DEFAULT_VISIBILITY extern void VirtualName(const char* Name, void* Ptr, size_t Size);
// All commit parameters are ignored here, they are unnecessary as Linux supports overcommit // All commit parameters are ignored here, they are unnecessary as Linux supports overcommit
@@ -12,8 +12,6 @@ struct InternalThreadState;
namespace FEXCore::ArchHelpers::Arm64 { namespace FEXCore::ArchHelpers::Arm64 {
enum class UnalignedHandlerType { enum class UnalignedHandlerType {
///< Don't backpatch code, instead handle inside SIGBUS handler.
Paranoid,
///< Backpatch unaligned access to half-barrier based atomic. ///< Backpatch unaligned access to half-barrier based atomic.
HalfBarrier, HalfBarrier,
///< Backpatch unaligned access to non-atomic. ///< Backpatch unaligned access to non-atomic.
@@ -26,7 +24,7 @@ enum class UnalignedHandlerType {
* This is an OS agnostic handler where the frontend must provide FEXCore with the information necessary to know if this is safe. * This is an OS agnostic handler where the frontend must provide FEXCore with the information necessary to know if this is safe.
* This does not check if the PC is within a JIT code buffer, the frontend must provide that safety with `CPUBackend::IsAddressInCodeBuffer`. * This does not check if the PC is within a JIT code buffer, the frontend must provide that safety with `CPUBackend::IsAddressInCodeBuffer`.
* *
* @param ParanoidTSO If the unaligned fault needs to handled directly or can be backpatched. * @param HandleType Type of TSO handling to use.
* @param ProgramCounter The location in memory for the instruction that did the access * @param ProgramCounter The location in memory for the instruction that did the access
* @param GPRs The array of GPRs from the signal context. This will be modified and the host context needs to be updated on signal return. * @param GPRs The array of GPRs from the signal context. This will be modified and the host context needs to be updated on signal return.
* *
@@ -34,6 +32,6 @@ enum class UnalignedHandlerType {
* by. FEXCore will return a positive or negative offset depending on internal handling. * by. FEXCore will return a positive or negative offset depending on internal handling.
*/ */
[[nodiscard]] [[nodiscard]]
FEX_DEFAULT_VISIBILITY std::optional<int32_t> FEX_DEFAULT_VISIBILITY std::optional<int32_t> HandleUnalignedAccess(
HandleUnalignedAccess(FEXCore::Core::InternalThreadState* Thread, UnalignedHandlerType HandleType, uintptr_t ProgramCounter, uint64_t* GPRs); FEXCore::Core::InternalThreadState* Thread, UnalignedHandlerType HandleType, uintptr_t ProgramCounter, uint64_t* GPRs, bool IsJIT = true);
} // namespace FEXCore::ArchHelpers::Arm64 } // namespace FEXCore::ArchHelpers::Arm64
+37
View File
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
#pragma once
#include <FEXCore/Utils/CompilerDefs.h>
#include <cstdint>
// Reimplementation of longjmp without glibc fortification checks.
// This is useful to avoid false positives reported by glibc.
namespace FEXCore::LongJump {
// JumpBuf definition needs to be public because the frontend needs to understand it.
#if defined(_M_ARM_64)
struct JumpBuf {
// All the registers that are required by AAPCS64 to save.
// GPRs
// X19, X20, X21, X22,
// X23, X24, X25, X26,
// X27, X28, X29, X30,
//
// Lower 64-bits:
// V8, V9, V10, V11,
// V12, V13, V14, V15,
//
// SP,
uint64_t Registers[21];
};
#else
struct JumpBuf {
// Registers to preserve
// RBX, RSP, RBP, R12, R13, R14, R15,
// <return address>
uint64_t Registers[8];
};
#endif
[[nodiscard]] FEX_DEFAULT_VISIBILITY uint64_t SetJump(JumpBuf& Buffer);
[[noreturn]] FEX_DEFAULT_VISIBILITY void LongJump(JumpBuf& Buffer, uint64_t Value);
} // namespace FEXCore::LongJump
+12 -1
View File
@@ -39,10 +39,12 @@ enum class AppType : uint8_t {
WIN_WOW64, WIN_WOW64,
}; };
// Only append new members to the end of {ThreadStatsHeader, ThreadStats} to allow old tools time to support new information.
// FEX isn't guaranteeing /not/ breaking compatibility with versions, but trying to not cause too much churn.
struct ThreadStatsHeader { struct ThreadStatsHeader {
uint8_t Version; uint8_t Version;
AppType app_type; AppType app_type;
uint8_t _pad[2]; uint16_t ThreadStatsSize;
char fex_version[48]; char fex_version[48];
std::atomic<uint32_t> Head; std::atomic<uint32_t> Head;
std::atomic<uint32_t> Size; std::atomic<uint32_t> Size;
@@ -61,8 +63,17 @@ struct ThreadStats {
uint64_t AccumulatedSIGBUSCount; uint64_t AccumulatedSIGBUSCount;
uint64_t AccumulatedSMCCount; uint64_t AccumulatedSMCCount;
uint64_t AccumulatedFloatFallbackCount; uint64_t AccumulatedFloatFallbackCount;
uint64_t AccumulatedCacheMissCount;
uint64_t AccumulatedCacheReadLockTime;
uint64_t AccumulatedCacheWriteLockTime;
uint64_t AccumulatedJITCount;
}; };
// Ensure 16-byte alignment to take advantage of ARM single-copy atomicity.
static_assert(sizeof(ThreadStats) % 16 == 0, "Needs to be 16-byte aligned!");
template<typename T, size_t FlatOffset = 0> template<typename T, size_t FlatOffset = 0>
class AccumulationBlock final { class AccumulationBlock final {
public: public:
@@ -380,6 +380,8 @@ private:
class PooledAllocatorVirtual final : public IntrusivePooledAllocator { class PooledAllocatorVirtual final : public IntrusivePooledAllocator {
public: public:
PooledAllocatorVirtual() = default; PooledAllocatorVirtual() = default;
PooledAllocatorVirtual(const char* Name)
: Name {Name} {}
virtual ~PooledAllocatorVirtual() { virtual ~PooledAllocatorVirtual() {
FreeAllBuffers(); FreeAllBuffers();
@@ -387,12 +389,18 @@ public:
private: private:
void* Alloc(size_t Size) override { void* Alloc(size_t Size) override {
return FEXCore::Allocator::VirtualAlloc(Size); auto Result = FEXCore::Allocator::VirtualAlloc(Size);
if (Name) {
FEXCore::Allocator::VirtualName(Name, Result, Size);
}
return Result;
} }
void Free(void* Ptr, size_t Size) override { void Free(void* Ptr, size_t Size) override {
FEXCore::Allocator::VirtualFree(Ptr, Size); FEXCore::Allocator::VirtualFree(Ptr, Size);
} }
const char* Name {};
}; };
/** /**
@@ -2,7 +2,9 @@
#pragma once #pragma once
#include <FEXCore/Utils/MathUtils.h> #include <FEXCore/Utils/MathUtils.h>
#include <FEXCore/Utils/LogManager.h> #include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/TypeDefines.h>
#include <FEXCore/fextl/allocator.h> #include <FEXCore/fextl/allocator.h>
#include <FEXCore/fextl/list.h>
#include <memory_resource> #include <memory_resource>
#include <fmt/format.h> #include <fmt/format.h>
@@ -26,6 +28,90 @@ namespace pmr {
FEX_DEFAULT_VISIBILITY std::pmr::memory_resource* get_default_resource(); FEX_DEFAULT_VISIBILITY std::pmr::memory_resource* get_default_resource();
/**
* @brief A `std::pmr::monotonic_buffer_resource` compatible class.
*
* Allocates internal buffers on page boundaries and names them for buffer tracking.
*/
class named_monotonic_page_buffer_resource final : public std::pmr::memory_resource {
public:
explicit named_monotonic_page_buffer_resource(const char* Name)
: Name {Name} {}
void release() noexcept {
for (auto& Iter : Buffers) {
FEXCore::Allocator::VirtualFree(Iter.Buffer, Iter.BufferSize);
}
Buffers.clear();
CurrentBufferRemaining = 0;
CurrentAllocationSize = FEXCore::Utils::FEX_PAGE_SIZE;
}
protected:
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
LOGMAN_THROW_A_FMT(bytes != 0, "Nope");
LOGMAN_THROW_A_FMT(alignment <= FEXCore::Utils::FEX_PAGE_SIZE, "Nope");
// Wow, an actual use case of std::align in the wild.
void* NewPointer = std::align(alignment, bytes, CurrentBuffer, CurrentBufferRemaining);
if (!NewPointer) [[unlikely]] {
AllocateNewBuffer(bytes, alignment);
NewPointer = CurrentBuffer;
}
CurrentBuffer = static_cast<char*>(CurrentBuffer) + bytes;
CurrentBufferRemaining -= bytes;
return NewPointer;
}
void do_deallocate(void*, std::size_t, std::size_t) override {
// Explicit no-op.
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
private:
const char* Name;
// Allocate a new buffer that can at least fit the passed in bytes with alignment.
void AllocateNewBuffer(std::size_t bytes, std::size_t) {
bytes = FEXCore::AlignUp(bytes, CurrentAllocationSize);
void* Ptr = FEXCore::Allocator::VirtualAlloc(bytes);
if (Name) {
FEXCore::Allocator::VirtualName(Name, Ptr, bytes);
}
Buffers.emplace_back(BufferData {
.Buffer = Ptr,
.BufferSize = bytes,
});
CurrentBuffer = Ptr;
CurrentBufferRemaining = bytes;
// Multiply the allocation size by 1.5 for the next allocation
// Avoid double math because of ugly conversions.
CurrentAllocationSize = FEXCore::AlignUp(CurrentAllocationSize + (CurrentAllocationSize >> 1), FEXCore::Utils::FEX_PAGE_SIZE);
}
// Current buffer management.
void* CurrentBuffer {};
size_t CurrentBufferRemaining {};
struct BufferData final {
void* Buffer;
size_t BufferSize;
};
fextl::list<BufferData> Buffers {};
size_t CurrentAllocationSize = FEXCore::Utils::FEX_PAGE_SIZE;
};
/** /**
* @brief This is similar to the std::pmr::monotonic_buffer_resource. * @brief This is similar to the std::pmr::monotonic_buffer_resource.
* *
+36 -36
View File
@@ -9,17 +9,17 @@ using namespace ARMEmitter;
TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") { TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
adr(Reg::r30, &Label); (void)adr(Reg::r30, &Label);
CHECK(DisassembleEncoding(1) == 0x10fffffe); CHECK(DisassembleEncoding(1) == 0x10fffffe);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
adr(Reg::r30, &Label); (void)adr(Reg::r30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x1000003e); CHECK(DisassembleEncoding(0) == 0x1000003e);
@@ -27,17 +27,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
adr(Reg::r30, &Label); (void)adr(Reg::r30, &Label);
CHECK(DisassembleEncoding(1) == 0x10fffffe); CHECK(DisassembleEncoding(1) == 0x10fffffe);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
adr(Reg::r30, &Label); (void)adr(Reg::r30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x1000003e); CHECK(DisassembleEncoding(0) == 0x1000003e);
@@ -45,42 +45,42 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
adrp(Reg::r30, &Label); (void)adrp(Reg::r30, &Label);
CHECK(DisassembleEncoding(1) == 0x9000001e); CHECK(DisassembleEncoding(1) == 0x9000001e);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
adrp(Reg::r30, &Label); (void)adrp(Reg::r30, &Label);
// Move label a page away // Move label a page away
for (size_t i = 0; i < 1023; ++i) { for (size_t i = 0; i < 1023; ++i) {
nop(); nop();
} }
Bind(&Label); (void)Bind(&Label);
CHECK(DisassembleEncoding(0) == 0xb000001e); CHECK(DisassembleEncoding(0) == 0xb000001e);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
adrp(Reg::r30, &Label); (void)adrp(Reg::r30, &Label);
CHECK(DisassembleEncoding(1) == 0x9000001e); CHECK(DisassembleEncoding(1) == 0x9000001e);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
adrp(Reg::r30, &Label); (void)adrp(Reg::r30, &Label);
// Move label a page away // Move label a page away
for (size_t i = 0; i < 1023; ++i) { for (size_t i = 0; i < 1023; ++i) {
nop(); nop();
} }
Bind(&Label); (void)Bind(&Label);
CHECK(DisassembleEncoding(0) == 0xb000001e); CHECK(DisassembleEncoding(0) == 0xb000001e);
} }
@@ -88,17 +88,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
// Will generate adr. // Will generate adr.
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
CHECK(DisassembleEncoding(1) == 0x10fffffe); CHECK(DisassembleEncoding(1) == 0x10fffffe);
} }
{ {
// Will generate nop + adr. // Will generate nop + adr.
ForwardLabel Label; ForwardLabel Label;
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xd503201f); CHECK(DisassembleEncoding(0) == 0xd503201f);
@@ -107,9 +107,9 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
// Will generate adr. // Will generate adr.
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
CHECK(DisassembleEncoding(1) == 0x10fffffe); CHECK(DisassembleEncoding(1) == 0x10fffffe);
} }
@@ -117,8 +117,8 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
// Will generate nop + adr. // Will generate nop + adr.
BiDirectionalLabel Label; BiDirectionalLabel Label;
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xd503201f); CHECK(DisassembleEncoding(0) == 0xd503201f);
@@ -128,7 +128,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
// Will generate adrp. // Will generate adrp.
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
// Move adrp 1MB away. // Move adrp 1MB away.
@@ -136,7 +136,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
nop(); nop();
} }
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
nop(); nop();
CHECK(DisassembleEncoding(262145) == 0x90fff81e); CHECK(DisassembleEncoding(262145) == 0x90fff81e);
CHECK(DisassembleEncoding(262146) == 0xd503201f); CHECK(DisassembleEncoding(262146) == 0xd503201f);
@@ -145,14 +145,14 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
// Will generate nop + adrp. // Will generate nop + adrp.
ForwardLabel Label; ForwardLabel Label;
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
// Move label 1MB away, plus a page, and then aligned to a page. // Move label 1MB away, plus a page, and then aligned to a page.
for (size_t i = 0; i < ((1 * 1024 * 1024 + 4096) / 4 - 2); ++i) { for (size_t i = 0; i < ((1 * 1024 * 1024 + 4096) / 4 - 2); ++i) {
nop(); nop();
} }
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xd503201f); CHECK(DisassembleEncoding(0) == 0xd503201f);
@@ -162,14 +162,14 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
// Will generate adrp + add. // Will generate adrp + add.
ForwardLabel Label; ForwardLabel Label;
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
// Move label 1MB away, plus a page, plus one instruction. // Move label 1MB away, plus a page, plus one instruction.
for (size_t i = 0; i < ((1 * 1024 * 1024 + 4096) / 4 - 1); ++i) { for (size_t i = 0; i < ((1 * 1024 * 1024 + 4096) / 4 - 1); ++i) {
nop(); nop();
} }
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb000081e); CHECK(DisassembleEncoding(0) == 0xb000081e);
@@ -180,7 +180,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
// Will generate adrp. // Will generate adrp.
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
// Move adrp 1MB away. // Move adrp 1MB away.
@@ -188,7 +188,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
nop(); nop();
} }
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
nop(); nop();
CHECK(DisassembleEncoding(262145) == 0x90fff81e); CHECK(DisassembleEncoding(262145) == 0x90fff81e);
CHECK(DisassembleEncoding(262146) == 0xd503201f); CHECK(DisassembleEncoding(262146) == 0xd503201f);
@@ -197,14 +197,14 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
// Will generate nop + adrp. // Will generate nop + adrp.
BiDirectionalLabel Label; BiDirectionalLabel Label;
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
// Move label 1MB away, plus a page, and then aligned to a page. // Move label 1MB away, plus a page, and then aligned to a page.
for (size_t i = 0; i < ((1 * 1024 * 1024 + 4096) / 4 - 2); ++i) { for (size_t i = 0; i < ((1 * 1024 * 1024 + 4096) / 4 - 2); ++i) {
nop(); nop();
} }
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xd503201f); CHECK(DisassembleEncoding(0) == 0xd503201f);
@@ -214,14 +214,14 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: ALU: PC relative") {
{ {
// Will generate adrp + add. // Will generate adrp + add.
BiDirectionalLabel Label; BiDirectionalLabel Label;
LongAddressGen(Reg::r30, &Label); (void)LongAddressGen(Reg::r30, &Label);
// Move label 1MB away, plus a page, plus one instruction. // Move label 1MB away, plus a page, plus one instruction.
for (size_t i = 0; i < ((1 * 1024 * 1024 + 4096) / 4 - 1); ++i) { for (size_t i = 0; i < ((1 * 1024 * 1024 + 4096) / 4 - 1); ++i) {
nop(); nop();
} }
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb000081e); CHECK(DisassembleEncoding(0) == 0xb000081e);
+96 -96
View File
@@ -9,17 +9,17 @@ using namespace ARMEmitter;
TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Conditional branch immediate") { TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Conditional branch immediate") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
b(Condition::CC_PL, &Label); (void)b(Condition::CC_PL, &Label);
CHECK(DisassembleEncoding(1) == 0x54ffffe5); CHECK(DisassembleEncoding(1) == 0x54ffffe5);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
b(Condition::CC_PL, &Label); (void)b(Condition::CC_PL, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x54000025); CHECK(DisassembleEncoding(0) == 0x54000025);
@@ -27,17 +27,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Conditional branch immediat
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
b(Condition::CC_PL, &Label); (void)b(Condition::CC_PL, &Label);
CHECK(DisassembleEncoding(1) == 0x54ffffe5); CHECK(DisassembleEncoding(1) == 0x54ffffe5);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
b(Condition::CC_PL, &Label); (void)b(Condition::CC_PL, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x54000025); CHECK(DisassembleEncoding(0) == 0x54000025);
@@ -46,17 +46,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Conditional branch immediat
TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Branch consistent conditional") { TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Branch consistent conditional") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
bc(Condition::CC_PL, &Label); (void)bc(Condition::CC_PL, &Label);
CHECK(DisassembleEncoding(1) == 0x54fffff5); CHECK(DisassembleEncoding(1) == 0x54fffff5);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
bc(Condition::CC_PL, &Label); (void)bc(Condition::CC_PL, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x54000035); CHECK(DisassembleEncoding(0) == 0x54000035);
@@ -64,17 +64,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Branch consistent condition
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
bc(Condition::CC_PL, &Label); (void)bc(Condition::CC_PL, &Label);
CHECK(DisassembleEncoding(1) == 0x54fffff5); CHECK(DisassembleEncoding(1) == 0x54fffff5);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
bc(Condition::CC_PL, &Label); (void)bc(Condition::CC_PL, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x54000035); CHECK(DisassembleEncoding(0) == 0x54000035);
@@ -89,17 +89,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Unconditional branch regist
TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Unconditional branch immediate") { TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Unconditional branch immediate") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
b(&Label); (void)b(&Label);
CHECK(DisassembleEncoding(1) == 0x17ffffff); CHECK(DisassembleEncoding(1) == 0x17ffffff);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
b(&Label); (void)b(&Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x14000001); CHECK(DisassembleEncoding(0) == 0x14000001);
@@ -107,17 +107,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Unconditional branch immedi
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
b(&Label); (void)b(&Label);
CHECK(DisassembleEncoding(1) == 0x17ffffff); CHECK(DisassembleEncoding(1) == 0x17ffffff);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
b(&Label); (void)b(&Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x14000001); CHECK(DisassembleEncoding(0) == 0x14000001);
@@ -125,17 +125,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Unconditional branch immedi
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
bl(&Label); (void)bl(&Label);
CHECK(DisassembleEncoding(1) == 0x97ffffff); CHECK(DisassembleEncoding(1) == 0x97ffffff);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
bl(&Label); (void)bl(&Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x94000001); CHECK(DisassembleEncoding(0) == 0x94000001);
@@ -143,17 +143,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Unconditional branch immedi
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
bl(&Label); (void)bl(&Label);
CHECK(DisassembleEncoding(1) == 0x97ffffff); CHECK(DisassembleEncoding(1) == 0x97ffffff);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
bl(&Label); (void)bl(&Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x94000001); CHECK(DisassembleEncoding(0) == 0x94000001);
@@ -162,17 +162,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Unconditional branch immedi
TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") { TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
cbz(Size::i32Bit, Reg::r29, &Label); (void)cbz(Size::i32Bit, Reg::r29, &Label);
CHECK(DisassembleEncoding(1) == 0x34fffffd); CHECK(DisassembleEncoding(1) == 0x34fffffd);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
cbz(Size::i32Bit, Reg::r29, &Label); (void)cbz(Size::i32Bit, Reg::r29, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x3400003d); CHECK(DisassembleEncoding(0) == 0x3400003d);
@@ -180,17 +180,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") {
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
cbz(Size::i32Bit, Reg::r29, &Label); (void)cbz(Size::i32Bit, Reg::r29, &Label);
CHECK(DisassembleEncoding(1) == 0x34fffffd); CHECK(DisassembleEncoding(1) == 0x34fffffd);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
cbz(Size::i32Bit, Reg::r29, &Label); (void)cbz(Size::i32Bit, Reg::r29, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x3400003d); CHECK(DisassembleEncoding(0) == 0x3400003d);
@@ -198,17 +198,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
cbz(Size::i64Bit, Reg::r29, &Label); (void)cbz(Size::i64Bit, Reg::r29, &Label);
CHECK(DisassembleEncoding(1) == 0xb4fffffd); CHECK(DisassembleEncoding(1) == 0xb4fffffd);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
cbz(Size::i64Bit, Reg::r29, &Label); (void)cbz(Size::i64Bit, Reg::r29, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb400003d); CHECK(DisassembleEncoding(0) == 0xb400003d);
@@ -216,17 +216,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") {
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
cbz(Size::i64Bit, Reg::r29, &Label); (void)cbz(Size::i64Bit, Reg::r29, &Label);
CHECK(DisassembleEncoding(1) == 0xb4fffffd); CHECK(DisassembleEncoding(1) == 0xb4fffffd);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
cbz(Size::i64Bit, Reg::r29, &Label); (void)cbz(Size::i64Bit, Reg::r29, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb400003d); CHECK(DisassembleEncoding(0) == 0xb400003d);
@@ -234,17 +234,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
cbnz(Size::i32Bit, Reg::r29, &Label); (void)cbnz(Size::i32Bit, Reg::r29, &Label);
CHECK(DisassembleEncoding(1) == 0x35fffffd); CHECK(DisassembleEncoding(1) == 0x35fffffd);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
cbnz(Size::i32Bit, Reg::r29, &Label); (void)cbnz(Size::i32Bit, Reg::r29, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x3500003d); CHECK(DisassembleEncoding(0) == 0x3500003d);
@@ -252,17 +252,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") {
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
cbnz(Size::i32Bit, Reg::r29, &Label); (void)cbnz(Size::i32Bit, Reg::r29, &Label);
CHECK(DisassembleEncoding(1) == 0x35fffffd); CHECK(DisassembleEncoding(1) == 0x35fffffd);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
cbnz(Size::i32Bit, Reg::r29, &Label); (void)cbnz(Size::i32Bit, Reg::r29, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x3500003d); CHECK(DisassembleEncoding(0) == 0x3500003d);
@@ -270,17 +270,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
cbnz(Size::i64Bit, Reg::r29, &Label); (void)cbnz(Size::i64Bit, Reg::r29, &Label);
CHECK(DisassembleEncoding(1) == 0xb5fffffd); CHECK(DisassembleEncoding(1) == 0xb5fffffd);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
cbnz(Size::i64Bit, Reg::r29, &Label); (void)cbnz(Size::i64Bit, Reg::r29, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb500003d); CHECK(DisassembleEncoding(0) == 0xb500003d);
@@ -288,17 +288,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") {
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
cbnz(Size::i64Bit, Reg::r29, &Label); (void)cbnz(Size::i64Bit, Reg::r29, &Label);
CHECK(DisassembleEncoding(1) == 0xb5fffffd); CHECK(DisassembleEncoding(1) == 0xb5fffffd);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
cbnz(Size::i64Bit, Reg::r29, &Label); (void)cbnz(Size::i64Bit, Reg::r29, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb500003d); CHECK(DisassembleEncoding(0) == 0xb500003d);
@@ -307,17 +307,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Compare and branch") {
TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Test and branch immediate") { TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Test and branch immediate") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
tbz(Reg::r29, 0, &Label); (void)tbz(Reg::r29, 0, &Label);
CHECK(DisassembleEncoding(1) == 0x3607fffd); CHECK(DisassembleEncoding(1) == 0x3607fffd);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
tbz(Reg::r29, 0, &Label); (void)tbz(Reg::r29, 0, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x3600003d); CHECK(DisassembleEncoding(0) == 0x3600003d);
@@ -325,17 +325,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Test and branch immediate")
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
tbz(Reg::r29, 0, &Label); (void)tbz(Reg::r29, 0, &Label);
CHECK(DisassembleEncoding(1) == 0x3607fffd); CHECK(DisassembleEncoding(1) == 0x3607fffd);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
tbz(Reg::r29, 0, &Label); (void)tbz(Reg::r29, 0, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x3600003d); CHECK(DisassembleEncoding(0) == 0x3600003d);
@@ -343,17 +343,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Test and branch immediate")
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
tbz(Reg::r29, 63, &Label); (void)tbz(Reg::r29, 63, &Label);
CHECK(DisassembleEncoding(1) == 0xb6fffffd); CHECK(DisassembleEncoding(1) == 0xb6fffffd);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
tbz(Reg::r29, 63, &Label); (void)tbz(Reg::r29, 63, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb6f8003d); CHECK(DisassembleEncoding(0) == 0xb6f8003d);
@@ -361,17 +361,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Test and branch immediate")
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
tbz(Reg::r29, 63, &Label); (void)tbz(Reg::r29, 63, &Label);
CHECK(DisassembleEncoding(1) == 0xb6fffffd); CHECK(DisassembleEncoding(1) == 0xb6fffffd);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
tbz(Reg::r29, 63, &Label); (void)tbz(Reg::r29, 63, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb6f8003d); CHECK(DisassembleEncoding(0) == 0xb6f8003d);
@@ -379,17 +379,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Test and branch immediate")
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
tbnz(Reg::r29, 0, &Label); (void)tbnz(Reg::r29, 0, &Label);
CHECK(DisassembleEncoding(1) == 0x3707fffd); CHECK(DisassembleEncoding(1) == 0x3707fffd);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
tbnz(Reg::r29, 0, &Label); (void)tbnz(Reg::r29, 0, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x3700003d); CHECK(DisassembleEncoding(0) == 0x3700003d);
@@ -397,17 +397,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Test and branch immediate")
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
tbnz(Reg::r29, 0, &Label); (void)tbnz(Reg::r29, 0, &Label);
CHECK(DisassembleEncoding(1) == 0x3707fffd); CHECK(DisassembleEncoding(1) == 0x3707fffd);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
tbnz(Reg::r29, 0, &Label); (void)tbnz(Reg::r29, 0, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x3700003d); CHECK(DisassembleEncoding(0) == 0x3700003d);
@@ -415,17 +415,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Test and branch immediate")
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
tbnz(Reg::r29, 63, &Label); (void)tbnz(Reg::r29, 63, &Label);
CHECK(DisassembleEncoding(1) == 0xb7fffffd); CHECK(DisassembleEncoding(1) == 0xb7fffffd);
} }
{ {
ForwardLabel Label; ForwardLabel Label;
tbnz(Reg::r29, 63, &Label); (void)tbnz(Reg::r29, 63, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb7f8003d); CHECK(DisassembleEncoding(0) == 0xb7f8003d);
@@ -433,17 +433,17 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Branch: Test and branch immediate")
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
tbnz(Reg::r29, 63, &Label); (void)tbnz(Reg::r29, 63, &Label);
CHECK(DisassembleEncoding(1) == 0xb7fffffd); CHECK(DisassembleEncoding(1) == 0xb7fffffd);
} }
{ {
BiDirectionalLabel Label; BiDirectionalLabel Label;
tbnz(Reg::r29, 63, &Label); (void)tbnz(Reg::r29, 63, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xb7f8003d); CHECK(DisassembleEncoding(0) == 0xb7f8003d);
+14 -14
View File
@@ -1323,7 +1323,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: LDAPR/STLR unscaled imme
TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal") { TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal") {
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
ldr(WReg::w30, &Label); ldr(WReg::w30, &Label);
@@ -1332,7 +1332,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
ldr(SReg::s30, &Label); ldr(SReg::s30, &Label);
@@ -1341,7 +1341,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
ldr(XReg::x30, &Label); ldr(XReg::x30, &Label);
@@ -1350,7 +1350,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
ldr(DReg::d30, &Label); ldr(DReg::d30, &Label);
@@ -1359,7 +1359,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
ldrsw(XReg::x30, &Label); ldrsw(XReg::x30, &Label);
@@ -1368,7 +1368,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
ldr(QReg::q30, &Label); ldr(QReg::q30, &Label);
@@ -1377,7 +1377,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
BackwardLabel Label; BackwardLabel Label;
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
prfm(Prefetch::PLDL1KEEP, &Label); prfm(Prefetch::PLDL1KEEP, &Label);
@@ -1387,7 +1387,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
ForwardLabel Label; ForwardLabel Label;
ldr(WReg::w30, &Label); ldr(WReg::w30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x1800003e); CHECK(DisassembleEncoding(0) == 0x1800003e);
@@ -1396,7 +1396,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
ForwardLabel Label; ForwardLabel Label;
ldr(SReg::s30, &Label); ldr(SReg::s30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x1c00003e); CHECK(DisassembleEncoding(0) == 0x1c00003e);
@@ -1405,7 +1405,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
ForwardLabel Label; ForwardLabel Label;
ldr(XReg::x30, &Label); ldr(XReg::x30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x5800003e); CHECK(DisassembleEncoding(0) == 0x5800003e);
@@ -1414,7 +1414,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
ForwardLabel Label; ForwardLabel Label;
ldr(DReg::d30, &Label); ldr(DReg::d30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x5c00003e); CHECK(DisassembleEncoding(0) == 0x5c00003e);
@@ -1423,7 +1423,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
ForwardLabel Label; ForwardLabel Label;
ldrsw(XReg::x30, &Label); ldrsw(XReg::x30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x9800003e); CHECK(DisassembleEncoding(0) == 0x9800003e);
@@ -1432,7 +1432,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
ForwardLabel Label; ForwardLabel Label;
ldr(QReg::q30, &Label); ldr(QReg::q30, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0x9c00003e); CHECK(DisassembleEncoding(0) == 0x9c00003e);
@@ -1441,7 +1441,7 @@ TEST_CASE_METHOD(TestDisassembler, "Emitter: Loadstore: Load register literal")
{ {
ForwardLabel Label; ForwardLabel Label;
prfm(Prefetch::PLDL1KEEP, &Label); prfm(Prefetch::PLDL1KEEP, &Label);
Bind(&Label); (void)Bind(&Label);
dc32(0); dc32(0);
CHECK(DisassembleEncoding(0) == 0xd8000020); CHECK(DisassembleEncoding(0) == 0xd8000020);
+1 -5
View File
@@ -81,11 +81,7 @@ def IsSupportedDistro():
# We only support Ubuntu # We only support Ubuntu
if Distro[0] == "ubuntu": if Distro[0] == "ubuntu":
# We only support what is available in ppa:fex-emu/fex return Distro[1] in {"22.04", "24.04", "24.10", "25.04", "25.10"}
return Distro[1] == "22.04" or \
Distro[1] == "24.04" or \
Distro[1] == "24.10" or \
Distro[1] == "25.04"
return False return False
+6 -5
View File
@@ -277,15 +277,16 @@ void EnvLoader::Load() {
#include <FEXCore/Config/ConfigOptions.inl> #include <FEXCore/Config/ConfigOptions.inl>
if (Value) { if (Value) {
EnvMap.insert_or_assign(Key, *Value); EnvMap.insert_or_assign(Key, std::move(*Value));
} else { } else {
EnvMap.insert_or_assign(Key, Value_View); EnvMap.insert_or_assign(Key, Value_View);
} }
} }
auto GetVar = [](EnvMapType& EnvMap, const std::string_view id) -> std::optional<std::string_view> { auto GetVar = [](const EnvMapType& EnvMap, std::string_view id) -> std::optional<std::string_view> {
if (EnvMap.find(id) != EnvMap.end()) { const auto EnvEntry = EnvMap.find(id);
return EnvMap.at(id); if (EnvEntry != EnvMap.end()) {
return EnvEntry->second;
} }
// If envp[] was empty, search using std::getenv() // If envp[] was empty, search using std::getenv()
@@ -570,7 +571,7 @@ fextl::string GetConfigDirectory(bool Global, const PortableInformation& Portabl
return fextl::fmt::format("{}/fex-emu/", PortableInfo.InterpreterPath); return fextl::fmt::format("{}/fex-emu/", PortableInfo.InterpreterPath);
} else if (PortableInfo.IsPortable && ConfigOverride && !Global) { } else if (PortableInfo.IsPortable && ConfigOverride && !Global) {
fextl::string AppConfigStr = ConfigOverride; fextl::string AppConfigStr = ConfigOverride;
if (PortableInfo.IsPortable && FHU::Filesystem::IsRelative(AppConfigStr)) { if (FHU::Filesystem::IsRelative(AppConfigStr)) {
AppConfigStr = PortableInfo.InterpreterPath + AppConfigStr; AppConfigStr = PortableInfo.InterpreterPath + AppConfigStr;
} }
+10 -2
View File
@@ -131,7 +131,7 @@ fextl::string GetServerMountFolder() {
fextl::string GetServerSocketName() { fextl::string GetServerSocketName() {
FEX_CONFIG_OPT(ServerSocketPath, SERVERSOCKETPATH); FEX_CONFIG_OPT(ServerSocketPath, SERVERSOCKETPATH);
if (ServerSocketPath().empty()) { if (ServerSocketPath().empty()) {
return fextl::fmt::format("{}.FEXServer.Socket", ::geteuid()); return fextl::fmt::format("{}.FEXServer.Socket", ::getuid());
} }
return ServerSocketPath; return ServerSocketPath;
} }
@@ -148,7 +148,7 @@ fextl::string GetServerSocketPath() {
auto Folder = GetTempFolder(); auto Folder = GetTempFolder();
if (name.empty()) { if (name.empty()) {
return fextl::fmt::format("{}/{}.FEXServer.Socket", Folder, ::geteuid()); return fextl::fmt::format("{}/{}.FEXServer.Socket", Folder, ::getuid());
} else { } else {
return fextl::fmt::format("{}/{}", Folder, name); return fextl::fmt::format("{}/{}", Folder, name);
} }
@@ -300,6 +300,14 @@ int ConnectToAndStartServer(std::string_view InterpreterPath) {
while (poll(&PollFD, 1, -1) == -1 && errno == EINTR) while (poll(&PollFD, 1, -1) == -1 && errno == EINTR)
; ;
// Check if child signaled an error
uint64_t error = 0;
ssize_t bytes_read = read(fds[0], &error, sizeof(error));
close(fds[0]);
if (bytes_read > 0 && error != 0) {
return -1;
}
for (size_t i = 0; i < 5; ++i) { for (size_t i = 0; i < 5; ++i) {
ServerFD = ConnectToServer(ConnectionOption::Default); ServerFD = ConnectToServer(ConnectionOption::Default);
+46
View File
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
#pragma once
#include <FEXCore/Utils/TypeDefines.h>
#include <cstdint>
#include <optional>
#include <span>
#include <elf.h>
namespace FEXCore {
/**
* Infers the base virtual address from a file mapping (as described by parameters to a single
* call to mmap()).
*
* The file offset of any given mapping need not match its virtual address offset from the base
* mapping (file offset = 0). Instead, this function searches the corresponding ELF program headers
* for an entry that generated the given file mapping.
*/
inline std::optional<uint64_t>
InferMappingBaseAddress(std::span<const Elf64_Phdr> ProgramHeaders, uint64_t Addr, uint64_t Size, uint64_t FileOffset, int AccessFlags) {
for (auto& phdr : ProgramHeaders) {
if (phdr.p_type != PT_LOAD) {
// Skip headers that don't trigger memory mappings
continue;
}
if ((phdr.p_flags & (PF_X | PF_W | PF_R)) != (AccessFlags & (PF_X | PF_W | PF_R))) {
continue;
}
// The mapped file offset must be included at the start of the section header
auto SegmentStartOffset = phdr.p_offset - (phdr.p_vaddr & 0xfff);
if (FileOffset >= SegmentStartOffset && FileOffset < SegmentStartOffset + phdr.p_filesz &&
(FileOffset & Utils::FEX_PAGE_MASK) == (phdr.p_offset & Utils::FEX_PAGE_MASK)) {
// Compute VA offset relative to the base mapping
return Addr - (phdr.p_vaddr - (phdr.p_offset & 0xfff)) + (ProgramHeaders[0].p_vaddr - (ProgramHeaders[0].p_offset & 0xfff)) -
(FileOffset - SegmentStartOffset);
}
}
return std::nullopt;
}
} // namespace FEXCore
-10
View File
@@ -598,16 +598,6 @@ FEXCore::HostFeatures FetchHostFeatures(FEX::CPUFeatures& Features, bool Support
#endif #endif
HostFeatures.SupportsPreserveAllABI = FEX_HAS_PRESERVE_ALL_ATTR; HostFeatures.SupportsPreserveAllABI = FEX_HAS_PRESERVE_ALL_ATTR;
if (!Is64BitMode()) {
///< Always disable AVX and AVX2 in 32-bit mode.
// When AVX256 is enabled, signal frames start using significantly more stack space.
// - 16bytes * 16 registers = 256 bytes for XMM registers.
// - 32bytes * 16 registers = 512 bytes for YMM registers.
// There are known game failures on real x86 hardware where a 32-bit game is running up against the wall on stack space on non-AVX
// hardware and then explodes when run on AVX hardware. This is to guard against that.
HostFeatures.SupportsAVX = false;
}
OverrideFeatures(&HostFeatures, ForceSVEWidth()); OverrideFeatures(&HostFeatures, ForceSVEWidth());
return HostFeatures; return HostFeatures;
} }
+11 -14
View File
@@ -2,22 +2,19 @@
#include "Common/JSONPool.h" #include "Common/JSONPool.h"
namespace FEX::JSON { namespace FEX::JSON {
json_t* PoolInit(jsonPool_t* Pool); static json_t* PoolInit(jsonPool_t* Pool) {
json_t* PoolAlloc(jsonPool_t* Pool); auto* alloc = static_cast<JsonAllocator*>(Pool);
return &*alloc->json_objects.emplace(alloc->json_objects.end());
}
static json_t* PoolAlloc(jsonPool_t* Pool) {
auto* alloc = static_cast<JsonAllocator*>(Pool);
return &*alloc->json_objects.emplace(alloc->json_objects.end());
}
JsonAllocator::JsonAllocator() JsonAllocator::JsonAllocator()
: jsonPool_t { : jsonPool_t {
.init = FEX::JSON::PoolInit, .init = PoolInit,
.alloc = FEX::JSON::PoolAlloc, .alloc = PoolAlloc,
} {} } {}
json_t* PoolInit(jsonPool_t* Pool) {
JsonAllocator* alloc = static_cast<JsonAllocator*>(Pool);
return &*alloc->json_objects.emplace(alloc->json_objects.end());
}
json_t* PoolAlloc(jsonPool_t* Pool) {
JsonAllocator* alloc = static_cast<JsonAllocator*>(Pool);
return &*alloc->json_objects.emplace(alloc->json_objects.end());
}
} // namespace FEX::JSON } // namespace FEX::JSON
+2 -1
View File
@@ -13,10 +13,11 @@ void StatAllocBase::SaveHeader(FEXCore::SHMStats::AppType AppType) {
Head = reinterpret_cast<FEXCore::SHMStats::ThreadStatsHeader*>(Base); Head = reinterpret_cast<FEXCore::SHMStats::ThreadStatsHeader*>(Base);
Head->Size.store(CurrentSize, std::memory_order_relaxed); Head->Size.store(CurrentSize, std::memory_order_relaxed);
Head->Version = FEXCore::SHMStats::STATS_VERSION; Head->Version = FEXCore::SHMStats::STATS_VERSION;
Head->app_type = AppType;
Head->ThreadStatsSize = sizeof(FEXCore::SHMStats::ThreadStats);
std::string_view GitString = GIT_DESCRIBE_STRING; std::string_view GitString = GIT_DESCRIBE_STRING;
strncpy(Head->fex_version, GitString.data(), std::min(GitString.size(), sizeof(Head->fex_version))); strncpy(Head->fex_version, GitString.data(), std::min(GitString.size(), sizeof(Head->fex_version)));
Head->app_type = AppType;
Stats = reinterpret_cast<FEXCore::SHMStats::ThreadStats*>(reinterpret_cast<uint64_t>(Base) + sizeof(FEXCore::SHMStats::ThreadStatsHeader)); Stats = reinterpret_cast<FEXCore::SHMStats::ThreadStats*>(reinterpret_cast<uint64_t>(Base) + sizeof(FEXCore::SHMStats::ThreadStatsHeader));
+2 -14
View File
@@ -52,8 +52,8 @@ public:
{ {
auto CodeInvalidationlk = FEXCore::GuardSignalDeferringSection(CTX->GetCodeInvalidationMutex(), Thread); auto CodeInvalidationlk = FEXCore::GuardSignalDeferringSection(CTX->GetCodeInvalidationMutex(), Thread);
FEXCore::Context::InvalidatedEntryAccumulator Accumulator; CTX->InvalidateCodeBuffersCodeRange(reinterpret_cast<uint64_t>(CodeStart), MAX_CODE_SIZE);
CTX->InvalidateGuestCodeRange(Thread, Accumulator, reinterpret_cast<uint64_t>(CodeStart), MAX_CODE_SIZE); CTX->InvalidateThreadCachedCodeRange(Thread, reinterpret_cast<uint64_t>(CodeStart), MAX_CODE_SIZE);
} }
ClearStats(); ClearStats();
@@ -447,18 +447,6 @@ public:
return 0; return 0;
} }
FEXCore::HLE::SyscallABI GetSyscallABI(uint64_t Syscall) override {
if (Syscall == 0) {
// Claim syscall 0 is simple for instcountci inline tests.
return FEXCore::HLE::SyscallABI {
.NumArgs = 0,
.HasReturn = true,
.HostSyscallNumber = 0, // Just map to host syscall zero, it isn't going to get called.
};
}
return {0, false, -1};
}
// These are no-ops implementations of the SyscallHandler API // These are no-ops implementations of the SyscallHandler API
std::optional<FEXCore::ExecutableFileSectionInfo> std::optional<FEXCore::ExecutableFileSectionInfo>
LookupExecutableFileSection(FEXCore::Core::InternalThreadState& Thread, uint64_t GuestAddr) override { LookupExecutableFileSection(FEXCore::Core::InternalThreadState& Thread, uint64_t GuestAddr) override {
-5
View File
@@ -16,11 +16,6 @@ public:
return 0; return 0;
} }
FEXCore::HLE::SyscallABI GetSyscallABI(uint64_t Syscall) override {
// Don't do anything
return {0, false, 0};
}
// These are no-ops implementations of the SyscallHandler API // These are no-ops implementations of the SyscallHandler API
std::optional<FEXCore::ExecutableFileSectionInfo> LookupExecutableFileSection(FEXCore::Core::InternalThreadState&, uint64_t) override { std::optional<FEXCore::ExecutableFileSectionInfo> LookupExecutableFileSection(FEXCore::Core::InternalThreadState&, uint64_t) override {
return std::nullopt; return std::nullopt;
@@ -209,7 +209,7 @@ bool ELFContainer::LoadELF_32() {
DynamicProgram = Header._32.e_type != ET_EXEC; DynamicProgram = Header._32.e_type != ET_EXEC;
// Default BRK size // Default BRK size
BRKSize = 4096; BRKSize = FEXCore::Utils::FEX_PAGE_SIZE;
return true; return true;
} }
@@ -344,7 +344,7 @@ void ELFContainer::CalculateMemoryLayouts() {
} }
// Calculate BRK // Calculate BRK
MaxPhysAddr = FEXCore::AlignUp(MaxPhysAddr, 4096); MaxPhysAddr = FEXCore::AlignUp(MaxPhysAddr, FEXCore::Utils::FEX_PAGE_SIZE);
BRKBase = MaxPhysAddr; BRKBase = MaxPhysAddr;
MaxPhysAddr += BRKSize; MaxPhysAddr += BRKSize;
@@ -182,17 +182,17 @@ struct ELFParser {
} else { } else {
phdrs.resize(ehdr.e_phnum); phdrs.resize(ehdr.e_phnum);
if (pread(fd, &phdrs[0], sizeof(Elf64_Phdr) * ehdr.e_phnum, ehdr.e_phoff) == -1) { if (pread(fd, phdrs.data(), sizeof(Elf64_Phdr) * ehdr.e_phnum, ehdr.e_phoff) == -1) {
LogMan::Msg::EFmt("Failed to read phdr64 from '{}'", fd); LogMan::Msg::EFmt("Failed to read phdr64 from '{}'", fd);
return false; return false;
} }
} }
for (auto phdr : phdrs) { for (const auto& phdr : phdrs) {
if (phdr.p_type == PT_INTERP) { if (phdr.p_type == PT_INTERP) {
InterpreterElf.resize(phdr.p_filesz); InterpreterElf.resize(phdr.p_filesz);
if (pread(fd, &InterpreterElf[0], phdr.p_filesz, phdr.p_offset) == -1) { if (pread(fd, InterpreterElf.data(), phdr.p_filesz, phdr.p_offset) == -1) {
LogMan::Msg::EFmt("Failed to read interpreter from '{}'", fd); LogMan::Msg::EFmt("Failed to read interpreter from '{}'", fd);
return false; return false;
} }
@@ -202,10 +202,9 @@ struct ELFParser {
return true; return true;
} }
ptrdiff_t FileToVA(off_t FileOffset) { ptrdiff_t FileToVA(off_t FileOffset) const {
for (auto phdr : phdrs) { for (const auto& phdr : phdrs) {
if (phdr.p_offset <= FileOffset && (phdr.p_offset + phdr.p_filesz) > FileOffset) { if (phdr.p_offset <= FileOffset && (phdr.p_offset + phdr.p_filesz) > FileOffset) {
auto SectionFileOffset = FileOffset - phdr.p_offset; auto SectionFileOffset = FileOffset - phdr.p_offset;
if (SectionFileOffset < phdr.p_memsz) { if (SectionFileOffset < phdr.p_memsz) {
@@ -217,10 +216,9 @@ struct ELFParser {
return {}; return {};
} }
off_t VAToFile(ptrdiff_t VAOffset) { off_t VAToFile(ptrdiff_t VAOffset) const {
for (auto phdr : phdrs) { for (const auto& phdr : phdrs) {
if (phdr.p_vaddr <= VAOffset && (phdr.p_vaddr + phdr.p_memsz) > VAOffset) { if (phdr.p_vaddr <= VAOffset && (phdr.p_vaddr + phdr.p_memsz) > VAOffset) {
auto SectionVAOffset = VAOffset - phdr.p_vaddr; auto SectionVAOffset = VAOffset - phdr.p_vaddr;
if (SectionVAOffset < phdr.p_filesz) { if (SectionVAOffset < phdr.p_filesz) {
+2 -24
View File
@@ -507,13 +507,8 @@ ApplicationWindow {
ButtonGroup { ButtonGroup {
id: tsoButtonGroup id: tsoButtonGroup
buttons: [tso1, tso2, tso3] buttons: [tso1, tso2]
// Trying to be too clever here will trigger property binding loops, checkedButton: ConfigModel.getBool("TSOEnabled", refreshCache) ? tso2 : tso1
// so require both TSOEnabled and ParanoidTSO to be listed in the config.
// If they are not, the state will be displayed as undetermined.
checkedButton: !(ConfigModel.has("TSOEnabled", refreshCache) && (ConfigModel.has("ParanoidTSO", refreshCache))) ? null
: ConfigModel.getBool("ParanoidTSO", refreshCache) ? tso3
: ConfigModel.getBool("TSOEnabled", refreshCache) ? tso2 : tso1
property int pendingItemChange: -1 property int pendingItemChange: -1
@@ -524,8 +519,6 @@ ApplicationWindow {
var newIndex = pendingItemChange var newIndex = pendingItemChange
var TSOEnabled = newIndex === 1 var TSOEnabled = newIndex === 1
var ParanoidTSO = newIndex === 2
ConfigModel.setBool("ParanoidTSO", ParanoidTSO)
ConfigModel.setBool("TSOEnabled", TSOEnabled) ConfigModel.setBool("TSOEnabled", TSOEnabled)
pendingItemChange = -1; pendingItemChange = -1;
@@ -543,8 +536,6 @@ ApplicationWindow {
var newIndex = pendingItemChange var newIndex = pendingItemChange
var TSOEnabled = newIndex === 1 var TSOEnabled = newIndex === 1
var ParanoidTSO = newIndex === 2
ConfigModel.setBool("ParanoidTSO", ParanoidTSO)
ConfigModel.setBool("TSOEnabled", TSOEnabled) ConfigModel.setBool("TSOEnabled", TSOEnabled)
pendingItemChange = -1; pendingItemChange = -1;
@@ -585,12 +576,6 @@ ApplicationWindow {
} }
} }
} }
RadioButton {
id: tso3
text: qsTr("Overly accurate (paranoid TSO)")
onToggled: tsoButtonGroup.onClickedButton(2)
}
} }
ConfigCheckBox { ConfigCheckBox {
@@ -721,17 +706,10 @@ ApplicationWindow {
} }
ConfigCheckBox { ConfigCheckBox {
id: x87ReducedPrecisionCheckbox
text: qsTr("Reduced x87 precision") text: qsTr("Reduced x87 precision")
config: "X87ReducedPrecision" config: "X87ReducedPrecision"
} }
ConfigCheckBox {
text: qsTr("Strict reduced x87 precision")
config: "X87StrictReducedPrecision"
enabled: x87ReducedPrecisionCheckbox.checked
}
ConfigCheckBox { ConfigCheckBox {
text: qsTr("Unsafe local flags optimization") text: qsTr("Unsafe local flags optimization")
config: "ABILocalFlags" config: "ABILocalFlags"
@@ -222,6 +222,28 @@ void CheckForGCS() {
} }
} // namespace FEX::GCS } // namespace FEX::GCS
namespace FEX::UnalignedAtomic {
void SetupKernelUnalignedAtomics() {
#ifndef PR_ARM64_SET_UNALIGN_ATOMIC
#define PR_ARM64_SET_UNALIGN_ATOMIC 0x46455849
#define PR_ARM64_UNALIGN_ATOMIC_EMULATE (1UL << 0)
#define PR_ARM64_UNALIGN_ATOMIC_BACKPATCH (1UL << 1)
#define PR_ARM64_UNALIGN_ATOMIC_STRICT_SPLIT_LOCKS (1UL << 2)
#endif
// Interfaces with downstream FEX kernel patches to control unaligned atomic handling
FEX_CONFIG_OPT(ParanoidTSO, PARANOIDTSO);
FEX_CONFIG_OPT(StrictInProcessSplitLocks, STRICTINPROCESSSPLITLOCKS);
uint64_t Flags = (StrictInProcessSplitLocks() ? PR_ARM64_UNALIGN_ATOMIC_STRICT_SPLIT_LOCKS : 0) |
(ParanoidTSO() ? 0 : PR_ARM64_UNALIGN_ATOMIC_BACKPATCH) | PR_ARM64_UNALIGN_ATOMIC_EMULATE;
if (prctl(PR_ARM64_SET_UNALIGN_ATOMIC, Flags, 0, 0, 0) != -1) {
LogMan::Msg::IFmt("FEX: Kernel unaligned atomics enabled!");
}
}
} // namespace FEX::UnalignedAtomic
/** /**
* @brief Get an FD from an environment variable and then unset the environment variable. * @brief Get an FD from an environment variable and then unset the environment variable.
* *
@@ -458,6 +480,7 @@ int main(int argc, char** argv, char** const envp) {
// Setup TSO hardware emulation immediately after initializing the context. // Setup TSO hardware emulation immediately after initializing the context.
FEX::TSO::SetupTSOEmulation(CTX.get()); FEX::TSO::SetupTSOEmulation(CTX.get());
FEX::UnalignedAtomic::SetupKernelUnalignedAtomics();
if (!Loader.Is64BitMode()) { if (!Loader.Is64BitMode()) {
// Tell the kernel we want to use the compat input syscalls even though we're // Tell the kernel we want to use the compat input syscalls even though we're
@@ -123,14 +123,14 @@ void FileManager::LoadThunkDatabase(fextl::unordered_map<fextl::string, ThunkDBO
} else if (ItemName == "Depends") { } else if (ItemName == "Depends") {
jsonType_t PropertyType = json_getType(LibraryItem); jsonType_t PropertyType = json_getType(LibraryItem);
if (PropertyType == JSON_TEXT) { if (PropertyType == JSON_TEXT) {
DBObject->second.Depends.insert(json_getValue(LibraryItem)); DBObject->second.Depends.emplace(json_getValue(LibraryItem));
} else if (PropertyType == JSON_ARRAY) { } else if (PropertyType == JSON_ARRAY) {
for (const json_t* Depend = json_getChild(LibraryItem); Depend != nullptr; Depend = json_getSibling(Depend)) { for (const json_t* Depend = json_getChild(LibraryItem); Depend != nullptr; Depend = json_getSibling(Depend)) {
DBObject->second.Depends.insert(json_getValue(Depend)); DBObject->second.Depends.emplace(json_getValue(Depend));
} }
} }
} else if (ItemName == "Overlay") { } else if (ItemName == "Overlay") {
auto AddWithReplacement = [HomeDirectory, &PathPrefixes](ThunkDBObject& DBObject, fextl::string LibraryItem) { auto AddWithReplacement = [HomeDirectory, &PathPrefixes](ThunkDBObject& DBObject, std::string_view LibraryItem) {
// Walk through template string and fill in prefixes from right to left // Walk through template string and fill in prefixes from right to left
using namespace std::string_view_literals; using namespace std::string_view_literals;
@@ -144,8 +144,8 @@ void FileManager::LoadThunkDatabase(fextl::unordered_map<fextl::string, ThunkDBO
// Sort offsets in descending order to enable safe in-place replacement // Sort offsets in descending order to enable safe in-place replacement
std::sort(std::begin(PrefixPositions), std::end(PrefixPositions), std::greater<> {}); std::sort(std::begin(PrefixPositions), std::end(PrefixPositions), std::greater<> {});
for (auto& LibPrefix : PathPrefixes) { for (const auto& LibPrefix : PathPrefixes) {
fextl::string Replacement = LibraryItem; fextl::string Replacement(LibraryItem);
for (auto PrefixPos : PrefixPositions) { for (auto PrefixPos : PrefixPositions) {
if (PrefixPos == fextl::string::npos) { if (PrefixPos == fextl::string::npos) {
continue; continue;
@@ -6,19 +6,59 @@ $end_info$
*/ */
#include "LinuxSyscalls/Seccomp/BPFEmitter.h" #include "LinuxSyscalls/Seccomp/BPFEmitter.h"
#include "LinuxSyscalls/Seccomp/SeccompEmulator.h"
#include <FEXCore/Utils/AllocatorHooks.h>
#include <FEXCore/Utils/LogManager.h>
#include <linux/bpf_common.h> #include <linux/bpf_common.h>
#include <linux/filter.h> #include <linux/filter.h>
#include <linux/seccomp.h> #include <linux/seccomp.h>
namespace FEX::HLE {
#define EMIT_INST(x) \
do { \
if constexpr (CalculateSize) { \
OpSize += 4; \
} else { \
x; \
} \
} while (0)
#define RETURN_ERROR(x) \
if constexpr (CalculateSize) { \
return ~0ULL; \
} else { \
static_assert(x == -EINVAL, "Early return error evaluation only supports EINVAL"); \
return x; \
}
#define RETURN_SUCCESS() \
do { \
if constexpr (CalculateSize) { \
return OpSize; \
} else { \
return 0; \
} \
} while (0)
#define VALIDATE(cond) \ #define VALIDATE(cond) \
do { \ do { \
if (!(cond)) { \ if (!(cond)) { \
RETURN_ERROR(-EINVAL) \ RETURN_ERROR(-EINVAL) \
} \ } \
} while (0) } while (0)
namespace FEX::HLE {
using SizeErrorCheck = decltype([](uint64_t Result) -> bool { return Result == ~0ULL; });
using EmissionErrorCheck = decltype([](uint64_t Result) { return Result != 0; });
// Register selection comes from function signature.
constexpr auto REG_A = ARMEmitter::WReg::w0;
constexpr auto REG_X = ARMEmitter::WReg::w1;
constexpr auto REG_TMP = ARMEmitter::WReg::w2;
constexpr auto REG_TMP2 = ARMEmitter::WReg::w3;
constexpr auto REG_SECCOMP_DATA = ARMEmitter::XReg::x4;
template<bool CalculateSize> template<bool CalculateSize>
uint64_t BPFEmitter::HandleLoad(uint32_t BPFIP, const sock_filter* Inst) { uint64_t BPFEmitter::HandleLoad(uint32_t BPFIP, const sock_filter* Inst) {
VALIDATE(BPF_SIZE(Inst->code) == BPF_W); VALIDATE(BPF_SIZE(Inst->code) == BPF_W);
@@ -158,13 +198,13 @@ uint64_t BPFEmitter::HandleJmp(uint32_t BPFIP, uint32_t NumInst, const sock_filt
// Must not jump past the end. // Must not jump past the end.
VALIDATE(Target < NumInst); VALIDATE(Target < NumInst);
fextl::unordered_map<uint32_t, ARMEmitter::ForwardLabel>::iterator TargetLabel {}; JumpLabelIterator TargetLabel {};
if constexpr (!CalculateSize) { if constexpr (!CalculateSize) {
TargetLabel = JumpLabels.try_emplace(Target, ARMEmitter::ForwardLabel {}).first; TargetLabel = JumpLabels.try_emplace(Target, ARMEmitter::ForwardLabel {}).first;
} }
EMIT_INST(b(&TargetLabel->second)); EMIT_INST((void)b(&TargetLabel->second));
break; break;
} }
case BPF_JEQ: case BPF_JEQ:
@@ -200,16 +240,16 @@ uint64_t BPFEmitter::HandleJmp(uint32_t BPFIP, uint32_t NumInst, const sock_filt
RETURN_ERROR(-EINVAL); RETURN_ERROR(-EINVAL);
} }
fextl::unordered_map<uint32_t, ARMEmitter::ForwardLabel>::iterator TargetTrueLabel {}; JumpLabelIterator TargetTrueLabel {};
fextl::unordered_map<uint32_t, ARMEmitter::ForwardLabel>::iterator TargetFalseLabel {}; JumpLabelIterator TargetFalseLabel {};
if constexpr (!CalculateSize) { if constexpr (!CalculateSize) {
TargetTrueLabel = JumpLabels.try_emplace(TargetTrue, ARMEmitter::ForwardLabel {}).first; TargetTrueLabel = JumpLabels.try_emplace(TargetTrue, ARMEmitter::ForwardLabel {}).first;
TargetFalseLabel = JumpLabels.try_emplace(TargetFalse, ARMEmitter::ForwardLabel {}).first; TargetFalseLabel = JumpLabels.try_emplace(TargetFalse, ARMEmitter::ForwardLabel {}).first;
} }
EMIT_INST(b(CompareResultOp, &TargetTrueLabel->second)); EMIT_INST((void)b(CompareResultOp, &TargetTrueLabel->second));
EMIT_INST(b(&TargetFalseLabel->second)); EMIT_INST((void)b(&TargetFalseLabel->second));
break; break;
} }
default: RETURN_ERROR(-EINVAL); // Unknown jump type default: RETURN_ERROR(-EINVAL); // Unknown jump type
@@ -263,7 +303,7 @@ uint64_t BPFEmitter::HandleEmission(uint32_t flags, const sock_fprog* prog) {
if constexpr (!CalculateSize) { if constexpr (!CalculateSize) {
auto jump_label = JumpLabels.find(i); auto jump_label = JumpLabels.find(i);
if (jump_label != JumpLabels.end()) { if (jump_label != JumpLabels.end()) {
Bind(&jump_label->second); (void)Bind(&jump_label->second);
} }
} }
@@ -344,6 +384,8 @@ uint64_t BPFEmitter::JITFilter(uint32_t flags, const sock_fprog* prog) {
SetBuffer((uint8_t*)FEXCore::Allocator::mmap(nullptr, FuncSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), FuncSize); SetBuffer((uint8_t*)FEXCore::Allocator::mmap(nullptr, FuncSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), FuncSize);
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(GetBufferBase()), FuncSize);
const auto CodeBegin = GetCursorAddress<uint8_t*>(); const auto CodeBegin = GetCursorAddress<uint8_t*>();
uint64_t Result = HandleEmission<false, EmissionErrorCheck>(flags, prog); uint64_t Result = HandleEmission<false, EmissionErrorCheck>(flags, prog);
@@ -359,7 +401,7 @@ uint64_t BPFEmitter::JITFilter(uint32_t flags, const sock_fprog* prog) {
// Emit the constant pool. // Emit the constant pool.
Align(); Align();
for (auto& Const : ConstPool) { for (auto& Const : ConstPool) {
Bind(&Const.second); (void)Bind(&Const.second);
dc32(Const.first); dc32(Const.first);
} }
@@ -369,7 +411,7 @@ uint64_t BPFEmitter::JITFilter(uint32_t flags, const sock_fprog* prog) {
if constexpr (false) { if constexpr (false) {
// Useful for debugging seccomp filters. // Useful for debugging seccomp filters.
LogMan::Msg::DFmt("JITFilter: disas 0x{:x},+{}", (uint64_t)CodeBegin, CodeOnlySize); LogMan::Msg::DFmt("JITFilter: disas 0x{:x},+{}", fmt::ptr(CodeBegin), CodeOnlySize);
} }
ConstPool.clear(); ConstPool.clear();
@@ -49,47 +49,14 @@ private:
template<bool CalculateSize> template<bool CalculateSize>
uint64_t HandleMisc(uint32_t BPFIP, const sock_filter* Inst); uint64_t HandleMisc(uint32_t BPFIP, const sock_filter* Inst);
#define EMIT_INST(x) \
do { \
if constexpr (CalculateSize) { \
OpSize += 4; \
} else { \
x; \
} \
} while (0)
#define RETURN_ERROR(x) \
if constexpr (CalculateSize) { \
return ~0ULL; \
} else { \
static_assert(x == -EINVAL, "Early return error evaluation only supports EINVAL"); \
return x; \
}
#define RETURN_SUCCESS() \
do { \
if constexpr (CalculateSize) { \
return OpSize; \
} else { \
return 0; \
} \
} while (0)
using SizeErrorCheck = decltype([](uint64_t Result) -> bool { return Result == ~0ULL; });
using EmissionErrorCheck = decltype([](uint64_t Result) { return Result != 0; });
template<bool CalculateSize, class Pred> template<bool CalculateSize, class Pred>
uint64_t HandleEmission(uint32_t flags, const sock_fprog* prog); uint64_t HandleEmission(uint32_t flags, const sock_fprog* prog);
// Register selection comes from function signature.
constexpr static auto REG_A = ARMEmitter::WReg::w0;
constexpr static auto REG_X = ARMEmitter::WReg::w1;
constexpr static auto REG_TMP = ARMEmitter::WReg::w2;
constexpr static auto REG_TMP2 = ARMEmitter::WReg::w3;
constexpr static auto REG_SECCOMP_DATA = ARMEmitter::XReg::x4;
fextl::unordered_map<uint32_t, ARMEmitter::ForwardLabel> JumpLabels; fextl::unordered_map<uint32_t, ARMEmitter::ForwardLabel> JumpLabels;
fextl::unordered_map<uint32_t, ARMEmitter::ForwardLabel> ConstPool; fextl::unordered_map<uint32_t, ARMEmitter::ForwardLabel> ConstPool;
using JumpLabelIterator = decltype(JumpLabels)::iterator;
void* Func {}; void* Func {};
size_t FuncSize {}; size_t FuncSize {};
}; };
@@ -300,6 +300,8 @@ void SeccompEmulator::DeserializeFilters(FEXCore::Core::CpuStateFrame* Frame, in
::mprotect(Ptr, SFilter.CodeSize, PROT_READ | PROT_EXEC); ::mprotect(Ptr, SFilter.CodeSize, PROT_READ | PROT_EXEC);
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(Ptr), SFilter.CodeSize);
auto& it = auto& it =
Filters.emplace_back(SeccompFilterInfo {(SeccompFilterFunc)Ptr, 1, SFilter.CodeSize, SFilter.FilterInstructions, SFilter.ShouldLog}); Filters.emplace_back(SeccompFilterInfo {(SeccompFilterFunc)Ptr, 1, SFilter.CodeSize, SFilter.FilterInstructions, SFilter.ShouldLog});
TotalFilterInstructions += SFilter.FilterInstructions; TotalFilterInstructions += SFilter.FilterInstructions;
@@ -579,80 +579,77 @@ void SignalDelegator::HandleGuestSignal(FEX::HLE::ThreadStateObject* ThreadObjec
ucontext_t* _context = (ucontext_t*)UContext; ucontext_t* _context = (ucontext_t*)UContext;
auto SigInfo = *static_cast<siginfo_t*>(Info); auto SigInfo = *static_cast<siginfo_t*>(Info);
constexpr bool SupportDeferredSignals = true; auto MustDeferSignal = (Thread->CurrentFrame->State.DeferredSignalRefCount.Load() != 0);
if (SupportDeferredSignals) {
auto MustDeferSignal = (Thread->CurrentFrame->State.DeferredSignalRefCount.Load() != 0);
if (Signal == SIGSEGV && SigInfo.si_code == SEGV_ACCERR && SigInfo.si_addr == reinterpret_cast<void*>(&Thread->InterruptFaultPage)) { if (Signal == SIGSEGV && SigInfo.si_code == SEGV_ACCERR && SigInfo.si_addr == reinterpret_cast<void*>(&Thread->InterruptFaultPage)) {
if (!MustDeferSignal) { if (!MustDeferSignal) {
// We just reached the end of the outermost signal-deferring section and faulted to check for pending signals. // We just reached the end of the outermost signal-deferring section and faulted to check for pending signals.
// Pull a signal frame off the stack. // Pull a signal frame off the stack.
mprotect(reinterpret_cast<void*>(&Thread->InterruptFaultPage), sizeof(Thread->InterruptFaultPage), PROT_READ | PROT_WRITE); mprotect(reinterpret_cast<void*>(&Thread->InterruptFaultPage), sizeof(Thread->InterruptFaultPage), PROT_READ | PROT_WRITE);
if (ThreadObject->SignalInfo.DeferredSignalFrames.empty()) { if (ThreadObject->SignalInfo.DeferredSignalFrames.empty()) {
// No signals to defer. Just set the fault page back to RW and continue execution. // No signals to defer. Just set the fault page back to RW and continue execution.
// This occurs as a minor race condition between the refcount decrement and the access to the fault page. // This occurs as a minor race condition between the refcount decrement and the access to the fault page.
return;
}
const auto& Top = ThreadObject->SignalInfo.DeferredSignalFrames.back();
Signal = Top.Signal;
SigInfo = Top.Info;
// sig mask has been updated at the defer time, recover the original mask
memcpy(&_context->uc_sigmask, &Top.SigMask, sizeof(uint64_t));
ThreadObject->SignalInfo.DeferredSignalFrames.pop_back();
// Until we re-protect the page to PROT_NONE, FEX will now *permanently* defer signals and /not/ check them.
//
// In order to return /back/ to a sane state, we wait for the rt_sigreturn to happen.
// rt_sigreturn will check if there are any more deferred signals to handle
// - If there are deferred signals
// - mprotect back to PROT_NONE
// - sigreturn will trampoline out to the previous fault address check, SIGSEGV and restart
// - If there are *no* deferred signals
// - No need to mprotect, it is already RW
} else {
#ifdef _M_ARM_64
// If RefCount != 0 then that means we hit an access with nested signal-deferring sections.
// Increment the PC past the `str zr, [x1]` to continue code execution until we reach the outermost section.
ArchHelpers::Context::SetPc(UContext, ArchHelpers::Context::GetPc(UContext) + 4);
return; return;
#else
// X86 should always be doing a refcount compare and branch since we can't guarantee instruction size.
// ARM64 just always does the access to reduce branching overhead.
ERROR_AND_DIE_FMT("X86 shouldn't hit this InterruptFaultPage");
#endif
} }
} else if (FaultSafeUserMemAccess::TryHandleSafeFault(Signal, SigInfo, UContext)) {
ERROR_AND_DIE_FMT("Received invalid data to syscall. Crashing now!"); const auto& Top = ThreadObject->SignalInfo.DeferredSignalFrames.back();
Signal = Top.Signal;
SigInfo = Top.Info;
// sig mask has been updated at the defer time, recover the original mask
memcpy(&_context->uc_sigmask, &Top.SigMask, sizeof(uint64_t));
ThreadObject->SignalInfo.DeferredSignalFrames.pop_back();
// Until we re-protect the page to PROT_NONE, FEX will now *permanently* defer signals and /not/ check them.
//
// In order to return /back/ to a sane state, we wait for the rt_sigreturn to happen.
// rt_sigreturn will check if there are any more deferred signals to handle
// - If there are deferred signals
// - mprotect back to PROT_NONE
// - sigreturn will trampoline out to the previous fault address check, SIGSEGV and restart
// - If there are *no* deferred signals
// - No need to mprotect, it is already RW
} else { } else {
if (IsAsyncSignal(&SigInfo, Signal) && MustDeferSignal) { #ifdef _M_ARM_64
// If the signal is asynchronous (as determined by si_code) and FEX is in a state of needing // If RefCount != 0 then that means we hit an access with nested signal-deferring sections.
// to defer the signal, then add the signal to the thread's signal queue. // Increment the PC past the `str zr, [x1]` to continue code execution until we reach the outermost section.
LOGMAN_THROW_A_FMT(ThreadObject->SignalInfo.DeferredSignalFrames.size() != ThreadObject->SignalInfo.DeferredSignalFrames.capacity(), ArchHelpers::Context::SetPc(UContext, ArchHelpers::Context::GetPc(UContext) + 4);
"Deferred signals vector hit " return;
"capacity size. This will " #else
"likely crash! Asserting now!"); // X86 should always be doing a refcount compare and branch since we can't guarantee instruction size.
// ARM64 just always does the access to reduce branching overhead.
ERROR_AND_DIE_FMT("X86 shouldn't hit this InterruptFaultPage");
#endif
}
} else if (FaultSafeUserMemAccess::TryHandleSafeFault(Signal, SigInfo, UContext)) {
ERROR_AND_DIE_FMT("Received invalid data to syscall. Crashing now!");
} else {
if (IsAsyncSignal(&SigInfo, Signal) && MustDeferSignal) {
// If the signal is asynchronous (as determined by si_code) and FEX is in a state of needing
// to defer the signal, then add the signal to the thread's signal queue.
LOGMAN_THROW_A_FMT(ThreadObject->SignalInfo.DeferredSignalFrames.size() != ThreadObject->SignalInfo.DeferredSignalFrames.capacity(),
"Deferred signals vector hit "
"capacity size. This will "
"likely crash! Asserting now!");
ThreadObject->SignalInfo.DeferredSignalFrames.emplace_back(ThreadStateObject::DeferredSignalState { ThreadObject->SignalInfo.DeferredSignalFrames.emplace_back(ThreadStateObject::DeferredSignalState {
.Info = SigInfo, .Info = SigInfo,
.Signal = Signal, .Signal = Signal,
.SigMask = _context->uc_sigmask.__val[0], .SigMask = _context->uc_sigmask.__val[0],
}); });
uint64_t NewMask = GetNewSigMask(Signal); uint64_t NewMask = GetNewSigMask(Signal);
// Update our host signal mask so we don't hit race conditions with signals // Update our host signal mask so we don't hit race conditions with signals
// This allows us to maintain the expected signal mask through the guest signal handling and then all the way back again // This allows us to maintain the expected signal mask through the guest signal handling and then all the way back again
memcpy(&_context->uc_sigmask, &NewMask, sizeof(uint64_t)); memcpy(&_context->uc_sigmask, &NewMask, sizeof(uint64_t));
// Now update the faulting page permissions so it will fault on write. // Now update the faulting page permissions so it will fault on write.
mprotect(reinterpret_cast<void*>(&Thread->InterruptFaultPage), sizeof(Thread->InterruptFaultPage), PROT_NONE); mprotect(reinterpret_cast<void*>(&Thread->InterruptFaultPage), sizeof(Thread->InterruptFaultPage), PROT_NONE);
// Postpone the remainder of signal handling logic until we process the SIGSEGV triggered by writing to InterruptFaultPage. // Postpone the remainder of signal handling logic until we process the SIGSEGV triggered by writing to InterruptFaultPage.
return; return;
}
} }
} }
@@ -873,9 +870,7 @@ SignalDelegator::SignalDelegator(FEXCore::Context::Context* _CTX, const std::str
HostHandlers[SIGKILL].Installed = true; HostHandlers[SIGKILL].Installed = true;
HostHandlers[SIGSTOP].Installed = true; HostHandlers[SIGSTOP].Installed = true;
if (ParanoidTSO()) { if (HalfBarrierTSOEnabled()) {
UnalignedHandlerType = FEXCore::ArchHelpers::Arm64::UnalignedHandlerType::Paranoid;
} else if (HalfBarrierTSOEnabled()) {
UnalignedHandlerType = FEXCore::ArchHelpers::Arm64::UnalignedHandlerType::HalfBarrier; UnalignedHandlerType = FEXCore::ArchHelpers::Arm64::UnalignedHandlerType::HalfBarrier;
} else { } else {
UnalignedHandlerType = FEXCore::ArchHelpers::Arm64::UnalignedHandlerType::NonAtomic; UnalignedHandlerType = FEXCore::ArchHelpers::Arm64::UnalignedHandlerType::NonAtomic;
@@ -995,6 +990,7 @@ void SignalDelegator::RegisterTLSState(FEX::HLE::ThreadStateObject* Thread) {
// Set up our signal alternative stack // Set up our signal alternative stack
// This is per thread rather than per signal // This is per thread rather than per signal
Thread->SignalInfo.AltStackPtr = FEXCore::Allocator::mmap(nullptr, SIGSTKSZ * 16, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); Thread->SignalInfo.AltStackPtr = FEXCore::Allocator::mmap(nullptr, SIGSTKSZ * 16, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(Thread->SignalInfo.AltStackPtr), SIGSTKSZ * 16);
stack_t altstack {}; stack_t altstack {};
altstack.ss_sp = reinterpret_cast<void*>(reinterpret_cast<uint64_t>(Thread->SignalInfo.AltStackPtr) + 8); altstack.ss_sp = reinterpret_cast<void*>(reinterpret_cast<uint64_t>(Thread->SignalInfo.AltStackPtr) + 8);
altstack.ss_size = SIGSTKSZ * 16 - 8; altstack.ss_size = SIGSTKSZ * 16 - 8;
@@ -1005,7 +1001,7 @@ void SignalDelegator::RegisterTLSState(FEX::HLE::ThreadStateObject* Thread) {
memcpy(Thread->SignalInfo.AltStackPtr, &Thread, sizeof(void*)); memcpy(Thread->SignalInfo.AltStackPtr, &Thread, sizeof(void*));
// Protect the first page of the alt-stack for overflow protection. // Protect the first page of the alt-stack for overflow protection.
mprotect(Thread->SignalInfo.AltStackPtr, 4096, PROT_READ); mprotect(Thread->SignalInfo.AltStackPtr, FEXCore::Utils::FEX_PAGE_SIZE, PROT_READ);
// Register the alt stack // Register the alt stack
const int Result = sigaltstack(&altstack, nullptr); const int Result = sigaltstack(&altstack, nullptr);
@@ -165,7 +165,6 @@ private:
FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE); FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE);
const fextl::string ApplicationName; const fextl::string ApplicationName;
FEX_CONFIG_OPT(ParanoidTSO, PARANOIDTSO);
FEX_CONFIG_OPT(HalfBarrierTSOEnabled, HALFBARRIERTSOENABLED); FEX_CONFIG_OPT(HalfBarrierTSOEnabled, HALFBARRIERTSOENABLED);
FEXCore::ArchHelpers::Arm64::UnalignedHandlerType UnalignedHandlerType {FEXCore::ArchHelpers::Arm64::UnalignedHandlerType::HalfBarrier}; FEXCore::ArchHelpers::Arm64::UnalignedHandlerType UnalignedHandlerType {FEXCore::ArchHelpers::Arm64::UnalignedHandlerType::HalfBarrier};
@@ -707,7 +707,7 @@ uint64_t SyscallHandler::HandleBRK(FEXCore::Core::CpuStateFrame* Frame, void* Ad
DataSpaceMappedSize = 0; DataSpaceMappedSize = 0;
} else { } else {
uint64_t NewSize = NewEnd - DataSpace; uint64_t NewSize = NewEnd - DataSpace;
uint64_t NewSizeAligned = FEXCore::AlignUp(NewSize, 4096); uint64_t NewSizeAligned = FEXCore::AlignUp(NewSize, FEXCore::Utils::FEX_PAGE_SIZE);
if (NewSizeAligned < DataSpaceMappedSize) { if (NewSizeAligned < DataSpaceMappedSize) {
// If we are shrinking the brk then munmap the ranges // If we are shrinking the brk then munmap the ranges
@@ -721,7 +721,7 @@ uint64_t SyscallHandler::HandleBRK(FEXCore::Core::CpuStateFrame* Frame, void* Ad
DataSpaceMappedSize = NewSizeAligned; DataSpaceMappedSize = NewSizeAligned;
} else if (NewSize > DataSpaceMappedSize) { } else if (NewSize > DataSpaceMappedSize) {
uint64_t AllocateNewSize = FEXCore::AlignUp(NewSize, 4096) - DataSpaceMappedSize; uint64_t AllocateNewSize = FEXCore::AlignUp(NewSize, FEXCore::Utils::FEX_PAGE_SIZE) - DataSpaceMappedSize;
if (!Is64BitMode() && (DataSpace + DataSpaceMappedSize + AllocateNewSize > 0x1'0000'0000ULL)) { if (!Is64BitMode() && (DataSpace + DataSpaceMappedSize + AllocateNewSize > 0x1'0000'0000ULL)) {
// If we are 32bit and we tried going about the 32bit limit then out of memory // If we are 32bit and we tried going about the 32bit limit then out of memory
return DataSpace + DataSpaceSize; return DataSpace + DataSpaceSize;
@@ -138,9 +138,7 @@ public:
SyscallPtrArg5 Ptr5; SyscallPtrArg5 Ptr5;
SyscallPtrArg6 Ptr6; SyscallPtrArg6 Ptr6;
}; };
int32_t HostSyscallNumber;
uint8_t NumArgs; uint8_t NumArgs;
FEXCore::IR::SyscallFlags Flags;
#ifdef DEBUG_STRACE #ifdef DEBUG_STRACE
fextl::string StraceFmt; fextl::string StraceFmt;
#endif #endif
@@ -150,32 +148,14 @@ public:
return &Definitions.at(Syscall); return &Definitions.at(Syscall);
} }
FEXCore::HLE::SyscallABI GetSyscallABI(uint64_t Syscall) override { virtual void RegisterSyscall_32(int SyscallNumber,
if (NeedsSeccomp) {
// Override ABI if seccomp is enabled.
return {FEXCore::HLE::SyscallArguments::MAX_ARGS, true, -1};
}
auto& Def = Definitions.at(Syscall);
return {Def.NumArgs, true, Def.HostSyscallNumber};
}
FEXCore::IR::SyscallFlags GetSyscallFlags(uint64_t Syscall) const override {
if (NeedsSeccomp) {
// Override flags if seccomp is enabled.
return FEXCore::IR::SyscallFlags::DEFAULT;
}
auto& Def = Definitions.at(Syscall);
return Def.Flags;
}
virtual void RegisterSyscall_32(int SyscallNumber, int32_t HostSyscallNumber, FEXCore::IR::SyscallFlags Flags,
#ifdef DEBUG_STRACE #ifdef DEBUG_STRACE
const fextl::string& TraceFormatString, const fextl::string& TraceFormatString,
#endif #endif
void* SyscallHandler, int ArgumentCount) { void* SyscallHandler, int ArgumentCount) {
} }
virtual void RegisterSyscall_64(int SyscallNumber, int32_t HostSyscallNumber, FEXCore::IR::SyscallFlags Flags, virtual void RegisterSyscall_64(int SyscallNumber,
#ifdef DEBUG_STRACE #ifdef DEBUG_STRACE
const fextl::string& TraceFormatString, const fextl::string& TraceFormatString,
#endif #endif
@@ -661,14 +641,8 @@ inline static uint64_t futimesat_compat(int dirfd, const char* pathname, const T
} // namespace FEX::HLE } // namespace FEX::HLE
// Registers syscall for both 32bit and 64bit // Registers syscall for both 32bit and 64bit
#define REGISTER_SYSCALL_IMPL(name, lambda) REGISTER_SYSCALL_IMPL_INTERNAL(name, ~0, FEXCore::IR::SyscallFlags::DEFAULT, lambda) #define REGISTER_SYSCALL_IMPL(name, lambda) \
do { \
#define REGISTER_SYSCALL_IMPL_FLAGS(name, flags, lambda) REGISTER_SYSCALL_IMPL_INTERNAL(name, ~0, flags, lambda) FEX::HLE::x64::RegisterSyscall(Handler, FEX::HLE::x64::SYSCALL_x64_##name, #name, (lambda)); \
FEX::HLE::x32::RegisterSyscall(Handler, FEX::HLE::x32::SYSCALL_x86_##name, #name, (lambda)); \
#define REGISTER_SYSCALL_IMPL_PASS_FLAGS(name, flags, lambda) REGISTER_SYSCALL_IMPL_INTERNAL(name, SYSCALL_DEF(name), flags, lambda)
#define REGISTER_SYSCALL_IMPL_INTERNAL(name, number, flags, lambda) \
do { \
FEX::HLE::x64::RegisterSyscall(Handler, FEX::HLE::x64::SYSCALL_x64_##name, (number), (flags), #name, (lambda)); \
FEX::HLE::x32::RegisterSyscall(Handler, FEX::HLE::x32::SYSCALL_x86_##name, (number), (flags), #name, (lambda)); \
} while (false) } while (false)
@@ -19,10 +19,9 @@ namespace FEX::HLE {
void RegisterEpoll(FEX::HLE::SyscallHandler* Handler) { void RegisterEpoll(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_FLAGS(epoll_create, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(epoll_create, [](FEXCore::Core::CpuStateFrame* Frame, int size) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int size) -> uint64_t { uint64_t Result = epoll_create(size);
uint64_t Result = epoll_create(size); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
} }
} // namespace FEX::HLE } // namespace FEX::HLE
@@ -31,127 +31,110 @@ $end_info$
namespace FEX::HLE { namespace FEX::HLE {
void RegisterFD(FEX::HLE::SyscallHandler* Handler) { void RegisterFD(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_FLAGS(poll, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(poll, [](FEXCore::Core::CpuStateFrame* Frame, struct pollfd* fds, nfds_t nfds, int timeout) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, struct pollfd* fds, nfds_t nfds, int timeout) -> uint64_t { if (nfds) {
if (nfds) { // fds is allowed to be garbage if nfds is zero.
// fds is allowed to be garbage if nfds is zero. FaultSafeUserMemAccess::VerifyIsWritable(fds, sizeof(struct pollfd) * nfds);
FaultSafeUserMemAccess::VerifyIsWritable(fds, sizeof(struct pollfd) * nfds); }
} uint64_t Result = ::poll(fds, nfds, timeout);
uint64_t Result = ::poll(fds, nfds, timeout); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(open, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(open, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, int flags, uint32_t mode) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, int flags, uint32_t mode) -> uint64_t { flags = FEX::HLE::RemapFromX86Flags(flags);
flags = FEX::HLE::RemapFromX86Flags(flags); uint64_t Result = FEX::HLE::_SyscallHandler->FM.Open(pathname, flags, mode);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.Open(pathname, flags, mode); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(close, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(close, [](FEXCore::Core::CpuStateFrame* Frame, int fd) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int fd) -> uint64_t { uint64_t Result = FEX::HLE::_SyscallHandler->FM.Close(fd);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.Close(fd); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(chown, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(chown, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, uid_t owner, gid_t group) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, uid_t owner, gid_t group) -> uint64_t { uint64_t Result = ::chown(pathname, owner, group);
uint64_t Result = ::chown(pathname, owner, group); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(lchown, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(lchown, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, uid_t owner, gid_t group) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, uid_t owner, gid_t group) -> uint64_t { uint64_t Result = ::lchown(pathname, owner, group);
uint64_t Result = ::lchown(pathname, owner, group); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(access, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(access, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, int mode) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, int mode) -> uint64_t { uint64_t Result = FEX::HLE::_SyscallHandler->FM.Access(pathname, mode);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.Access(pathname, mode); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(pipe, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(pipe, [](FEXCore::Core::CpuStateFrame* Frame, int pipefd[2]) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int pipefd[2]) -> uint64_t { uint64_t Result = ::pipe(pipefd);
uint64_t Result = ::pipe(pipefd); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(dup3, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(dup3, [](FEXCore::Core::CpuStateFrame* Frame, int oldfd, int newfd, int flags) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int oldfd, int newfd, int flags) -> uint64_t { flags = FEX::HLE::RemapFromX86Flags(flags);
flags = FEX::HLE::RemapFromX86Flags(flags); uint64_t Result = ::dup3(oldfd, newfd, flags);
uint64_t Result = ::dup3(oldfd, newfd, flags); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(inotify_init, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(inotify_init, [](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t { uint64_t Result = ::inotify_init();
uint64_t Result = ::inotify_init(); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(openat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(openat, [](FEXCore::Core::CpuStateFrame* Frame, int dirfs, const char* pathname, int flags, uint32_t mode) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int dirfs, const char* pathname, int flags, uint32_t mode) -> uint64_t { flags = FEX::HLE::RemapFromX86Flags(flags);
flags = FEX::HLE::RemapFromX86Flags(flags); uint64_t Result = FEX::HLE::_SyscallHandler->FM.Openat(dirfs, pathname, flags, mode);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.Openat(dirfs, pathname, flags, mode); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(readlinkat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(readlinkat, [](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, char* buf, size_t bufsiz) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, char* buf, size_t bufsiz) -> uint64_t { uint64_t Result = FEX::HLE::_SyscallHandler->FM.Readlinkat(dirfd, pathname, buf, bufsiz);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.Readlinkat(dirfd, pathname, buf, bufsiz); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(faccessat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(faccessat, [](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, int mode) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, int mode) -> uint64_t { uint64_t Result = FEX::HLE::_SyscallHandler->FM.FAccessat(dirfd, pathname, mode);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.FAccessat(dirfd, pathname, mode); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(faccessat2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(faccessat2, [](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, int mode, int flags) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, int mode, int flags) -> uint64_t { uint64_t Result = FEX::HLE::_SyscallHandler->FM.FAccessat2(dirfd, pathname, mode, flags);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.FAccessat2(dirfd, pathname, mode, flags); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(openat2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(
[](FEXCore::Core::CpuStateFrame* Frame, int dirfs, const char* pathname, struct open_how* how, size_t usize) -> uint64_t { openat2, [](FEXCore::Core::CpuStateFrame* Frame, int dirfs, const char* pathname, struct open_how* how, size_t usize) -> uint64_t {
open_how HostHow {}; open_how HostHow {};
size_t HostSize = std::min(sizeof(open_how), usize); size_t HostSize = std::min(sizeof(open_how), usize);
memcpy(&HostHow, how, HostSize); memcpy(&HostHow, how, HostSize);
HostHow.flags = FEX::HLE::RemapFromX86Flags(HostHow.flags); HostHow.flags = FEX::HLE::RemapFromX86Flags(HostHow.flags);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.Openat2(dirfs, pathname, &HostHow, HostSize); uint64_t Result = FEX::HLE::_SyscallHandler->FM.Openat2(dirfs, pathname, &HostHow, HostSize);
SYSCALL_ERRNO(); SYSCALL_ERRNO();
}); });
REGISTER_SYSCALL_IMPL_FLAGS(eventfd, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(eventfd, [](FEXCore::Core::CpuStateFrame* Frame, uint32_t count) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, uint32_t count) -> uint64_t { uint64_t Result = ::syscall(SYSCALL_DEF(eventfd2), count, 0);
uint64_t Result = ::syscall(SYSCALL_DEF(eventfd2), count, 0); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(pipe2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(pipe2, [](FEXCore::Core::CpuStateFrame* Frame, int pipefd[2], int flags) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int pipefd[2], int flags) -> uint64_t { flags = FEX::HLE::RemapFromX86Flags(flags);
flags = FEX::HLE::RemapFromX86Flags(flags); uint64_t Result = ::pipe2(pipefd, flags);
uint64_t Result = ::pipe2(pipefd, flags); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS( REGISTER_SYSCALL_IMPL(
statx, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, statx, [](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, int flags, uint32_t mask, struct statx* statxbuf) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int dirfd, const char* pathname, int flags, uint32_t mask, struct statx* statxbuf) -> uint64_t {
// Flags don't need remapped // Flags don't need remapped
uint64_t Result = FEX::HLE::_SyscallHandler->FM.Statx(dirfd, pathname, flags, mask, statxbuf); uint64_t Result = FEX::HLE::_SyscallHandler->FM.Statx(dirfd, pathname, flags, mask, statxbuf);
SYSCALL_ERRNO(); SYSCALL_ERRNO();
}); });
REGISTER_SYSCALL_IMPL_FLAGS(close_range, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(close_range, [](FEXCore::Core::CpuStateFrame* Frame, unsigned int first, unsigned int last, unsigned int flags) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, unsigned int first, unsigned int last, unsigned int flags) -> uint64_t { uint64_t Result = FEX::HLE::_SyscallHandler->FM.CloseRange(first, last, flags);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.CloseRange(first, last, flags); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
} }
} // namespace FEX::HLE } // namespace FEX::HLE
@@ -25,65 +25,55 @@ namespace FEX::HLE {
void RegisterFS(FEX::HLE::SyscallHandler* Handler) { void RegisterFS(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_FLAGS(rename, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(rename, [](FEXCore::Core::CpuStateFrame* Frame, const char* oldpath, const char* newpath) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* oldpath, const char* newpath) -> uint64_t { uint64_t Result = ::rename(oldpath, newpath);
uint64_t Result = ::rename(oldpath, newpath); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(mkdir, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(mkdir, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, mode_t mode) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, mode_t mode) -> uint64_t { uint64_t Result = ::mkdir(pathname, mode);
uint64_t Result = ::mkdir(pathname, mode); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(rmdir, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(rmdir, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname) -> uint64_t { uint64_t Result = ::rmdir(pathname);
uint64_t Result = ::rmdir(pathname); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(link, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(link, [](FEXCore::Core::CpuStateFrame* Frame, const char* oldpath, const char* newpath) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* oldpath, const char* newpath) -> uint64_t { uint64_t Result = ::link(oldpath, newpath);
uint64_t Result = ::link(oldpath, newpath); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(unlink, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(unlink, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname) -> uint64_t { uint64_t Result = ::unlink(pathname);
uint64_t Result = ::unlink(pathname); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(symlink, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(symlink, [](FEXCore::Core::CpuStateFrame* Frame, const char* target, const char* linkpath) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* target, const char* linkpath) -> uint64_t { uint64_t Result = ::symlink(target, linkpath);
uint64_t Result = ::symlink(target, linkpath); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(readlink, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(readlink, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, char* buf, size_t bufsiz) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, char* buf, size_t bufsiz) -> uint64_t { uint64_t Result = FEX::HLE::_SyscallHandler->FM.Readlink(pathname, buf, bufsiz);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.Readlink(pathname, buf, bufsiz); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(chmod, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(chmod, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, mode_t mode) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, mode_t mode) -> uint64_t { uint64_t Result = ::chmod(pathname, mode);
uint64_t Result = ::chmod(pathname, mode); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(mknod, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(mknod, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, mode_t mode, dev_t dev) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, mode_t mode, dev_t dev) -> uint64_t { uint64_t Result = FEX::HLE::_SyscallHandler->FM.Mknod(pathname, mode, dev);
uint64_t Result = FEX::HLE::_SyscallHandler->FM.Mknod(pathname, mode, dev); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(creat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(creat, [](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, mode_t mode) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const char* pathname, mode_t mode) -> uint64_t { uint64_t Result = ::creat(pathname, mode);
uint64_t Result = ::creat(pathname, mode); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL( REGISTER_SYSCALL_IMPL(
setxattr, [](FEXCore::Core::CpuStateFrame* Frame, const char* path, const char* name, const void* value, size_t size, int flags) -> uint64_t { setxattr, [](FEXCore::Core::CpuStateFrame* Frame, const char* path, const char* name, const void* value, size_t size, int flags) -> uint64_t {
@@ -19,16 +19,14 @@ namespace FEX::HLE {
void RegisterIO(FEX::HLE::SyscallHandler* Handler) { void RegisterIO(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_FLAGS(iopl, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(iopl, [](FEXCore::Core::CpuStateFrame* Frame, int level) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int level) -> uint64_t { // Just claim we don't have permission
// Just claim we don't have permission return -EPERM;
return -EPERM; });
});
REGISTER_SYSCALL_IMPL_FLAGS(ioperm, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(ioperm, [](FEXCore::Core::CpuStateFrame* Frame, unsigned long from, unsigned long num, int turn_on) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, unsigned long from, unsigned long num, int turn_on) -> uint64_t { // ioperm not available on our architecture
// ioperm not available on our architecture return -EPERM;
return -EPERM; });
});
} }
} // namespace FEX::HLE } // namespace FEX::HLE
@@ -37,68 +37,65 @@ using cap_user_data_t = void*;
void RegisterInfo(FEX::HLE::SyscallHandler* Handler) { void RegisterInfo(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_FLAGS( REGISTER_SYSCALL_IMPL(uname, [](FEXCore::Core::CpuStateFrame* Frame, struct utsname* buf) -> uint64_t {
uname, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, [](FEXCore::Core::CpuStateFrame* Frame, struct utsname* buf) -> uint64_t { auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
struct utsname Local {}; struct utsname Local {};
if (::uname(&Local) == 0) { if (::uname(&Local) == 0) {
memcpy(buf->nodename, Local.nodename, sizeof(Local.nodename)); memcpy(buf->nodename, Local.nodename, sizeof(Local.nodename));
static_assert(sizeof(Local.nodename) <= sizeof(buf->nodename)); static_assert(sizeof(Local.nodename) <= sizeof(buf->nodename));
memcpy(buf->domainname, Local.domainname, sizeof(Local.domainname)); memcpy(buf->domainname, Local.domainname, sizeof(Local.domainname));
static_assert(sizeof(Local.domainname) <= sizeof(buf->domainname)); static_assert(sizeof(Local.domainname) <= sizeof(buf->domainname));
} else { } else {
strcpy(buf->nodename, "FEXCore"); strcpy(buf->nodename, "FEXCore");
LogMan::Msg::EFmt("Couldn't determine host nodename. Defaulting to '{}'", buf->nodename); LogMan::Msg::EFmt("Couldn't determine host nodename. Defaulting to '{}'", buf->nodename);
} }
strcpy(buf->sysname, "Linux"); strcpy(buf->sysname, "Linux");
uint32_t GuestVersion = FEX::HLE::_SyscallHandler->GetGuestKernelVersion(); uint32_t GuestVersion = FEX::HLE::_SyscallHandler->GetGuestKernelVersion();
if (Thread->persona & UNAME26) { if (Thread->persona & UNAME26) {
// Kernel version converts from 6.x.y to 2.6.60+x. // Kernel version converts from 6.x.y to 2.6.60+x.
GuestVersion = FEX::HLE::SyscallHandler::KernelVersion(2, 6, 60 + FEX::HLE::SyscallHandler::KernelMinor(GuestVersion)); GuestVersion = FEX::HLE::SyscallHandler::KernelVersion(2, 6, 60 + FEX::HLE::SyscallHandler::KernelMinor(GuestVersion));
} }
snprintf(buf->release, sizeof(buf->release), "%d.%d.%d", FEX::HLE::SyscallHandler::KernelMajor(GuestVersion), snprintf(buf->release, sizeof(buf->release), "%d.%d.%d", FEX::HLE::SyscallHandler::KernelMajor(GuestVersion),
FEX::HLE::SyscallHandler::KernelMinor(GuestVersion), FEX::HLE::SyscallHandler::KernelPatch(GuestVersion)); FEX::HLE::SyscallHandler::KernelMinor(GuestVersion), FEX::HLE::SyscallHandler::KernelPatch(GuestVersion));
const char version[] = "#" GIT_DESCRIBE_STRING " SMP " __DATE__ " " __TIME__; const char version[] = "#" GIT_DESCRIBE_STRING " SMP " __DATE__ " " __TIME__;
strcpy(buf->version, version); strcpy(buf->version, version);
static_assert(sizeof(version) <= sizeof(buf->version), "uname version define became too large!"); static_assert(sizeof(version) <= sizeof(buf->version), "uname version define became too large!");
if (Thread->persona & PER_LINUX32) { if (Thread->persona & PER_LINUX32) {
// Tell the guest that we are a 32bit kernel // Tell the guest that we are a 32bit kernel
strcpy(buf->machine, "i686"); strcpy(buf->machine, "i686");
} else { } else {
// Tell the guest that we are a 64bit kernel // Tell the guest that we are a 64bit kernel
strcpy(buf->machine, "x86_64"); strcpy(buf->machine, "x86_64");
} }
return 0; return 0;
}); });
REGISTER_SYSCALL_IMPL_PASS_FLAGS(personality, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(personality, [](FEXCore::Core::CpuStateFrame* Frame, uint32_t persona) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, uint32_t persona) -> uint64_t { auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
if (persona == ~0U) { if (persona == ~0U) {
// Special case, only queries the persona. // Special case, only queries the persona.
return Thread->persona; return Thread->persona;
} }
// Mask off `PER_LINUX32` because AArch64 doesn't support it. // Mask off `PER_LINUX32` because AArch64 doesn't support it.
uint32_t NewPersona = persona & ~PER_LINUX32; uint32_t NewPersona = persona & ~PER_LINUX32;
// This syscall can not physically fail with PER_LINUX32 masked off. // This syscall can not physically fail with PER_LINUX32 masked off.
// It also can not fail on a real x86 kernel. // It also can not fail on a real x86 kernel.
(void)::syscall(SYSCALL_DEF(personality), NewPersona); (void)::syscall(SYSCALL_DEF(personality), NewPersona);
// Return the old persona while setting the new one. // Return the old persona while setting the new one.
auto OldPersona = Thread->persona; auto OldPersona = Thread->persona;
Thread->persona = persona; Thread->persona = persona;
return OldPersona; return OldPersona;
}); });
REGISTER_SYSCALL_IMPL_FLAGS(seccomp, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(seccomp, [](FEXCore::Core::CpuStateFrame* Frame, unsigned int operation, unsigned int flags, void* args) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, unsigned int operation, unsigned int flags, void* args) -> uint64_t { return FEX::HLE::_SyscallHandler->SeccompEmulator.Handle(Frame, operation, flags, args);
return FEX::HLE::_SyscallHandler->SeccompEmulator.Handle(Frame, operation, flags, args); });
});
REGISTER_SYSCALL_IMPL( REGISTER_SYSCALL_IMPL(
ptrace, [](FEXCore::Core::CpuStateFrame* Frame, int /*enum __ptrace_request*/ request, pid_t pid, void* addr, void* data) -> uint64_t { ptrace, [](FEXCore::Core::CpuStateFrame* Frame, int /*enum __ptrace_request*/ request, pid_t pid, void* addr, void* data) -> uint64_t {
uint64_t Result {}; uint64_t Result {};
@@ -22,20 +22,18 @@ namespace FEX::HLE {
void RegisterMemory(FEX::HLE::SyscallHandler* Handler) { void RegisterMemory(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_FLAGS(brk, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(brk, [](FEXCore::Core::CpuStateFrame* Frame, void* addr) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, void* addr) -> uint64_t { uint64_t Result = FEX::HLE::_SyscallHandler->HandleBRK(Frame, addr);
uint64_t Result = FEX::HLE::_SyscallHandler->HandleBRK(Frame, addr); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(madvise, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(madvise, [](FEXCore::Core::CpuStateFrame* Frame, void* addr, size_t length, int32_t advice) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, void* addr, size_t length, int32_t advice) -> uint64_t { uint64_t Result = ::madvise(addr, length, advice);
uint64_t Result = ::madvise(addr, length, advice);
if (Result != -1) { if (Result != -1) {
FEX::HLE::_SyscallHandler->TrackMadvise(Frame->Thread, (uintptr_t)addr, length, advice); FEX::HLE::_SyscallHandler->TrackMadvise(Frame->Thread, (uintptr_t)addr, length, advice);
} }
SYSCALL_ERRNO(); SYSCALL_ERRNO();
}); });
} }
} // namespace FEX::HLE } // namespace FEX::HLE
@@ -213,349 +213,187 @@ uint64_t SyscallPassthrough7(FEXCore::Core::CpuStateFrame* Frame, uint64_t arg1,
void RegisterCommon(FEX::HLE::SyscallHandler* Handler) { void RegisterCommon(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_PASS_FLAGS(read, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, SyscallPassthrough3<SYSCALL_DEF(read)>); REGISTER_SYSCALL_IMPL(read, SyscallPassthrough3<SYSCALL_DEF(read)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(write, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(write, SyscallPassthrough3<SYSCALL_DEF(write)>);
SyscallPassthrough3<SYSCALL_DEF(write)>); REGISTER_SYSCALL_IMPL(lseek, SyscallPassthrough3<SYSCALL_DEF(lseek)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(lseek, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(sched_yield, SyscallPassthrough0<SYSCALL_DEF(sched_yield)>);
SyscallPassthrough3<SYSCALL_DEF(lseek)>); REGISTER_SYSCALL_IMPL(msync, SyscallPassthrough3<SYSCALL_DEF(msync)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_yield, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(mincore, SyscallPassthrough3<SYSCALL_DEF(mincore)>);
SyscallPassthrough0<SYSCALL_DEF(sched_yield)>); REGISTER_SYSCALL_IMPL(shmget, SyscallPassthrough3<SYSCALL_DEF(shmget)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(msync, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(shmctl, SyscallPassthrough3<SYSCALL_DEF(shmctl)>);
SyscallPassthrough3<SYSCALL_DEF(msync)>); REGISTER_SYSCALL_IMPL(getpid, SyscallPassthrough0<SYSCALL_DEF(getpid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mincore, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(socket, SyscallPassthrough3<SYSCALL_DEF(socket)>);
SyscallPassthrough3<SYSCALL_DEF(mincore)>); REGISTER_SYSCALL_IMPL(connect, SyscallPassthrough3<SYSCALL_DEF(connect)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(shmget, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(sendto, SyscallPassthrough6<SYSCALL_DEF(sendto)>);
SyscallPassthrough3<SYSCALL_DEF(shmget)>); REGISTER_SYSCALL_IMPL(recvfrom, SyscallPassthrough6<SYSCALL_DEF(recvfrom)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(shmctl, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(shutdown, SyscallPassthrough2<SYSCALL_DEF(shutdown)>);
SyscallPassthrough3<SYSCALL_DEF(shmctl)>); REGISTER_SYSCALL_IMPL(bind, SyscallPassthrough3<SYSCALL_DEF(bind)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getpid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(listen, SyscallPassthrough2<SYSCALL_DEF(listen)>);
SyscallPassthrough0<SYSCALL_DEF(getpid)>); REGISTER_SYSCALL_IMPL(getsockname, SyscallPassthrough3<SYSCALL_DEF(getsockname)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(socket, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(getpeername, SyscallPassthrough3<SYSCALL_DEF(getpeername)>);
SyscallPassthrough3<SYSCALL_DEF(socket)>); REGISTER_SYSCALL_IMPL(socketpair, SyscallPassthrough4<SYSCALL_DEF(socketpair)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(connect, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(kill, SyscallPassthrough2<SYSCALL_DEF(kill)>);
SyscallPassthrough3<SYSCALL_DEF(connect)>); REGISTER_SYSCALL_IMPL(semget, SyscallPassthrough3<SYSCALL_DEF(semget)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sendto, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(msgget, SyscallPassthrough2<SYSCALL_DEF(msgget)>);
SyscallPassthrough6<SYSCALL_DEF(sendto)>); REGISTER_SYSCALL_IMPL(msgsnd, SyscallPassthrough4<SYSCALL_DEF(msgsnd)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(recvfrom, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(msgrcv, SyscallPassthrough5<SYSCALL_DEF(msgrcv)>);
SyscallPassthrough6<SYSCALL_DEF(recvfrom)>); REGISTER_SYSCALL_IMPL(msgctl, SyscallPassthrough3<SYSCALL_DEF(msgctl)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(shutdown, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(flock, SyscallPassthrough2<SYSCALL_DEF(flock)>);
SyscallPassthrough2<SYSCALL_DEF(shutdown)>); REGISTER_SYSCALL_IMPL(fsync, SyscallPassthrough1<SYSCALL_DEF(fsync)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(bind, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, SyscallPassthrough3<SYSCALL_DEF(bind)>); REGISTER_SYSCALL_IMPL(fdatasync, SyscallPassthrough1<SYSCALL_DEF(fdatasync)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(listen, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(truncate, SyscallPassthrough2<SYSCALL_DEF(truncate)>);
SyscallPassthrough2<SYSCALL_DEF(listen)>); REGISTER_SYSCALL_IMPL(ftruncate, SyscallPassthrough2<SYSCALL_DEF(ftruncate)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getsockname, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(getcwd, SyscallPassthrough2<SYSCALL_DEF(getcwd)>);
SyscallPassthrough3<SYSCALL_DEF(getsockname)>); REGISTER_SYSCALL_IMPL(chdir, SyscallPassthrough1<SYSCALL_DEF(chdir)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getpeername, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(fchdir, SyscallPassthrough1<SYSCALL_DEF(fchdir)>);
SyscallPassthrough3<SYSCALL_DEF(getpeername)>); REGISTER_SYSCALL_IMPL(fchmod, SyscallPassthrough2<SYSCALL_DEF(fchmod)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(socketpair, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(fchown, SyscallPassthrough3<SYSCALL_DEF(fchown)>);
SyscallPassthrough4<SYSCALL_DEF(socketpair)>); REGISTER_SYSCALL_IMPL(umask, SyscallPassthrough1<SYSCALL_DEF(umask)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(kill, SyscallFlags::DEFAULT, SyscallPassthrough2<SYSCALL_DEF(kill)>); REGISTER_SYSCALL_IMPL(getuid, SyscallPassthrough0<SYSCALL_DEF(getuid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(semget, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(syslog, SyscallPassthrough3<SYSCALL_DEF(syslog)>);
SyscallPassthrough3<SYSCALL_DEF(semget)>); REGISTER_SYSCALL_IMPL(getgid, SyscallPassthrough0<SYSCALL_DEF(getgid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(msgget, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(setuid, SyscallPassthrough1<SYSCALL_DEF(setuid)>);
SyscallPassthrough2<SYSCALL_DEF(msgget)>); REGISTER_SYSCALL_IMPL(setgid, SyscallPassthrough1<SYSCALL_DEF(setgid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(msgsnd, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(geteuid, SyscallPassthrough0<SYSCALL_DEF(geteuid)>);
SyscallPassthrough4<SYSCALL_DEF(msgsnd)>); REGISTER_SYSCALL_IMPL(getegid, SyscallPassthrough0<SYSCALL_DEF(getegid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(msgrcv, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(setpgid, SyscallPassthrough2<SYSCALL_DEF(setpgid)>);
SyscallPassthrough5<SYSCALL_DEF(msgrcv)>); REGISTER_SYSCALL_IMPL(getppid, SyscallPassthrough0<SYSCALL_DEF(getppid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(msgctl, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(setsid, SyscallPassthrough0<SYSCALL_DEF(setsid)>);
SyscallPassthrough3<SYSCALL_DEF(msgctl)>); REGISTER_SYSCALL_IMPL(setreuid, SyscallPassthrough2<SYSCALL_DEF(setreuid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(flock, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(setregid, SyscallPassthrough2<SYSCALL_DEF(setregid)>);
SyscallPassthrough2<SYSCALL_DEF(flock)>); REGISTER_SYSCALL_IMPL(getgroups, SyscallPassthrough2<SYSCALL_DEF(getgroups)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fsync, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(setgroups, SyscallPassthrough2<SYSCALL_DEF(setgroups)>);
SyscallPassthrough1<SYSCALL_DEF(fsync)>); REGISTER_SYSCALL_IMPL(setresuid, SyscallPassthrough3<SYSCALL_DEF(setresuid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fdatasync, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(getresuid, SyscallPassthrough3<SYSCALL_DEF(getresuid)>);
SyscallPassthrough1<SYSCALL_DEF(fdatasync)>); REGISTER_SYSCALL_IMPL(setresgid, SyscallPassthrough3<SYSCALL_DEF(setresgid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(truncate, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(getresgid, SyscallPassthrough3<SYSCALL_DEF(getresgid)>);
SyscallPassthrough2<SYSCALL_DEF(truncate)>); REGISTER_SYSCALL_IMPL(getpgid, SyscallPassthrough1<SYSCALL_DEF(getpgid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(ftruncate, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(setfsuid, SyscallPassthrough1<SYSCALL_DEF(setfsuid)>);
SyscallPassthrough2<SYSCALL_DEF(ftruncate)>); REGISTER_SYSCALL_IMPL(setfsgid, SyscallPassthrough1<SYSCALL_DEF(setfsgid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getcwd, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(getsid, SyscallPassthrough1<SYSCALL_DEF(getsid)>);
SyscallPassthrough2<SYSCALL_DEF(getcwd)>); REGISTER_SYSCALL_IMPL(capget, SyscallPassthrough2<SYSCALL_DEF(capget)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(chdir, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(capset, SyscallPassthrough2<SYSCALL_DEF(capset)>);
SyscallPassthrough1<SYSCALL_DEF(chdir)>); REGISTER_SYSCALL_IMPL(getpriority, SyscallPassthrough2<SYSCALL_DEF(getpriority)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fchdir, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(setpriority, SyscallPassthrough3<SYSCALL_DEF(setpriority)>);
SyscallPassthrough1<SYSCALL_DEF(fchdir)>); REGISTER_SYSCALL_IMPL(sched_setparam, SyscallPassthrough2<SYSCALL_DEF(sched_setparam)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fchmod, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(sched_getparam, SyscallPassthrough2<SYSCALL_DEF(sched_getparam)>);
SyscallPassthrough2<SYSCALL_DEF(fchmod)>); REGISTER_SYSCALL_IMPL(sched_setscheduler, SyscallPassthrough3<SYSCALL_DEF(sched_setscheduler)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fchown, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(sched_getscheduler, SyscallPassthrough1<SYSCALL_DEF(sched_getscheduler)>);
SyscallPassthrough3<SYSCALL_DEF(fchown)>); REGISTER_SYSCALL_IMPL(sched_get_priority_max, SyscallPassthrough1<SYSCALL_DEF(sched_get_priority_max)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(umask, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(sched_get_priority_min, SyscallPassthrough1<SYSCALL_DEF(sched_get_priority_min)>);
SyscallPassthrough1<SYSCALL_DEF(umask)>); REGISTER_SYSCALL_IMPL(mlock, SyscallPassthrough2<SYSCALL_DEF(mlock)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(munlock, SyscallPassthrough2<SYSCALL_DEF(munlock)>);
SyscallPassthrough0<SYSCALL_DEF(getuid)>); REGISTER_SYSCALL_IMPL(pivot_root, SyscallPassthrough2<SYSCALL_DEF(pivot_root)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(syslog, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(chroot, SyscallPassthrough1<SYSCALL_DEF(chroot)>);
SyscallPassthrough3<SYSCALL_DEF(syslog)>); REGISTER_SYSCALL_IMPL(sync, SyscallPassthrough0<SYSCALL_DEF(sync)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(acct, SyscallPassthrough1<SYSCALL_DEF(acct)>);
SyscallPassthrough0<SYSCALL_DEF(getgid)>); REGISTER_SYSCALL_IMPL(mount, SyscallPassthrough5<SYSCALL_DEF(mount)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(umount2, SyscallPassthrough2<SYSCALL_DEF(umount2)>);
SyscallPassthrough1<SYSCALL_DEF(setuid)>); REGISTER_SYSCALL_IMPL(swapon, SyscallPassthrough2<SYSCALL_DEF(swapon)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(swapoff, SyscallPassthrough1<SYSCALL_DEF(swapoff)>);
SyscallPassthrough1<SYSCALL_DEF(setgid)>); REGISTER_SYSCALL_IMPL(gettid, SyscallPassthrough0<SYSCALL_DEF(gettid)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(geteuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(fsetxattr, SyscallPassthrough5<SYSCALL_DEF(fsetxattr)>);
SyscallPassthrough0<SYSCALL_DEF(geteuid)>); REGISTER_SYSCALL_IMPL(fgetxattr, SyscallPassthrough4<SYSCALL_DEF(fgetxattr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getegid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(flistxattr, SyscallPassthrough3<SYSCALL_DEF(flistxattr)>);
SyscallPassthrough0<SYSCALL_DEF(getegid)>); REGISTER_SYSCALL_IMPL(fremovexattr, SyscallPassthrough2<SYSCALL_DEF(fremovexattr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setpgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(tkill, SyscallPassthrough2<SYSCALL_DEF(tkill)>);
SyscallPassthrough2<SYSCALL_DEF(setpgid)>); REGISTER_SYSCALL_IMPL(sched_setaffinity, SyscallPassthrough3<SYSCALL_DEF(sched_setaffinity)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getppid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(sched_getaffinity, SyscallPassthrough3<SYSCALL_DEF(sched_getaffinity)>);
SyscallPassthrough0<SYSCALL_DEF(getppid)>); REGISTER_SYSCALL_IMPL(io_setup, SyscallPassthrough2<SYSCALL_DEF(io_setup)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setsid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(io_destroy, SyscallPassthrough1<SYSCALL_DEF(io_destroy)>);
SyscallPassthrough0<SYSCALL_DEF(setsid)>); REGISTER_SYSCALL_IMPL(io_submit, SyscallPassthrough3<SYSCALL_DEF(io_submit)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setreuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(io_cancel, SyscallPassthrough3<SYSCALL_DEF(io_cancel)>);
SyscallPassthrough2<SYSCALL_DEF(setreuid)>); REGISTER_SYSCALL_IMPL(remap_file_pages, SyscallPassthrough5<SYSCALL_DEF(remap_file_pages)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setregid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(timer_getoverrun, SyscallPassthrough1<SYSCALL_DEF(timer_getoverrun)>);
SyscallPassthrough2<SYSCALL_DEF(setregid)>); REGISTER_SYSCALL_IMPL(timer_delete, SyscallPassthrough1<SYSCALL_DEF(timer_delete)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getgroups, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(tgkill, SyscallPassthrough3<SYSCALL_DEF(tgkill)>);
SyscallPassthrough2<SYSCALL_DEF(getgroups)>); REGISTER_SYSCALL_IMPL(mbind, SyscallPassthrough6<SYSCALL_DEF(mbind)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setgroups, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(set_mempolicy, SyscallPassthrough3<SYSCALL_DEF(set_mempolicy)>);
SyscallPassthrough2<SYSCALL_DEF(setgroups)>); REGISTER_SYSCALL_IMPL(get_mempolicy, SyscallPassthrough5<SYSCALL_DEF(get_mempolicy)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setresuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(mq_unlink, SyscallPassthrough1<SYSCALL_DEF(mq_unlink)>);
SyscallPassthrough3<SYSCALL_DEF(setresuid)>); REGISTER_SYSCALL_IMPL(add_key, SyscallPassthrough5<SYSCALL_DEF(add_key)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getresuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(request_key, SyscallPassthrough4<SYSCALL_DEF(request_key)>);
SyscallPassthrough3<SYSCALL_DEF(getresuid)>); REGISTER_SYSCALL_IMPL(keyctl, SyscallPassthrough5<SYSCALL_DEF(keyctl)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setresgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(ioprio_set, SyscallPassthrough2<SYSCALL_DEF(ioprio_set)>);
SyscallPassthrough3<SYSCALL_DEF(setresgid)>); REGISTER_SYSCALL_IMPL(ioprio_get, SyscallPassthrough3<SYSCALL_DEF(ioprio_get)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getresgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(inotify_add_watch, SyscallPassthrough3<SYSCALL_DEF(inotify_add_watch)>);
SyscallPassthrough3<SYSCALL_DEF(getresgid)>); REGISTER_SYSCALL_IMPL(inotify_rm_watch, SyscallPassthrough2<SYSCALL_DEF(inotify_rm_watch)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getpgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(migrate_pages, SyscallPassthrough4<SYSCALL_DEF(migrate_pages)>);
SyscallPassthrough1<SYSCALL_DEF(getpgid)>); REGISTER_SYSCALL_IMPL(mkdirat, SyscallPassthrough3<SYSCALL_DEF(mkdirat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setfsuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(mknodat, SyscallPassthrough4<SYSCALL_DEF(mknodat)>);
SyscallPassthrough1<SYSCALL_DEF(setfsuid)>); REGISTER_SYSCALL_IMPL(fchownat, SyscallPassthrough5<SYSCALL_DEF(fchownat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setfsgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(unlinkat, SyscallPassthrough3<SYSCALL_DEF(unlinkat)>);
SyscallPassthrough1<SYSCALL_DEF(setfsgid)>); REGISTER_SYSCALL_IMPL(renameat, SyscallPassthrough4<SYSCALL_DEF(renameat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getsid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(linkat, SyscallPassthrough5<SYSCALL_DEF(linkat)>);
SyscallPassthrough1<SYSCALL_DEF(getsid)>); REGISTER_SYSCALL_IMPL(symlinkat, SyscallPassthrough3<SYSCALL_DEF(symlinkat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(capget, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(fchmodat, SyscallPassthrough3<SYSCALL_DEF(fchmodat)>);
SyscallPassthrough2<SYSCALL_DEF(capget)>); REGISTER_SYSCALL_IMPL(unshare, SyscallPassthrough1<SYSCALL_DEF(unshare)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(capset, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(splice, SyscallPassthrough6<SYSCALL_DEF(splice)>);
SyscallPassthrough2<SYSCALL_DEF(capset)>); REGISTER_SYSCALL_IMPL(tee, SyscallPassthrough4<SYSCALL_DEF(tee)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getpriority, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(move_pages, SyscallPassthrough6<SYSCALL_DEF(move_pages)>);
SyscallPassthrough2<SYSCALL_DEF(getpriority)>); REGISTER_SYSCALL_IMPL(timerfd_create, SyscallPassthrough2<SYSCALL_DEF(timerfd_create)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setpriority, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(accept4, SyscallPassthrough4<SYSCALL_DEF(accept4)>);
SyscallPassthrough3<SYSCALL_DEF(setpriority)>); REGISTER_SYSCALL_IMPL(eventfd2, SyscallPassthrough2<SYSCALL_DEF(eventfd2)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_setparam, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(epoll_create1, SyscallPassthrough1<SYSCALL_DEF(epoll_create1)>);
SyscallPassthrough2<SYSCALL_DEF(sched_setparam)>); REGISTER_SYSCALL_IMPL(inotify_init1, SyscallPassthrough1<SYSCALL_DEF(inotify_init1)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_getparam, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(fanotify_init, SyscallPassthrough2<SYSCALL_DEF(fanotify_init)>);
SyscallPassthrough2<SYSCALL_DEF(sched_getparam)>); REGISTER_SYSCALL_IMPL(fanotify_mark, SyscallPassthrough5<SYSCALL_DEF(fanotify_mark)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_setscheduler, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(prlimit_64, SyscallPassthrough4<SYSCALL_DEF(prlimit_64)>);
SyscallPassthrough3<SYSCALL_DEF(sched_setscheduler)>); REGISTER_SYSCALL_IMPL(name_to_handle_at, SyscallPassthrough5<SYSCALL_DEF(name_to_handle_at)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_getscheduler, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(open_by_handle_at, SyscallPassthrough3<SYSCALL_DEF(open_by_handle_at)>);
SyscallPassthrough1<SYSCALL_DEF(sched_getscheduler)>); REGISTER_SYSCALL_IMPL(syncfs, SyscallPassthrough1<SYSCALL_DEF(syncfs)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_get_priority_max, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(setns, SyscallPassthrough2<SYSCALL_DEF(setns)>);
SyscallPassthrough1<SYSCALL_DEF(sched_get_priority_max)>); REGISTER_SYSCALL_IMPL(getcpu, SyscallPassthrough3<SYSCALL_DEF(getcpu)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_get_priority_min, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(kcmp, SyscallPassthrough5<SYSCALL_DEF(kcmp)>);
SyscallPassthrough1<SYSCALL_DEF(sched_get_priority_min)>); REGISTER_SYSCALL_IMPL(sched_setattr, SyscallPassthrough3<SYSCALL_DEF(sched_setattr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mlock, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(sched_getattr, SyscallPassthrough4<SYSCALL_DEF(sched_getattr)>);
SyscallPassthrough2<SYSCALL_DEF(mlock)>); REGISTER_SYSCALL_IMPL(renameat2, SyscallPassthrough5<SYSCALL_DEF(renameat2)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(munlock, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(getrandom, SyscallPassthrough3<SYSCALL_DEF(getrandom)>);
SyscallPassthrough2<SYSCALL_DEF(munlock)>); REGISTER_SYSCALL_IMPL(memfd_create, SyscallPassthrough2<SYSCALL_DEF(memfd_create)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(pivot_root, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(membarrier, SyscallPassthrough2<SYSCALL_DEF(membarrier)>);
SyscallPassthrough2<SYSCALL_DEF(pivot_root)>); REGISTER_SYSCALL_IMPL(mlock2, SyscallPassthrough3<SYSCALL_DEF(mlock2)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(chroot, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(copy_file_range, SyscallPassthrough6<SYSCALL_DEF(copy_file_range)>);
SyscallPassthrough1<SYSCALL_DEF(chroot)>); REGISTER_SYSCALL_IMPL(pkey_mprotect, SyscallPassthrough4<SYSCALL_DEF(pkey_mprotect)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sync, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, SyscallPassthrough0<SYSCALL_DEF(sync)>); REGISTER_SYSCALL_IMPL(pkey_alloc, SyscallPassthrough2<SYSCALL_DEF(pkey_alloc)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(acct, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, SyscallPassthrough1<SYSCALL_DEF(acct)>); REGISTER_SYSCALL_IMPL(pkey_free, SyscallPassthrough1<SYSCALL_DEF(pkey_free)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mount, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(io_uring_setup, SyscallPassthrough2<SYSCALL_DEF(io_uring_setup)>);
SyscallPassthrough5<SYSCALL_DEF(mount)>); REGISTER_SYSCALL_IMPL(io_uring_enter, SyscallPassthrough6<SYSCALL_DEF(io_uring_enter)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(umount2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(io_uring_register, SyscallPassthrough4<SYSCALL_DEF(io_uring_register)>);
SyscallPassthrough2<SYSCALL_DEF(umount2)>); REGISTER_SYSCALL_IMPL(open_tree, SyscallPassthrough3<SYSCALL_DEF(open_tree)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(swapon, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(move_mount, SyscallPassthrough5<SYSCALL_DEF(move_mount)>);
SyscallPassthrough2<SYSCALL_DEF(swapon)>); REGISTER_SYSCALL_IMPL(fsopen, SyscallPassthrough3<SYSCALL_DEF(fsopen)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(swapoff, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(fsconfig, SyscallPassthrough5<SYSCALL_DEF(fsconfig)>);
SyscallPassthrough1<SYSCALL_DEF(swapoff)>); REGISTER_SYSCALL_IMPL(fsmount, SyscallPassthrough3<SYSCALL_DEF(fsmount)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(gettid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(fspick, SyscallPassthrough3<SYSCALL_DEF(fspick)>);
SyscallPassthrough0<SYSCALL_DEF(gettid)>); REGISTER_SYSCALL_IMPL(pidfd_open, SyscallPassthrough2<SYSCALL_DEF(pidfd_open)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fsetxattr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(pidfd_getfd, SyscallPassthrough3<SYSCALL_DEF(pidfd_getfd)>);
SyscallPassthrough5<SYSCALL_DEF(fsetxattr)>); REGISTER_SYSCALL_IMPL(mount_setattr, SyscallPassthrough5<SYSCALL_DEF(mount_setattr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fgetxattr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(quotactl_fd, SyscallPassthrough4<SYSCALL_DEF(quotactl_fd)>);
SyscallPassthrough4<SYSCALL_DEF(fgetxattr)>); REGISTER_SYSCALL_IMPL(landlock_create_ruleset, SyscallPassthrough3<SYSCALL_DEF(landlock_create_ruleset)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(flistxattr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(landlock_add_rule, SyscallPassthrough4<SYSCALL_DEF(landlock_add_rule)>);
SyscallPassthrough3<SYSCALL_DEF(flistxattr)>); REGISTER_SYSCALL_IMPL(landlock_restrict_self, SyscallPassthrough2<SYSCALL_DEF(landlock_restrict_self)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fremovexattr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(memfd_secret, SyscallPassthrough1<SYSCALL_DEF(memfd_secret)>);
SyscallPassthrough2<SYSCALL_DEF(fremovexattr)>); REGISTER_SYSCALL_IMPL(process_mrelease, SyscallPassthrough2<SYSCALL_DEF(process_mrelease)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(tkill, SyscallFlags::DEFAULT, SyscallPassthrough2<SYSCALL_DEF(tkill)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_setaffinity, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(sched_setaffinity)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_getaffinity, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(sched_getaffinity)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(io_setup, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(io_setup)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(io_destroy, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(io_destroy)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(io_submit, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(io_submit)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(io_cancel, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(io_cancel)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(remap_file_pages, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(remap_file_pages)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(timer_getoverrun, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(timer_getoverrun)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(timer_delete, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(timer_delete)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(tgkill, SyscallFlags::DEFAULT, SyscallPassthrough3<SYSCALL_DEF(tgkill)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mbind, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(mbind)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(set_mempolicy, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(set_mempolicy)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(get_mempolicy, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(get_mempolicy)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mq_unlink, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(mq_unlink)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(add_key, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(add_key)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(request_key, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(request_key)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(keyctl, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(keyctl)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(ioprio_set, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(ioprio_set)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(ioprio_get, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(ioprio_get)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(inotify_add_watch, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(inotify_add_watch)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(inotify_rm_watch, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(inotify_rm_watch)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(migrate_pages, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(migrate_pages)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mkdirat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(mkdirat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mknodat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(mknodat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fchownat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(fchownat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(unlinkat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(unlinkat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(renameat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(renameat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(linkat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(linkat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(symlinkat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(symlinkat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fchmodat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(fchmodat)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(unshare, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(unshare)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(splice, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(splice)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(tee, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, SyscallPassthrough4<SYSCALL_DEF(tee)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(move_pages, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(move_pages)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(timerfd_create, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(timerfd_create)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(accept4, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(accept4)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(eventfd2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(eventfd2)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(epoll_create1, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(epoll_create1)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(inotify_init1, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(inotify_init1)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fanotify_init, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(fanotify_init)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fanotify_mark, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(fanotify_mark)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(prlimit_64, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(prlimit_64)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(name_to_handle_at, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(name_to_handle_at)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(open_by_handle_at, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(open_by_handle_at)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(syncfs, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(syncfs)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(setns, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(setns)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getcpu, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(getcpu)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(kcmp, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, SyscallPassthrough5<SYSCALL_DEF(kcmp)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_setattr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(sched_setattr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(sched_getattr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(sched_getattr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(renameat2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(renameat2)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(getrandom, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(getrandom)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(memfd_create, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(memfd_create)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(membarrier, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(membarrier)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mlock2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(mlock2)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(copy_file_range, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(copy_file_range)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(pkey_mprotect, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(pkey_mprotect)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(pkey_alloc, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(pkey_alloc)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(pkey_free, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(pkey_free)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(io_uring_setup, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(io_uring_setup)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(io_uring_enter, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(io_uring_enter)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(io_uring_register, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(io_uring_register)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(open_tree, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(open_tree)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(move_mount, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(move_mount)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fsopen, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(fsopen)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fsconfig, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(fsconfig)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fsmount, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(fsmount)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(fspick, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(fspick)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(pidfd_open, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(pidfd_open)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(pidfd_getfd, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(pidfd_getfd)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mount_setattr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(mount_setattr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(quotactl_fd, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(quotactl_fd)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(landlock_create_ruleset, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(landlock_create_ruleset)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(landlock_add_rule, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(landlock_add_rule)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(landlock_restrict_self, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(landlock_restrict_self)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(memfd_secret, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough1<SYSCALL_DEF(memfd_secret)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(process_mrelease, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(process_mrelease)>);
if (Handler->IsHostKernelVersionAtLeast(5, 16, 0)) { if (Handler->IsHostKernelVersionAtLeast(5, 16, 0)) {
REGISTER_SYSCALL_IMPL_PASS_FLAGS(futex_waitv, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(futex_waitv, SyscallPassthrough5<SYSCALL_DEF(futex_waitv)>);
SyscallPassthrough5<SYSCALL_DEF(futex_waitv)>);
} else { } else {
REGISTER_SYSCALL_IMPL(futex_waitv, UnimplementedSyscallSafe); REGISTER_SYSCALL_IMPL(futex_waitv, UnimplementedSyscallSafe);
} }
if (Handler->IsHostKernelVersionAtLeast(5, 17, 0)) { if (Handler->IsHostKernelVersionAtLeast(5, 17, 0)) {
REGISTER_SYSCALL_IMPL_PASS_FLAGS(set_mempolicy_home_node, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(set_mempolicy_home_node, SyscallPassthrough4<SYSCALL_DEF(set_mempolicy_home_node)>);
SyscallPassthrough4<SYSCALL_DEF(set_mempolicy_home_node)>);
} else { } else {
REGISTER_SYSCALL_IMPL(set_mempolicy_home_node, UnimplementedSyscallSafe); REGISTER_SYSCALL_IMPL(set_mempolicy_home_node, UnimplementedSyscallSafe);
} }
if (Handler->IsHostKernelVersionAtLeast(6, 8, 0)) { if (Handler->IsHostKernelVersionAtLeast(6, 8, 0)) {
REGISTER_SYSCALL_IMPL_PASS_FLAGS(futex_wake, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(futex_wake, SyscallPassthrough4<SYSCALL_DEF(futex_wake)>);
SyscallPassthrough4<SYSCALL_DEF(futex_wake)>); REGISTER_SYSCALL_IMPL(futex_wait, SyscallPassthrough6<SYSCALL_DEF(futex_wait)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(futex_wait, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(futex_requeue, SyscallPassthrough4<SYSCALL_DEF(futex_requeue)>);
SyscallPassthrough6<SYSCALL_DEF(futex_wait)>); REGISTER_SYSCALL_IMPL(statmount, SyscallPassthrough4<SYSCALL_DEF(statmount)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(futex_requeue, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(listmount, SyscallPassthrough4<SYSCALL_DEF(listmount)>);
SyscallPassthrough4<SYSCALL_DEF(futex_requeue)>); REGISTER_SYSCALL_IMPL(lsm_get_self_attr, SyscallPassthrough4<SYSCALL_DEF(lsm_get_self_attr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(statmount, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(lsm_set_self_attr, SyscallPassthrough4<SYSCALL_DEF(lsm_set_self_attr)>);
SyscallPassthrough4<SYSCALL_DEF(statmount)>); REGISTER_SYSCALL_IMPL(lsm_list_modules, SyscallPassthrough3<SYSCALL_DEF(lsm_list_modules)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(listmount, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(listmount)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(lsm_get_self_attr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(lsm_get_self_attr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(lsm_set_self_attr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(lsm_set_self_attr)>);
REGISTER_SYSCALL_IMPL_PASS_FLAGS(lsm_list_modules, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(lsm_list_modules)>);
} else { } else {
REGISTER_SYSCALL_IMPL(futex_wake, UnimplementedSyscallSafe); REGISTER_SYSCALL_IMPL(futex_wake, UnimplementedSyscallSafe);
REGISTER_SYSCALL_IMPL(futex_wait, UnimplementedSyscallSafe); REGISTER_SYSCALL_IMPL(futex_wait, UnimplementedSyscallSafe);
@@ -567,8 +405,7 @@ void RegisterCommon(FEX::HLE::SyscallHandler* Handler) {
REGISTER_SYSCALL_IMPL(lsm_list_modules, UnimplementedSyscallSafe); REGISTER_SYSCALL_IMPL(lsm_list_modules, UnimplementedSyscallSafe);
} }
if (Handler->IsHostKernelVersionAtLeast(6, 10, 0)) { if (Handler->IsHostKernelVersionAtLeast(6, 10, 0)) {
REGISTER_SYSCALL_IMPL_PASS_FLAGS(mseal, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(mseal, SyscallPassthrough3<SYSCALL_DEF(mseal)>);
SyscallPassthrough3<SYSCALL_DEF(mseal)>);
} else { } else {
REGISTER_SYSCALL_IMPL(mseal, UnimplementedSyscallSafe); REGISTER_SYSCALL_IMPL(mseal, UnimplementedSyscallSafe);
} }
@@ -578,157 +415,84 @@ namespace x64 {
void RegisterPassthrough(FEX::HLE::SyscallHandler* Handler) { void RegisterPassthrough(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
RegisterCommon(Handler); RegisterCommon(Handler);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(ioctl, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(ioctl, SyscallPassthrough3<SYSCALL_DEF(ioctl)>);
SyscallPassthrough3<SYSCALL_DEF(ioctl)>); REGISTER_SYSCALL_IMPL_X64(pread_64, SyscallPassthrough4<SYSCALL_DEF(pread_64)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(pread_64, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(pwrite_64, SyscallPassthrough4<SYSCALL_DEF(pwrite_64)>);
SyscallPassthrough4<SYSCALL_DEF(pread_64)>); REGISTER_SYSCALL_IMPL_X64(readv, SyscallPassthrough3<SYSCALL_DEF(readv)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(pwrite_64, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(writev, SyscallPassthrough3<SYSCALL_DEF(writev)>);
SyscallPassthrough4<SYSCALL_DEF(pwrite_64)>); REGISTER_SYSCALL_IMPL_X64(dup, SyscallPassthrough1<SYSCALL_DEF(dup)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(readv, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(nanosleep, SyscallPassthrough2<SYSCALL_DEF(nanosleep)>);
SyscallPassthrough3<SYSCALL_DEF(readv)>); REGISTER_SYSCALL_IMPL_X64(getitimer, SyscallPassthrough2<SYSCALL_DEF(getitimer)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(writev, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(setitimer, SyscallPassthrough3<SYSCALL_DEF(setitimer)>);
SyscallPassthrough3<SYSCALL_DEF(writev)>); REGISTER_SYSCALL_IMPL_X64(sendfile, SyscallPassthrough4<SYSCALL_DEF(sendfile)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(dup, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(accept, SyscallPassthrough3<SYSCALL_DEF(accept)>);
SyscallPassthrough1<SYSCALL_DEF(dup)>); REGISTER_SYSCALL_IMPL_X64(sendmsg, SyscallPassthrough3<SYSCALL_DEF(sendmsg)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(nanosleep, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(recvmsg, SyscallPassthrough3<SYSCALL_DEF(recvmsg)>);
SyscallPassthrough2<SYSCALL_DEF(nanosleep)>); REGISTER_SYSCALL_IMPL_X64(setsockopt, SyscallPassthrough5<SYSCALL_DEF(setsockopt)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(getitimer, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(getsockopt, SyscallPassthrough5<SYSCALL_DEF(getsockopt)>);
SyscallPassthrough2<SYSCALL_DEF(getitimer)>); REGISTER_SYSCALL_IMPL_X64(wait4, SyscallPassthrough4<SYSCALL_DEF(wait4)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(setitimer, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(semop, SyscallPassthrough3<SYSCALL_DEF(semop)>);
SyscallPassthrough3<SYSCALL_DEF(setitimer)>); REGISTER_SYSCALL_IMPL_X64(gettimeofday, SyscallPassthrough2<SYSCALL_DEF(gettimeofday)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(sendfile, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(getrlimit, SyscallPassthrough2<SYSCALL_DEF(getrlimit)>);
SyscallPassthrough4<SYSCALL_DEF(sendfile)>); REGISTER_SYSCALL_IMPL_X64(getrusage, SyscallPassthrough2<SYSCALL_DEF(getrusage)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(accept, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(sysinfo, SyscallPassthrough1<SYSCALL_DEF(sysinfo)>);
SyscallPassthrough3<SYSCALL_DEF(accept)>); REGISTER_SYSCALL_IMPL_X64(times, SyscallPassthrough1<SYSCALL_DEF(times)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(sendmsg, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(rt_sigqueueinfo, SyscallPassthrough3<SYSCALL_DEF(rt_sigqueueinfo)>);
SyscallPassthrough3<SYSCALL_DEF(sendmsg)>); REGISTER_SYSCALL_IMPL_X64(fstatfs, SyscallPassthrough2<SYSCALL_DEF(fstatfs)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(recvmsg, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(sched_rr_get_interval, SyscallPassthrough2<SYSCALL_DEF(sched_rr_get_interval)>);
SyscallPassthrough3<SYSCALL_DEF(recvmsg)>); REGISTER_SYSCALL_IMPL_X64(mlockall, SyscallPassthrough1<SYSCALL_DEF(mlockall)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(setsockopt, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(munlockall, SyscallPassthrough0<SYSCALL_DEF(munlockall)>);
SyscallPassthrough5<SYSCALL_DEF(setsockopt)>); REGISTER_SYSCALL_IMPL_X64(adjtimex, SyscallPassthrough1<SYSCALL_DEF(adjtimex)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(getsockopt, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(setrlimit, SyscallPassthrough2<SYSCALL_DEF(setrlimit)>);
SyscallPassthrough5<SYSCALL_DEF(getsockopt)>); REGISTER_SYSCALL_IMPL_X64(settimeofday, SyscallPassthrough2<SYSCALL_DEF(settimeofday)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(wait4, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(readahead, SyscallPassthrough3<SYSCALL_DEF(readahead)>);
SyscallPassthrough4<SYSCALL_DEF(wait4)>); REGISTER_SYSCALL_IMPL_X64(futex, SyscallPassthrough6<SYSCALL_DEF(futex)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(semop, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(io_getevents, SyscallPassthrough5<SYSCALL_DEF(io_getevents)>);
SyscallPassthrough3<SYSCALL_DEF(semop)>); REGISTER_SYSCALL_IMPL_X64(semtimedop, SyscallPassthrough4<SYSCALL_DEF(semtimedop)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(gettimeofday, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(timer_create, SyscallPassthrough3<SYSCALL_DEF(timer_create)>);
SyscallPassthrough2<SYSCALL_DEF(gettimeofday)>); REGISTER_SYSCALL_IMPL_X64(timer_settime, SyscallPassthrough4<SYSCALL_DEF(timer_settime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(getrlimit, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(timer_gettime, SyscallPassthrough2<SYSCALL_DEF(timer_gettime)>);
SyscallPassthrough2<SYSCALL_DEF(getrlimit)>); REGISTER_SYSCALL_IMPL_X64(clock_settime, SyscallPassthrough2<SYSCALL_DEF(clock_settime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(getrusage, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(clock_gettime, SyscallPassthrough2<SYSCALL_DEF(clock_gettime)>);
SyscallPassthrough2<SYSCALL_DEF(getrusage)>); REGISTER_SYSCALL_IMPL_X64(clock_getres, SyscallPassthrough2<SYSCALL_DEF(clock_getres)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(sysinfo, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(clock_nanosleep, SyscallPassthrough4<SYSCALL_DEF(clock_nanosleep)>);
SyscallPassthrough1<SYSCALL_DEF(sysinfo)>); REGISTER_SYSCALL_IMPL_X64(mq_open, SyscallPassthrough4<SYSCALL_DEF(mq_open)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(times, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(mq_timedsend, SyscallPassthrough5<SYSCALL_DEF(mq_timedsend)>);
SyscallPassthrough1<SYSCALL_DEF(times)>); REGISTER_SYSCALL_IMPL_X64(mq_timedreceive, SyscallPassthrough5<SYSCALL_DEF(mq_timedreceive)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(rt_sigqueueinfo, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(mq_notify, SyscallPassthrough2<SYSCALL_DEF(mq_notify)>);
SyscallPassthrough3<SYSCALL_DEF(rt_sigqueueinfo)>); REGISTER_SYSCALL_IMPL_X64(mq_getsetattr, SyscallPassthrough3<SYSCALL_DEF(mq_getsetattr)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(fstatfs, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(waitid, SyscallPassthrough5<SYSCALL_DEF(waitid)>);
SyscallPassthrough2<SYSCALL_DEF(fstatfs)>); REGISTER_SYSCALL_IMPL_X64(pselect6, SyscallPassthrough6<SYSCALL_DEF(pselect6)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(sched_rr_get_interval, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(ppoll, SyscallPassthrough5<SYSCALL_DEF(ppoll)>);
SyscallPassthrough2<SYSCALL_DEF(sched_rr_get_interval)>); REGISTER_SYSCALL_IMPL_X64(set_robust_list, SyscallPassthrough2<SYSCALL_DEF(set_robust_list)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(mlockall, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(get_robust_list, SyscallPassthrough3<SYSCALL_DEF(get_robust_list)>);
SyscallPassthrough1<SYSCALL_DEF(mlockall)>); REGISTER_SYSCALL_IMPL_X64(sync_file_range, SyscallPassthrough4<SYSCALL_DEF(sync_file_range)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(munlockall, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(vmsplice, SyscallPassthrough4<SYSCALL_DEF(vmsplice)>);
SyscallPassthrough0<SYSCALL_DEF(munlockall)>); REGISTER_SYSCALL_IMPL_X64(utimensat, SyscallPassthrough4<SYSCALL_DEF(utimensat)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(adjtimex, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(fallocate, SyscallPassthrough4<SYSCALL_DEF(fallocate)>);
SyscallPassthrough1<SYSCALL_DEF(adjtimex)>); REGISTER_SYSCALL_IMPL_X64(timerfd_settime, SyscallPassthrough4<SYSCALL_DEF(timerfd_settime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(setrlimit, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(timerfd_gettime, SyscallPassthrough2<SYSCALL_DEF(timerfd_gettime)>);
SyscallPassthrough2<SYSCALL_DEF(setrlimit)>); REGISTER_SYSCALL_IMPL_X64(preadv, SyscallPassthrough5<SYSCALL_DEF(preadv)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(settimeofday, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(pwritev, SyscallPassthrough5<SYSCALL_DEF(pwritev)>);
SyscallPassthrough2<SYSCALL_DEF(settimeofday)>); REGISTER_SYSCALL_IMPL_X64(rt_tgsigqueueinfo, SyscallPassthrough4<SYSCALL_DEF(rt_tgsigqueueinfo)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(readahead, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(recvmmsg, SyscallPassthrough5<SYSCALL_DEF(recvmmsg)>);
SyscallPassthrough3<SYSCALL_DEF(readahead)>); REGISTER_SYSCALL_IMPL_X64(clock_adjtime, SyscallPassthrough2<SYSCALL_DEF(clock_adjtime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(futex, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(sendmmsg, SyscallPassthrough4<SYSCALL_DEF(sendmmsg)>);
SyscallPassthrough6<SYSCALL_DEF(futex)>); REGISTER_SYSCALL_IMPL_X64(process_vm_readv, SyscallPassthrough6<SYSCALL_DEF(process_vm_readv)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(io_getevents, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(process_vm_writev, SyscallPassthrough6<SYSCALL_DEF(process_vm_writev)>);
SyscallPassthrough5<SYSCALL_DEF(io_getevents)>); REGISTER_SYSCALL_IMPL_X64(preadv2, SyscallPassthrough6<SYSCALL_DEF(preadv2)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(semtimedop, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(pwritev2, SyscallPassthrough6<SYSCALL_DEF(pwritev2)>);
SyscallPassthrough4<SYSCALL_DEF(semtimedop)>); REGISTER_SYSCALL_IMPL_X64(io_pgetevents, SyscallPassthrough6<SYSCALL_DEF(io_pgetevents)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(timer_create, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(pidfd_send_signal, SyscallPassthrough4<SYSCALL_DEF(pidfd_send_signal)>);
SyscallPassthrough3<SYSCALL_DEF(timer_create)>); REGISTER_SYSCALL_IMPL_X64(process_madvise, SyscallPassthrough5<SYSCALL_DEF(process_madvise)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(timer_settime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(fadvise64, SyscallPassthrough4<SYSCALL_DEF(fadvise64)>);
SyscallPassthrough4<SYSCALL_DEF(timer_settime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(timer_gettime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(timer_gettime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(clock_settime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(clock_settime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(clock_gettime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(clock_gettime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(clock_getres, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(clock_getres)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(clock_nanosleep, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(clock_nanosleep)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(mq_open, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(mq_open)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(mq_timedsend, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(mq_timedsend)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(mq_timedreceive, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(mq_timedreceive)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(mq_notify, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(mq_notify)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(mq_getsetattr, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(mq_getsetattr)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(waitid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(waitid)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(pselect6, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(pselect6)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(ppoll, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(ppoll)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(set_robust_list, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(set_robust_list)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(get_robust_list, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough3<SYSCALL_DEF(get_robust_list)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(sync_file_range, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(sync_file_range)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(vmsplice, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(vmsplice)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(utimensat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(utimensat)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(fallocate, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(fallocate)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(timerfd_settime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(timerfd_settime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(timerfd_gettime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(timerfd_gettime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(preadv, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(preadv)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(pwritev, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(pwritev)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(rt_tgsigqueueinfo, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(rt_tgsigqueueinfo)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(recvmmsg, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(recvmmsg)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(clock_adjtime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(clock_adjtime)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(sendmmsg, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(sendmmsg)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(process_vm_readv, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(process_vm_readv)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(process_vm_writev, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(process_vm_writev)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(preadv2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(preadv2)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(pwritev2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(pwritev2)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(io_pgetevents, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(io_pgetevents)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(pidfd_send_signal, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(pidfd_send_signal)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(process_madvise, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(process_madvise)>);
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(fadvise64, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(fadvise64)>);
if (Handler->IsHostKernelVersionAtLeast(6, 5, 0)) { if (Handler->IsHostKernelVersionAtLeast(6, 5, 0)) {
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(cachestat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(cachestat, SyscallPassthrough4<SYSCALL_DEF(cachestat)>);
SyscallPassthrough4<SYSCALL_DEF(cachestat)>);
} else { } else {
REGISTER_SYSCALL_IMPL_X64(cachestat, UnimplementedSyscallSafe); REGISTER_SYSCALL_IMPL_X64(cachestat, UnimplementedSyscallSafe);
} }
if (Handler->IsHostKernelVersionAtLeast(6, 6, 0)) { if (Handler->IsHostKernelVersionAtLeast(6, 6, 0)) {
REGISTER_SYSCALL_IMPL_X64_PASS_FLAGS(fchmodat2, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(fchmodat2, SyscallPassthrough4<SYSCALL_DEF(fchmodat2)>);
SyscallPassthrough4<SYSCALL_DEF(fchmodat2)>);
} else { } else {
REGISTER_SYSCALL_IMPL_X64(fchmodat2, UnimplementedSyscallSafe); REGISTER_SYSCALL_IMPL_X64(fchmodat2, UnimplementedSyscallSafe);
} }
@@ -739,79 +503,41 @@ namespace x32 {
void RegisterPassthrough(FEX::HLE::SyscallHandler* Handler) { void RegisterPassthrough(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
RegisterCommon(Handler); RegisterCommon(Handler);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(getuid32, getuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(getuid32, SyscallPassthrough0<SYSCALL_DEF(getuid)>);
SyscallPassthrough0<SYSCALL_DEF(getuid)>); REGISTER_SYSCALL_IMPL_X32(getgid32, SyscallPassthrough0<SYSCALL_DEF(getgid)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(getgid32, getgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(geteuid32, SyscallPassthrough0<SYSCALL_DEF(geteuid)>);
SyscallPassthrough0<SYSCALL_DEF(getgid)>); REGISTER_SYSCALL_IMPL_X32(getegid32, SyscallPassthrough0<SYSCALL_DEF(getegid)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(geteuid32, geteuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(setreuid32, SyscallPassthrough2<SYSCALL_DEF(setreuid)>);
SyscallPassthrough0<SYSCALL_DEF(geteuid)>); REGISTER_SYSCALL_IMPL_X32(setregid32, SyscallPassthrough2<SYSCALL_DEF(setregid)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(getegid32, getegid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(getgroups32, SyscallPassthrough2<SYSCALL_DEF(getgroups)>);
SyscallPassthrough0<SYSCALL_DEF(getegid)>); REGISTER_SYSCALL_IMPL_X32(setgroups32, SyscallPassthrough2<SYSCALL_DEF(setgroups)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(setreuid32, setreuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(fchown32, SyscallPassthrough3<SYSCALL_DEF(fchown)>);
SyscallPassthrough2<SYSCALL_DEF(setreuid)>); REGISTER_SYSCALL_IMPL_X32(setresuid32, SyscallPassthrough3<SYSCALL_DEF(setresuid)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(setregid32, setregid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(getresuid32, SyscallPassthrough3<SYSCALL_DEF(getresuid)>);
SyscallPassthrough2<SYSCALL_DEF(setregid)>); REGISTER_SYSCALL_IMPL_X32(setresgid32, SyscallPassthrough3<SYSCALL_DEF(setresgid)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(getgroups32, getgroups, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(getresgid32, SyscallPassthrough3<SYSCALL_DEF(getresgid)>);
SyscallPassthrough2<SYSCALL_DEF(getgroups)>); REGISTER_SYSCALL_IMPL_X32(setuid32, SyscallPassthrough1<SYSCALL_DEF(setuid)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(setgroups32, setgroups, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(setgid32, SyscallPassthrough1<SYSCALL_DEF(setgid)>);
SyscallPassthrough2<SYSCALL_DEF(setgroups)>); REGISTER_SYSCALL_IMPL_X32(setfsuid32, SyscallPassthrough1<SYSCALL_DEF(setfsuid)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(fchown32, fchown, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(setfsgid32, SyscallPassthrough1<SYSCALL_DEF(setfsgid)>);
SyscallPassthrough3<SYSCALL_DEF(fchown)>); REGISTER_SYSCALL_IMPL_X32(sendfile64, SyscallPassthrough4<SYSCALL_DEF(sendfile)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(setresuid32, setresuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(clock_gettime64, SyscallPassthrough2<SYSCALL_DEF(clock_gettime)>);
SyscallPassthrough3<SYSCALL_DEF(setresuid)>); REGISTER_SYSCALL_IMPL_X32(clock_settime64, SyscallPassthrough2<SYSCALL_DEF(clock_settime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(getresuid32, getresuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(clock_adjtime64, SyscallPassthrough2<SYSCALL_DEF(clock_adjtime)>);
SyscallPassthrough3<SYSCALL_DEF(getresuid)>); REGISTER_SYSCALL_IMPL_X32(clock_getres_time64, SyscallPassthrough2<SYSCALL_DEF(clock_getres)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(setresgid32, setresgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(clock_nanosleep_time64, SyscallPassthrough4<SYSCALL_DEF(clock_nanosleep)>);
SyscallPassthrough3<SYSCALL_DEF(setresgid)>); REGISTER_SYSCALL_IMPL_X32(timer_gettime64, SyscallPassthrough2<SYSCALL_DEF(timer_gettime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(getresgid32, getresgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(timer_settime64, SyscallPassthrough4<SYSCALL_DEF(timer_settime)>);
SyscallPassthrough3<SYSCALL_DEF(getresgid)>); REGISTER_SYSCALL_IMPL_X32(timerfd_gettime64, SyscallPassthrough2<SYSCALL_DEF(timerfd_gettime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(setuid32, setuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(timerfd_settime64, SyscallPassthrough4<SYSCALL_DEF(timerfd_settime)>);
SyscallPassthrough1<SYSCALL_DEF(setuid)>); REGISTER_SYSCALL_IMPL_X32(utimensat_time64, SyscallPassthrough4<SYSCALL_DEF(utimensat)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(setgid32, setgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(ppoll_time64, SyscallPassthrough5<SYSCALL_DEF(ppoll)>);
SyscallPassthrough1<SYSCALL_DEF(setgid)>); REGISTER_SYSCALL_IMPL_X32(io_pgetevents_time64, SyscallPassthrough6<SYSCALL_DEF(io_pgetevents)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(setfsuid32, setfsuid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(mq_timedsend_time64, SyscallPassthrough5<SYSCALL_DEF(mq_timedsend)>);
SyscallPassthrough1<SYSCALL_DEF(setfsuid)>); REGISTER_SYSCALL_IMPL_X32(mq_timedreceive_time64, SyscallPassthrough5<SYSCALL_DEF(mq_timedreceive)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(setfsgid32, setfsgid, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(semtimedop_time64, SyscallPassthrough4<SYSCALL_DEF(semtimedop)>);
SyscallPassthrough1<SYSCALL_DEF(setfsgid)>); REGISTER_SYSCALL_IMPL_X32(futex_time64, SyscallPassthrough6<SYSCALL_DEF(futex)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(sendfile64, sendfile, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X32(sched_rr_get_interval_time64, SyscallPassthrough2<SYSCALL_DEF(sched_rr_get_interval)>);
SyscallPassthrough4<SYSCALL_DEF(sendfile)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(clock_gettime64, clock_gettime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(clock_gettime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(clock_settime64, clock_settime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(clock_settime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(clock_adjtime64, clock_adjtime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(clock_adjtime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(clock_getres_time64, clock_getres, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(clock_getres)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(clock_nanosleep_time64, clock_nanosleep,
SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(clock_nanosleep)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(timer_gettime64, timer_gettime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(timer_gettime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(timer_settime64, timer_settime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(timer_settime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(timerfd_gettime64, timerfd_gettime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(timerfd_gettime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(timerfd_settime64, timerfd_settime, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(timerfd_settime)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(utimensat_time64, utimensat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(utimensat)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(ppoll_time64, ppoll, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(ppoll)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(io_pgetevents_time64, io_pgetevents, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(io_pgetevents)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(mq_timedsend_time64, mq_timedsend, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(mq_timedsend)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(mq_timedreceive_time64, mq_timedreceive,
SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough5<SYSCALL_DEF(mq_timedreceive)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(semtimedop_time64, semtimedop, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough4<SYSCALL_DEF(semtimedop)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(futex_time64, futex, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough6<SYSCALL_DEF(futex)>);
REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(sched_rr_get_interval_time64, sched_rr_get_interval,
SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY,
SyscallPassthrough2<SYSCALL_DEF(sched_rr_get_interval)>);
} }
} // namespace x32 } // namespace x32
} // namespace FEX::HLE } // namespace FEX::HLE
@@ -398,127 +398,124 @@ void RegisterThread(FEX::HLE::SyscallHandler* Handler) {
FEX_UNREACHABLE; FEX_UNREACHABLE;
}); });
REGISTER_SYSCALL_IMPL_FLAGS(fork, SyscallFlags::DEFAULT, ([](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t { REGISTER_SYSCALL_IMPL(fork, ([](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t {
FEX::HLE::clone3_args args {.Type = TypeOfClone::TYPE_CLONE2, FEX::HLE::clone3_args args {.Type = TypeOfClone::TYPE_CLONE2,
.args = { .args = {
.flags = 0, .flags = 0,
.pidfd = 0, .pidfd = 0,
.child_tid = 0, .child_tid = 0,
.parent_tid = 0, .parent_tid = 0,
.exit_signal = SIGCHLD, .exit_signal = SIGCHLD,
.stack = 0, .stack = 0,
.stack_size = 0, .stack_size = 0,
.tls = 0, .tls = 0,
.set_tid = 0, .set_tid = 0,
.set_tid_size = 0, .set_tid_size = 0,
.cgroup = 0, .cgroup = 0,
}}; }};
return ForkGuest(Frame->Thread, Frame, &args); return ForkGuest(Frame->Thread, Frame, &args);
})); }));
REGISTER_SYSCALL_IMPL_FLAGS(vfork, SyscallFlags::DEFAULT, ([](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t { REGISTER_SYSCALL_IMPL(vfork, ([](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t {
FEX::HLE::clone3_args args {.Type = TypeOfClone::TYPE_CLONE2, FEX::HLE::clone3_args args {.Type = TypeOfClone::TYPE_CLONE2,
.args = { .args = {
.flags = CLONE_VFORK, .flags = CLONE_VFORK,
.pidfd = 0, .pidfd = 0,
.child_tid = 0, .child_tid = 0,
.parent_tid = 0, .parent_tid = 0,
.exit_signal = SIGCHLD, .exit_signal = SIGCHLD,
.stack = 0, .stack = 0,
.stack_size = 0, .stack_size = 0,
.tls = 0, .tls = 0,
.set_tid = 0, .set_tid = 0,
.set_tid_size = 0, .set_tid_size = 0,
.cgroup = 0, .cgroup = 0,
}}; }};
return ForkGuest(Frame->Thread, Frame, &args); return ForkGuest(Frame->Thread, Frame, &args);
})); }));
REGISTER_SYSCALL_IMPL_FLAGS(getpgrp, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(getpgrp, [](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t { uint64_t Result = ::getpgrp();
uint64_t Result = ::getpgrp(); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(clone3, SyscallFlags::DEFAULT, REGISTER_SYSCALL_IMPL(clone3, ([](FEXCore::Core::CpuStateFrame* Frame, FEX::HLE::kernel_clone3_args* cl_args, size_t size) -> uint64_t {
([](FEXCore::Core::CpuStateFrame* Frame, FEX::HLE::kernel_clone3_args* cl_args, size_t size) -> uint64_t { FEX::HLE::clone3_args args {};
FEX::HLE::clone3_args args {}; args.Type = TypeOfClone::TYPE_CLONE3;
args.Type = TypeOfClone::TYPE_CLONE3; memcpy(&args.args, cl_args, std::min(sizeof(FEX::HLE::kernel_clone3_args), size));
memcpy(&args.args, cl_args, std::min(sizeof(FEX::HLE::kernel_clone3_args), size)); return CloneHandler(Frame, &args);
return CloneHandler(Frame, &args); }));
}));
REGISTER_SYSCALL_IMPL_FLAGS(exit, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY | SyscallFlags::NORETURN, REGISTER_SYSCALL_IMPL(exit, [](FEXCore::Core::CpuStateFrame* Frame, int status) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int status) -> uint64_t { // TLS/DTV teardown is something FEX can't control. Disable glibc checking when we leave a pthread.
// TLS/DTV teardown is something FEX can't control. Disable glibc checking when we leave a pthread. // Since this thread is hard stopping, we can't track the TLS/DTV teardown in FEX's thread handling.
// Since this thread is hard stopping, we can't track the TLS/DTV teardown in FEX's thread handling. FEXCore::Allocator::YesIKnowImNotSupposedToUseTheGlibcAllocator::HardDisable();
FEXCore::Allocator::YesIKnowImNotSupposedToUseTheGlibcAllocator::HardDisable(); auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
if (ThreadObject->ThreadInfo.clear_child_tid) { if (ThreadObject->ThreadInfo.clear_child_tid) {
std::atomic<uint32_t>* Addr = reinterpret_cast<std::atomic<uint32_t>*>(ThreadObject->ThreadInfo.clear_child_tid); auto Addr = std::atomic_ref<int32_t>(*ThreadObject->ThreadInfo.clear_child_tid);
Addr->store(0); Addr.store(0);
syscall(SYSCALL_DEF(futex), ThreadObject->ThreadInfo.clear_child_tid, FUTEX_WAKE, ~0ULL, 0, 0, 0); syscall(SYSCALL_DEF(futex), ThreadObject->ThreadInfo.clear_child_tid, FUTEX_WAKE, ~0ULL, 0, 0, 0);
} }
ThreadObject->StatusCode = status; ThreadObject->StatusCode = status;
FEX::HLE::_SyscallHandler->UninstallTLSState(ThreadObject); FEX::HLE::_SyscallHandler->UninstallTLSState(ThreadObject);
if (ThreadObject->ExecutionThread) { if (ThreadObject->ExecutionThread) {
// If this is a pthread based execution thread, then there is more work to be done. // If this is a pthread based execution thread, then there is more work to be done.
// Delegate final deletion and cleanup to the pthreads Thread management. // Delegate final deletion and cleanup to the pthreads Thread management.
FEX::LinuxEmulation::Threads::LongjumpDeallocateAndExit(ThreadObject, status); FEX::LinuxEmulation::Threads::LongjumpDeallocateAndExit(ThreadObject, status);
} else { } else {
FEX::HLE::_SyscallHandler->TM.DestroyThread(ThreadObject, true); FEX::HLE::_SyscallHandler->TM.DestroyThread(ThreadObject, true);
FEX::LinuxEmulation::Threads::DeallocateStackObjectAndExit(nullptr, status); FEX::LinuxEmulation::Threads::DeallocateStackObjectAndExit(nullptr, status);
} }
// This will never be reached // This will never be reached
std::terminate(); std::terminate();
}); });
REGISTER_SYSCALL_IMPL_FLAGS(prctl, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(prctl,
[](FEXCore::Core::CpuStateFrame* Frame, int option, unsigned long arg2, unsigned long arg3, [](FEXCore::Core::CpuStateFrame* Frame, int option, unsigned long arg2, unsigned long arg3, unsigned long arg4,
unsigned long arg4, unsigned long arg5) -> uint64_t { unsigned long arg5) -> uint64_t {
uint64_t Result {}; uint64_t Result {};
#ifndef PR_GET_AUXV #ifndef PR_GET_AUXV
#define PR_GET_AUXV 0x41555856 #define PR_GET_AUXV 0x41555856
#endif #endif
switch (option) { switch (option) {
case PR_SET_SECCOMP: { case PR_SET_SECCOMP: {
uint32_t Operation {}; uint32_t Operation {};
if (arg2 == SECCOMP_MODE_STRICT) Operation = SECCOMP_SET_MODE_STRICT; if (arg2 == SECCOMP_MODE_STRICT) Operation = SECCOMP_SET_MODE_STRICT;
if (arg2 == SECCOMP_MODE_FILTER) Operation = SECCOMP_SET_MODE_FILTER; if (arg2 == SECCOMP_MODE_FILTER) Operation = SECCOMP_SET_MODE_FILTER;
return FEX::HLE::_SyscallHandler->SeccompEmulator.Handle(Frame, Operation, 0, reinterpret_cast<void*>(arg3)); return FEX::HLE::_SyscallHandler->SeccompEmulator.Handle(Frame, Operation, 0, reinterpret_cast<void*>(arg3));
} }
case PR_GET_SECCOMP: return FEX::HLE::_SyscallHandler->SeccompEmulator.GetSeccomp(Frame); case PR_GET_SECCOMP: return FEX::HLE::_SyscallHandler->SeccompEmulator.GetSeccomp(Frame);
case PR_GET_AUXV: { case PR_GET_AUXV: {
if (arg4 || arg5) { if (arg4 || arg5) {
return -EINVAL; return -EINVAL;
} }
void* addr = reinterpret_cast<void*>(arg2); void* addr = reinterpret_cast<void*>(arg2);
size_t UserSize = reinterpret_cast<size_t>(arg3); size_t UserSize = reinterpret_cast<size_t>(arg3);
const auto auxv = FEX::HLE::_SyscallHandler->GetCodeLoader()->GetAuxv(); const auto auxv = FEX::HLE::_SyscallHandler->GetCodeLoader()->GetAuxv();
const auto auxvBase = auxv.address; const auto auxvBase = auxv.address;
const auto auxvSize = auxv.size; const auto auxvSize = auxv.size;
size_t MinSize = std::min(auxvSize, UserSize); size_t MinSize = std::min(auxvSize, UserSize);
memcpy(addr, reinterpret_cast<void*>(auxvBase), MinSize); memcpy(addr, reinterpret_cast<void*>(auxvBase), MinSize);
// Returns the size of auxv without truncation. // Returns the size of auxv without truncation.
return auxvSize; return auxvSize;
} }
default: Result = ::prctl(option, arg2, arg3, arg4, arg5); break; default: Result = ::prctl(option, arg2, arg3, arg4, arg5); break;
} }
SYSCALL_ERRNO(); SYSCALL_ERRNO();
}); });
REGISTER_SYSCALL_IMPL_FLAGS(arch_prctl, SyscallFlags::DEFAULT, [](FEXCore::Core::CpuStateFrame* Frame, int code, unsigned long addr) -> uint64_t { REGISTER_SYSCALL_IMPL(arch_prctl, [](FEXCore::Core::CpuStateFrame* Frame, int code, unsigned long addr) -> uint64_t {
uint64_t Result {}; uint64_t Result {};
switch (code) { switch (code) {
case 0x1001: // ARCH_SET_GS case 0x1001: // ARCH_SET_GS
@@ -562,22 +559,20 @@ void RegisterThread(FEX::HLE::SyscallHandler* Handler) {
SYSCALL_ERRNO(); SYSCALL_ERRNO();
}); });
REGISTER_SYSCALL_IMPL_FLAGS(set_tid_address, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(set_tid_address, [](FEXCore::Core::CpuStateFrame* Frame, int* tidptr) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int* tidptr) -> uint64_t { auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame); ThreadObject->ThreadInfo.clear_child_tid = tidptr;
ThreadObject->ThreadInfo.clear_child_tid = tidptr; return ThreadObject->ThreadInfo.TID;
return ThreadObject->ThreadInfo.TID; });
});
REGISTER_SYSCALL_IMPL_FLAGS(exit_group, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY | SyscallFlags::NORETURN, REGISTER_SYSCALL_IMPL(exit_group, [](FEXCore::Core::CpuStateFrame* Frame, int status) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, int status) -> uint64_t { // Save telemetry if we're exiting.
// Save telemetry if we're exiting. FEX::HLE::_SyscallHandler->GetSignalDelegator()->SaveTelemetry();
FEX::HLE::_SyscallHandler->GetSignalDelegator()->SaveTelemetry(); FEX::HLE::_SyscallHandler->TM.CleanupForExit();
FEX::HLE::_SyscallHandler->TM.CleanupForExit();
syscall(SYSCALL_DEF(exit_group), status); syscall(SYSCALL_DEF(exit_group), status);
// This will never be reached // This will never be reached
std::terminate(); std::terminate();
}); });
} }
} // namespace FEX::HLE } // namespace FEX::HLE
@@ -24,16 +24,14 @@ namespace FEX::HLE {
void RegisterTimer(FEX::HLE::SyscallHandler* Handler) { void RegisterTimer(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_FLAGS(alarm, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(alarm, [](FEXCore::Core::CpuStateFrame* Frame, unsigned int seconds) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, unsigned int seconds) -> uint64_t { uint64_t Result = ::alarm(seconds);
uint64_t Result = ::alarm(seconds); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
REGISTER_SYSCALL_IMPL_FLAGS(pause, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL(pause, [](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame) -> uint64_t { uint64_t Result = ::pause();
uint64_t Result = ::pause(); SYSCALL_ERRNO();
SYSCALL_ERRNO(); });
});
} }
} // namespace FEX::HLE } // namespace FEX::HLE
@@ -8,6 +8,7 @@ $end_info$
*/ */
#include "Common/FDUtils.h" #include "Common/FDUtils.h"
#include "Common/FileMappingBaseAddress.h"
#include <filesystem> #include <filesystem>
#include <sys/shm.h> #include <sys/shm.h>
@@ -23,6 +24,7 @@ $end_info$
#include <FEXCore/Utils/SignalScopeGuards.h> #include <FEXCore/Utils/SignalScopeGuards.h>
#include <FEXCore/Utils/TypeDefines.h> #include <FEXCore/Utils/TypeDefines.h>
#include <FEXHeaderUtils/Filesystem.h> #include <FEXHeaderUtils/Filesystem.h>
#include <Linux/Utils/ELFParser.h>
namespace FEX::HLE { namespace FEX::HLE {
// SMC interactions // SMC interactions
@@ -165,22 +167,20 @@ void SyscallHandler::MarkGuestExecutableRange(FEXCore::Core::InternalThreadState
} }
void SyscallHandler::InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) { void SyscallHandler::InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) {
FEX::HLE::_SyscallHandler->InvalidateCodeRangeIfNecessary(Thread, Start, Length); InvalidateCodeRangeIfNecessary(Thread, Start, Length);
} }
std::optional<FEXCore::ExecutableFileSectionInfo> std::optional<FEXCore::ExecutableFileSectionInfo>
SyscallHandler::LookupExecutableFileSection(FEXCore::Core::InternalThreadState& Thread, uint64_t GuestAddr) { SyscallHandler::LookupExecutableFileSection(FEXCore::Core::InternalThreadState& Thread, uint64_t GuestAddr) {
auto lk = FEXCore::GuardSignalDeferringSection<std::shared_lock>(VMATracking.Mutex, &Thread); auto lk = FEXCore::GuardSignalDeferringSection<std::shared_lock>(VMATracking.Mutex, &Thread);
// Get the first mapping after GuestAddr, or end auto EntryIt = VMATracking.FindVMAEntry(GuestAddr);
// GuestAddr is inclusive if (EntryIt == VMATracking.VMAs.end() || !EntryIt->second.Resource) {
// If the write spans two pages, they will be flushed one at a time (generating two faults)
auto Entry = VMATracking.FindVMAEntry(GuestAddr);
if (Entry == VMATracking.VMAs.end() || !Entry->second.Resource) {
return std::nullopt; return std::nullopt;
} }
return FEXCore::ExecutableFileSectionInfo {*Entry->second.Resource->MappedFile, Entry->second.Base - Entry->second.Offset}; auto& [MappingBaseAddr, Entry] = *EntryIt;
return FEXCore::ExecutableFileSectionInfo {*Entry.Resource->MappedFile, Entry.Resource->FirstVMA->Base};
} }
FEXCore::HLE::ExecutableRangeInfo SyscallHandler::QueryGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Address) { FEXCore::HLE::ExecutableRangeInfo SyscallHandler::QueryGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Address) {
@@ -195,6 +195,22 @@ FEXCore::HLE::ExecutableRangeInfo SyscallHandler::QueryGuestExecutableRange(FEXC
return {Entry->first, Entry->second.Length, Entry->second.Prot.Writable}; return {Entry->first, Entry->second.Length, Entry->second.Prot.Writable};
} }
static fextl::vector<Elf64_Phdr> ReadELFHeaders(int FD, std::span<std::byte> HeaderData = {}) {
std::string_view ELFMagic = ELFMAG;
if (HeaderData.data()) {
if (HeaderData.size_bytes() < ELFMagic.size() || std::memcmp(ELFMagic.data(), HeaderData.data(), ELFMagic.size()) != 0) {
// Not an ELF file
return {};
}
} else {
// Read from FD in case the caller didn't have a mapped header available
}
ELFParser Parser;
Parser.ReadElf(dup(FD));
return std::move(Parser.phdrs);
}
void* SyscallHandler::GuestMmap(bool Is64Bit, FEXCore::Core::InternalThreadState* Thread, void* addr, size_t length, int prot, int flags, void* SyscallHandler::GuestMmap(bool Is64Bit, FEXCore::Core::InternalThreadState* Thread, void* addr, size_t length, int prot, int flags,
int fd, off_t offset) { int fd, off_t offset) {
LOGMAN_THROW_A_FMT(Is64Bit || (length >> 32) == 0, "values must fit to 32 bits"); LOGMAN_THROW_A_FMT(Is64Bit || (length >> 32) == 0, "values must fit to 32 bits");
@@ -223,10 +239,10 @@ void* SyscallHandler::GuestMmap(bool Is64Bit, FEXCore::Core::InternalThreadState
} }
} }
LateMetadata = FEX::HLE::_SyscallHandler->TrackMmap(Thread, Result, length, prot, flags, fd, offset); LateMetadata = TrackMmap(Thread, Result, length, prot, flags, fd, offset);
} }
FEX::HLE::_SyscallHandler->InvalidateCodeRangeIfNecessary(Thread, Result, Size); InvalidateCodeRangeIfNecessary(Thread, Result, Size);
if (LateMetadata) { if (LateMetadata) {
auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), Thread); auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), Thread);
@@ -250,7 +266,7 @@ uint64_t SyscallHandler::GuestMunmap(bool Is64Bit, FEXCore::Core::InternalThread
auto lk = FEXCore::GuardSignalDeferringSectionWithFallback(VMATracking.Mutex, Thread); auto lk = FEXCore::GuardSignalDeferringSectionWithFallback(VMATracking.Mutex, Thread);
if (reinterpret_cast<uintptr_t>(addr) < 0x1'0000'0000ULL) { if (reinterpret_cast<uintptr_t>(addr) < 0x1'0000'0000ULL) {
Result = FEX::HLE::_SyscallHandler->Get32BitAllocator()->Munmap(addr, length); Result = Get32BitAllocator()->Munmap(addr, length);
if (FEX::HLE::HasSyscallError(Result)) { if (FEX::HLE::HasSyscallError(Result)) {
return Result; return Result;
} }
@@ -260,9 +276,9 @@ uint64_t SyscallHandler::GuestMunmap(bool Is64Bit, FEXCore::Core::InternalThread
return -errno; return -errno;
} }
} }
FEX::HLE::_SyscallHandler->TrackMunmap(Thread, addr, length); TrackMunmap(Thread, addr, length);
} }
FEX::HLE::_SyscallHandler->InvalidateCodeRangeIfNecessary(Thread, reinterpret_cast<uint64_t>(addr), Size); InvalidateCodeRangeIfNecessary(Thread, reinterpret_cast<uint64_t>(addr), Size);
if (length) { if (length) {
auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), Thread); auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), Thread);
@@ -277,23 +293,22 @@ uint64_t SyscallHandler::GuestMremap(bool Is64Bit, FEXCore::Core::InternalThread
uint64_t Result {}; uint64_t Result {};
{ {
auto lk = FEXCore::GuardSignalDeferringSection(FEX::HLE::_SyscallHandler->VMATracking.Mutex, Thread); auto lk = FEXCore::GuardSignalDeferringSection(VMATracking.Mutex, Thread);
if (Is64Bit) { if (Is64Bit) {
Result = reinterpret_cast<uint64_t>(::mremap(old_address, old_size, new_size, flags, new_address)); Result = reinterpret_cast<uint64_t>(::mremap(old_address, old_size, new_size, flags, new_address));
if (Result == -1) { if (Result == -1) {
return -errno; return -errno;
} }
} else { } else {
Result = Result = reinterpret_cast<uint64_t>(Get32BitAllocator()->Mremap(old_address, old_size, new_size, flags, new_address));
reinterpret_cast<uint64_t>(FEX::HLE::_SyscallHandler->Get32BitAllocator()->Mremap(old_address, old_size, new_size, flags, new_address));
if (FEX::HLE::HasSyscallError(Result)) { if (FEX::HLE::HasSyscallError(Result)) {
return Result; return Result;
} }
} }
FEX::HLE::_SyscallHandler->TrackMremap(Thread, reinterpret_cast<uint64_t>(old_address), old_size, new_size, flags, Result); TrackMremap(Thread, reinterpret_cast<uint64_t>(old_address), old_size, new_size, flags, Result);
} }
FEX::HLE::_SyscallHandler->InvalidateCodeRangeIfNecessaryOnRemap(Thread, reinterpret_cast<uint64_t>(old_address), Result, old_size, new_size); InvalidateCodeRangeIfNecessaryOnRemap(Thread, reinterpret_cast<uint64_t>(old_address), Result, old_size, new_size);
return Result; return Result;
} }
@@ -301,17 +316,16 @@ uint64_t SyscallHandler::GuestMprotect(FEXCore::Core::InternalThreadState* Threa
uint64_t Result {}; uint64_t Result {};
{ {
auto lk = FEXCore::GuardSignalDeferringSection(FEX::HLE::_SyscallHandler->VMATracking.Mutex, Thread); auto lk = FEXCore::GuardSignalDeferringSection(VMATracking.Mutex, Thread);
Result = ::mprotect(addr, len, prot); Result = ::mprotect(addr, len, prot);
if (Result == -1) { if (Result == -1) {
return -errno; return -errno;
} }
FEX::HLE::_SyscallHandler->TrackMprotect(Thread, addr, len, prot); TrackMprotect(Thread, addr, len, prot);
} }
InvalidateCodeRangeIfNecessary(Thread, reinterpret_cast<uint64_t>(addr), len);
FEX::HLE::_SyscallHandler->InvalidateCodeRangeIfNecessary(Thread, reinterpret_cast<uint64_t>(addr), len);
return Result; return Result;
} }
@@ -320,7 +334,7 @@ uint64_t SyscallHandler::GuestShmat(bool Is64Bit, FEXCore::Core::InternalThreadS
uint64_t Length {}; uint64_t Length {};
{ {
auto lk = FEXCore::GuardSignalDeferringSection(FEX::HLE::_SyscallHandler->VMATracking.Mutex, Thread); auto lk = FEXCore::GuardSignalDeferringSection(VMATracking.Mutex, Thread);
if (Is64Bit) { if (Is64Bit) {
Result = reinterpret_cast<uint64_t>(::shmat(shmid, shmaddr, shmflg)); Result = reinterpret_cast<uint64_t>(::shmat(shmid, shmaddr, shmflg));
if (Result == -1) { if (Result == -1) {
@@ -328,7 +342,7 @@ uint64_t SyscallHandler::GuestShmat(bool Is64Bit, FEXCore::Core::InternalThreadS
} }
} else { } else {
uint32_t Addr; uint32_t Addr;
Result = FEX::HLE::_SyscallHandler->Get32BitAllocator()->Shmat(shmid, shmaddr, shmflg, &Addr); Result = Get32BitAllocator()->Shmat(shmid, shmaddr, shmflg, &Addr);
if (FEX::HLE::HasSyscallError(Result)) { if (FEX::HLE::HasSyscallError(Result)) {
return Result; return Result;
} }
@@ -341,10 +355,10 @@ uint64_t SyscallHandler::GuestShmat(bool Is64Bit, FEXCore::Core::InternalThreadS
LOGMAN_THROW_A_FMT(res != -1, "shmctl IPC_STAT failed"); LOGMAN_THROW_A_FMT(res != -1, "shmctl IPC_STAT failed");
Length = stat.shm_segsz; Length = stat.shm_segsz;
FEX::HLE::_SyscallHandler->TrackShmat(Thread, shmid, Result, shmflg, Length); TrackShmat(Thread, shmid, Result, shmflg, Length);
} }
FEX::HLE::_SyscallHandler->InvalidateCodeRangeIfNecessary(Thread, Result, Length); InvalidateCodeRangeIfNecessary(Thread, Result, Length);
return Result; return Result;
} }
@@ -352,23 +366,23 @@ uint64_t SyscallHandler::GuestShmdt(bool Is64Bit, FEXCore::Core::InternalThreadS
uint64_t Result {}; uint64_t Result {};
uint64_t Length {}; uint64_t Length {};
{ {
auto lk = FEXCore::GuardSignalDeferringSection(FEX::HLE::_SyscallHandler->VMATracking.Mutex, Thread); auto lk = FEXCore::GuardSignalDeferringSection(VMATracking.Mutex, Thread);
if (Is64Bit) { if (Is64Bit) {
Result = ::shmdt(shmaddr); Result = ::shmdt(shmaddr);
if (Result == -1) { if (Result == -1) {
return -errno; return -errno;
} }
} else { } else {
Result = FEX::HLE::_SyscallHandler->Get32BitAllocator()->Shmdt(shmaddr); Result = Get32BitAllocator()->Shmdt(shmaddr);
if (FEX::HLE::HasSyscallError(Result)) { if (FEX::HLE::HasSyscallError(Result)) {
return Result; return Result;
} }
} }
Length = FEX::HLE::_SyscallHandler->TrackShmdt(Thread, reinterpret_cast<uintptr_t>(shmaddr)); Length = TrackShmdt(Thread, reinterpret_cast<uintptr_t>(shmaddr));
} }
FEX::HLE::_SyscallHandler->InvalidateCodeRangeIfNecessary(Thread, reinterpret_cast<uintptr_t>(shmaddr), Length); InvalidateCodeRangeIfNecessary(Thread, reinterpret_cast<uintptr_t>(shmaddr), Length);
return Result; return Result;
} }
@@ -385,19 +399,56 @@ std::optional<SyscallHandler::LateApplyExtendedVolatileMetadata> SyscallHandler:
if (!(flags & MAP_ANONYMOUS)) { if (!(flags & MAP_ANONYMOUS)) {
struct stat64 buf; struct stat64 buf;
fstat64(fd, &buf); fstat64(fd, &buf);
VMATracking::MRID mrid {buf.st_dev, buf.st_ino};
const VMATracking::MRID mrid {buf.st_dev, buf.st_ino};
char Tmp[PATH_MAX]; char Tmp[PATH_MAX];
auto PathLength = FEX::get_fdpath(fd, Tmp); auto PathLength = FEX::get_fdpath(fd, Tmp);
if (PathLength != -1) { auto [ResourceIt, ResourceEnd] = VMATracking.FindResources(mrid);
auto [Iter, Inserted] = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, 0}); bool Inserted = false;
Resource = &Iter->second; const bool MappedELFHeaderAgain = ResourceIt != ResourceEnd && offset == 0 && !ResourceIt->second.ProgramHeaders.empty();
if (ResourceIt == ResourceEnd || MappedELFHeaderAgain) {
// Create a new MappedResource for previously unseen file and for re-mappings of an ELF header
ResourceIt = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, 0});
ResourceIt->second.Iterator = ResourceIt;
Inserted = true;
}
Resource = &ResourceIt->second;
// Only handle FDs that are backed by regular files that are executable
if (PathLength != -1 && S_ISREG(buf.st_mode) && (buf.st_mode & S_IXUSR)) {
// ELF files that are mapped multiple times get a separate MappedResource for each base virtual address
if (Inserted) { if (Inserted) {
Resource->MappedFile = fextl::make_unique<FEXCore::ExecutableFileInfo>(); Resource->MappedFile = fextl::make_unique<FEXCore::ExecutableFileInfo>();
Resource->MappedFile->Filename = fextl::string(Tmp, PathLength); Resource->MappedFile->Filename = fextl::string(Tmp, PathLength);
Resource->Iterator = Iter;
// Read ELF headers if applicable.
// For performance, skip ELF checks if we're not mapping the file header
bool CheckForElfFile = (offset == 0);
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
CheckForElfFile = true;
#endif
if (CheckForElfFile) {
Resource->ProgramHeaders = ReadELFHeaders(fd, std::span {reinterpret_cast<std::byte*>(addr), length});
LOGMAN_THROW_A_FMT(Resource->ProgramHeaders.empty() || offset == 0, "Expected file offset 0 for the first mapping of an ELF "
"file");
}
} else if (ResourceIt->second.ProgramHeaders.empty()) {
// Not an ELF file, so we don't need to distinguish between different base addresses
} else {
// Mapped a non-header section of an ELF file.
// Look up the corresponding MappedResource using the expected base address.
ResourceIt = std::find_if(ResourceIt, ResourceEnd, [&](const VMATracking::MappedResource::ContainerType::value_type& ResourcePair) {
auto& Resource = ResourcePair.second;
auto ExpectedBase = FEXCore::InferMappingBaseAddress(
Resource.ProgramHeaders, addr, Size, offset,
(ProtMapping.Executable ? PF_X : 0) | (ProtMapping.Writable ? PF_W : 0) | (ProtMapping.Readable ? PF_R : 0));
return ExpectedBase == Resource.FirstVMA->Base;
});
LOGMAN_THROW_A_FMT(ResourceIt != ResourceEnd, "ERROR: Could not find base for file mapping at {:#x} (offset {:#x})", addr, offset);
Resource = &ResourceIt->second;
} }
const fextl::string Filename = FHU::Filesystem::GetFilename(Resource->MappedFile->Filename); const fextl::string Filename = FHU::Filesystem::GetFilename(Resource->MappedFile->Filename);
@@ -418,12 +469,12 @@ std::optional<SyscallHandler::LateApplyExtendedVolatileMetadata> SyscallHandler:
} else if (flags & MAP_SHARED) { } else if (flags & MAP_SHARED) {
VMATracking::MRID mrid {VMATracking::SpecialDev::Anon, AnonSharedId++}; VMATracking::MRID mrid {VMATracking::SpecialDev::Anon, AnonSharedId++};
auto [Iter, Inserted] = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, 0}); auto [Iter, IterEnd] = VMATracking.FindResources(mrid);
LOGMAN_THROW_A_FMT(Inserted == true, "VMA tracking error"); LOGMAN_THROW_A_FMT(Iter == IterEnd, "VMA tracking error");
Iter = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, 0});
Resource = &Iter->second; Resource = &Iter->second;
Resource->Iterator = Iter; Resource->Iterator = Iter;
} else {
Resource = nullptr;
} }
VMATracking.TrackVMARange(CTX, Resource, addr, offset, Size, VMATracking::VMAFlags::fromFlags(flags), ProtMapping); VMATracking.TrackVMARange(CTX, Resource, addr, offset, Size, VMATracking::VMAFlags::fromFlags(flags), ProtMapping);
@@ -479,11 +530,12 @@ void SyscallHandler::TrackMremap(FEXCore::Core::InternalThreadState* Thread, uin
void SyscallHandler::TrackShmat(FEXCore::Core::InternalThreadState* Thread, int shmid, uint64_t shmaddr, int shmflg, uint64_t Length) { void SyscallHandler::TrackShmat(FEXCore::Core::InternalThreadState* Thread, int shmid, uint64_t shmaddr, int shmflg, uint64_t Length) {
VMATracking::MRID mrid {VMATracking::SpecialDev::SHM, static_cast<uint64_t>(shmid)}; VMATracking::MRID mrid {VMATracking::SpecialDev::SHM, static_cast<uint64_t>(shmid)};
auto [Iter, Inserted] = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, Length}); auto [Iter, IterEnd] = VMATracking.FindResources(mrid);
auto Resource = &Iter->second; if (Iter == IterEnd) {
if (Inserted) { Iter = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, Length});
Resource->Iterator = Iter; Iter->second.Iterator = Iter;
} }
auto Resource = &Iter->second;
VMATracking.TrackVMARange(CTX, Resource, shmaddr, 0, Length, VMATracking::VMAFlags::fromFlags(MAP_SHARED), VMATracking::VMAProt::fromSHM(shmflg)); VMATracking.TrackVMARange(CTX, Resource, shmaddr, 0, Length, VMATracking::VMAFlags::fromFlags(MAP_SHARED), VMATracking::VMAProt::fromSHM(shmflg));
} }
@@ -35,7 +35,7 @@ auto VMAFlags::fromFlags(int Flags) -> VMAFlags {
} }
/// List Operations /// /// List Operations ///
inline void VMATracking::ListCheckVMALinks(VMAEntry* VMA) { static inline void ListCheckVMALinks(const VMAEntry* VMA) {
if (VMA) { if (VMA) {
LOGMAN_THROW_A_FMT(VMA->ResourceNextVMA != VMA, "VMA tracking error"); LOGMAN_THROW_A_FMT(VMA->ResourceNextVMA != VMA, "VMA tracking error");
LOGMAN_THROW_A_FMT(VMA->ResourcePrevVMA != VMA, "VMA tracking error"); LOGMAN_THROW_A_FMT(VMA->ResourcePrevVMA != VMA, "VMA tracking error");
@@ -44,7 +44,7 @@ inline void VMATracking::ListCheckVMALinks(VMAEntry* VMA) {
// Removes a VMA from corresponding MappedResource list // Removes a VMA from corresponding MappedResource list
// Returns true if list is empty // Returns true if list is empty
bool VMATracking::ListRemove(VMAEntry* VMA) { static bool ListRemove(VMAEntry* VMA) {
LOGMAN_THROW_A_FMT(VMA->Resource != nullptr, "VMA tracking error"); LOGMAN_THROW_A_FMT(VMA->Resource != nullptr, "VMA tracking error");
// if it has prev, make prev to next // if it has prev, make prev to next
@@ -78,7 +78,7 @@ bool VMATracking::ListRemove(VMAEntry* VMA) {
// Replaces a VMA in corresponding MappedResource list // Replaces a VMA in corresponding MappedResource list
// Requires NewVMA->Resource, NewVMA->ResourcePrevVMA and NewVMA->ResourceNextVMA to be already setup // Requires NewVMA->Resource, NewVMA->ResourcePrevVMA and NewVMA->ResourceNextVMA to be already setup
void VMATracking::ListReplace(VMAEntry* VMA, VMAEntry* NewVMA) { static void ListReplace(VMAEntry* VMA, VMAEntry* NewVMA) {
LOGMAN_THROW_A_FMT(VMA->Resource != nullptr, "VMA tracking error"); LOGMAN_THROW_A_FMT(VMA->Resource != nullptr, "VMA tracking error");
LOGMAN_THROW_A_FMT(VMA->Resource == NewVMA->Resource, "VMA tracking error"); LOGMAN_THROW_A_FMT(VMA->Resource == NewVMA->Resource, "VMA tracking error");
@@ -107,7 +107,7 @@ void VMATracking::ListReplace(VMAEntry* VMA, VMAEntry* NewVMA) {
// Inserts a VMA in corresponding MappedResource list // Inserts a VMA in corresponding MappedResource list
// Requires NewVMA->Resource, NewVMA->ResourcePrevVMA and NewVMA->ResourceNextVMA to be already setup // Requires NewVMA->Resource, NewVMA->ResourcePrevVMA and NewVMA->ResourceNextVMA to be already setup
void VMATracking::ListInsertAfter(VMAEntry* AfterVMA, VMAEntry* NewVMA) { static void ListInsertAfter(VMAEntry* AfterVMA, VMAEntry* NewVMA) {
LOGMAN_THROW_A_FMT(NewVMA->Resource != nullptr, "VMA tracking error"); LOGMAN_THROW_A_FMT(NewVMA->Resource != nullptr, "VMA tracking error");
LOGMAN_THROW_A_FMT(AfterVMA->Resource == NewVMA->Resource, "VMA tracking error"); LOGMAN_THROW_A_FMT(AfterVMA->Resource == NewVMA->Resource, "VMA tracking error");
@@ -128,7 +128,7 @@ void VMATracking::ListInsertAfter(VMAEntry* AfterVMA, VMAEntry* NewVMA) {
// Prepends a VMA // Prepends a VMA
// Requires NewVMA->Resource, NewVMA->ResourcePrevVMA and NewVMA->ResourceNextVMA to be already setup // Requires NewVMA->Resource, NewVMA->ResourcePrevVMA and NewVMA->ResourceNextVMA to be already setup
void VMATracking::ListPrepend(MappedResource* Resource, VMAEntry* NewVMA) { static void ListPrepend(MappedResource* Resource, VMAEntry* NewVMA) {
LOGMAN_THROW_A_FMT(Resource != nullptr, "VMA tracking error"); LOGMAN_THROW_A_FMT(Resource != nullptr, "VMA tracking error");
LOGMAN_THROW_A_FMT(NewVMA->Resource == Resource, "VMA tracking error"); LOGMAN_THROW_A_FMT(NewVMA->Resource == Resource, "VMA tracking error");
@@ -8,6 +8,8 @@
#include <FEXCore/fextl/memory.h> #include <FEXCore/fextl/memory.h>
#include <FEXCore/Utils/SignalScopeGuards.h> #include <FEXCore/Utils/SignalScopeGuards.h>
#include <elf.h>
namespace FEX::HLE::VMATracking { namespace FEX::HLE::VMATracking {
///// VMA (Virtual Memory Area) tracking ///// ///// VMA (Virtual Memory Area) tracking /////
@@ -30,15 +32,24 @@ struct MRID {
struct VMAEntry; struct VMAEntry;
// Used to all MAP_SHARED VMAs of a system resource. /**
* Meta data associated to one system resource.
*
* Typically there is one instance of this type per ELF/PE file or special device.
* However if an ELF/PE file is mapped multiple times at different base addresses,
* there will be one separate MappedResource for each base address. The MRID
* is the same in this case.
*/
struct MappedResource { struct MappedResource {
using ContainerType = fextl::map<MRID, MappedResource>; using ContainerType = fextl::multimap<MRID, MappedResource>;
fextl::unique_ptr<FEXCore::ExecutableFileInfo> MappedFile; fextl::unique_ptr<FEXCore::ExecutableFileInfo> MappedFile;
// Pointer to lowest memory range this file is mapped to // Pointer to lowest memory range this file is mapped to
VMAEntry* FirstVMA; VMAEntry* FirstVMA;
uint64_t Length; // 0 if not fixed size uint64_t Length; // 0 if not fixed size
ContainerType::iterator Iterator; ContainerType::iterator Iterator;
fextl::vector<Elf64_Phdr> ProgramHeaders;
}; };
union VMAProt { union VMAProt {
@@ -115,13 +126,16 @@ struct VMATracking {
inline auto InsertMappedResource(const MRID& mrid, MappedResource Resource) { inline auto InsertMappedResource(const MRID& mrid, MappedResource Resource) {
return MappedResources.emplace(mrid, std::move(Resource)); return MappedResources.emplace(mrid, std::move(Resource));
} }
private:
bool ListRemove(VMAEntry* Mapping);
void ListReplace(VMAEntry* Mapping, VMAEntry* NewMapping);
void ListInsertAfter(VMAEntry* Mapping, VMAEntry* NewMapping);
void ListPrepend(MappedResource* Resource, VMAEntry* NewVMA);
static void ListCheckVMALinks(VMAEntry* VMA);
// Returns an iterator pair spanning the range of all MappedResources matching the given MRID.
// Typically there is only one associated resource, however sometimes the same file gets mapped
// multiple times at different base addresses. In that case, each MappedResource will cover an
// exclusive set of VMAEntries that refer to a consistent base mapping address.
inline auto FindResources(const MRID& mrid) {
return MappedResources.equal_range(mrid);
}
private:
MappedResource::ContainerType MappedResources; MappedResource::ContainerType MappedResources;
}; };
@@ -53,6 +53,8 @@ void ThreadManager::StatAlloc::Initialize() {
goto err; goto err;
} }
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(Base), MAX_STATS_SIZE);
// Allocate a small working shared space for now, grow as necessary. // Allocate a small working shared space for now, grow as necessary.
{ {
auto SharedBase = FEXCore::Allocator::mmap(Base, CurrentSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); auto SharedBase = FEXCore::Allocator::mmap(Base, CurrentSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
@@ -175,6 +177,9 @@ FEX::HLE::ThreadStateObject* ThreadManager::CreateThread(uint64_t InitialRIP, ui
// Allocate the call-ret stack with guard pages on both sides // Allocate the call-ret stack with guard pages on both sides
auto AllocBase = auto AllocBase =
reinterpret_cast<uint64_t>(FEXCore::Allocator::mmap(nullptr, CALLRET_STACK_ALLOC_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); reinterpret_cast<uint64_t>(FEXCore::Allocator::mmap(nullptr, CALLRET_STACK_ALLOC_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(AllocBase), CALLRET_STACK_ALLOC_SIZE);
// Set the base used for invalidation to the start past the guard pages // Set the base used for invalidation to the start past the guard pages
ThreadStateObject->Thread->CallRetStackBase = reinterpret_cast<void*>(AllocBase + FEXCore::Utils::FEX_PAGE_SIZE); ThreadStateObject->Thread->CallRetStackBase = reinterpret_cast<void*>(AllocBase + FEXCore::Utils::FEX_PAGE_SIZE);
::mprotect(ThreadStateObject->Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE, PROT_READ | PROT_WRITE); ::mprotect(ThreadStateObject->Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE, PROT_READ | PROT_WRITE);
@@ -199,6 +204,7 @@ FEX::HLE::ThreadStateObject* ThreadManager::CreateThread(uint64_t InitialRIP, ui
const auto new_ldt_size = InheritThread->ldt_entry_count * FEX::HLE::SyscallHandler::LDT_ENTRY_SIZE; const auto new_ldt_size = InheritThread->ldt_entry_count * FEX::HLE::SyscallHandler::LDT_ENTRY_SIZE;
ThreadStateObject->ldt_entries = reinterpret_cast<FEXCore::Core::CPUState::gdt_segment*>( ThreadStateObject->ldt_entries = reinterpret_cast<FEXCore::Core::CPUState::gdt_segment*>(
FEXCore::Allocator::mmap(nullptr, new_ldt_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); FEXCore::Allocator::mmap(nullptr, new_ldt_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(ThreadStateObject->ldt_entries), new_ldt_size);
ThreadStateObject->ldt_entry_count = InheritThread->ldt_entry_count; ThreadStateObject->ldt_entry_count = InheritThread->ldt_entry_count;
memcpy(ThreadStateObject->ldt_entries, InheritThread->ldt_entries, new_ldt_size); memcpy(ThreadStateObject->ldt_entries, InheritThread->ldt_entries, new_ldt_size);
@@ -208,10 +208,9 @@ public:
// Thread object isn't valid very early in frontend's initialization. // Thread object isn't valid very early in frontend's initialization.
// To be more optimal the frontend should provide this code with a valid Thread object earlier. // To be more optimal the frontend should provide this code with a valid Thread object earlier.
auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread); auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread);
FEXCore::Context::InvalidatedEntryAccumulator Accumulator; CTX->InvalidateCodeBuffersCodeRange(Start, Length);
for (auto& Thread : Threads) { for (auto& Thread : Threads) {
CTX->InvalidateGuestCodeRange(Thread->Thread, Accumulator, Start, Length); CTX->InvalidateThreadCachedCodeRange(Thread->Thread, Start, Length);
} }
} }
@@ -223,10 +222,9 @@ public:
// Thread object isn't valid very early in frontend's initialization. // Thread object isn't valid very early in frontend's initialization.
// To be more optimal the frontend should provide this code with a valid Thread object earlier. // To be more optimal the frontend should provide this code with a valid Thread object earlier.
auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread); auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread);
FEXCore::Context::InvalidatedEntryAccumulator Accumulator; CTX->InvalidateCodeBuffersCodeRange(Start, Length);
for (auto& Thread : Threads) { for (auto& Thread : Threads) {
CTX->InvalidateGuestCodeRange(Thread->Thread, Accumulator, Start, Length); CTX->InvalidateThreadCachedCodeRange(Thread->Thread, Start, Length);
} }
// Callback while holding the locks. // Callback while holding the locks.
@@ -4,6 +4,7 @@
#include <FEXCore/Core/Context.h> #include <FEXCore/Core/Context.h>
#include <FEXCore/Utils/Allocator.h> #include <FEXCore/Utils/Allocator.h>
#include <FEXCore/Utils/LongJump.h>
#include <FEXCore/Utils/Threads.h> #include <FEXCore/Utils/Threads.h>
namespace FEX::LinuxEmulation::Threads { namespace FEX::LinuxEmulation::Threads {
@@ -32,6 +33,7 @@ void* StackTracker::AllocateStackObject() {
if (Ptr == nullptr) { if (Ptr == nullptr) {
Ptr = FEXCore::Allocator::mmap(nullptr, FEX::LinuxEmulation::Threads::STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); Ptr = FEXCore::Allocator::mmap(nullptr, FEX::LinuxEmulation::Threads::STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(Ptr), FEX::LinuxEmulation::Threads::STACK_SIZE);
} }
return Ptr; return Ptr;
@@ -189,143 +191,6 @@ __attribute__((naked)) void StackPivotAndCall(void* Arg, FEXCore::Threads::Threa
} }
#endif #endif
namespace PThreads { namespace PThreads {
namespace LongJump {
// This is a custom long jump implementation that avoids the glibc implementation.
// This is required behaviour because glibc's fortification checks don't understand stack pivots.
// FEX requires a stack pivot to work through a long jump, so these two features are at odds with each other.
#ifdef _M_ARM_64
struct JumpBuf {
// All the registers that are required by AAPCS64 to save.
// GPRs
// X19, X20, X21, X22,
// X23, X24, X25, X26,
// X27, X28, X29, X30,
//
// Lower 64-bits:
// V8, V9, V10, V11,
// V12, V13, V14, V15,
//
// SP,
uint64_t Registers[21];
};
FEX_NAKED uint64_t SetJump(JumpBuf& Buffer) {
__asm volatile(R"(
// x0 contains the jumpbuffer
stp x19, x20, [x0, #( 0 * 8)];
stp x21, x22, [x0, #( 2 * 8)];
stp x23, x24, [x0, #( 4 * 8)];
stp x25, x26, [x0, #( 6 * 8)];
stp x27, x28, [x0, #( 8 * 8)];
stp x29, x30, [x0, #(10 * 8)];
// FPRs
stp d8, d9, [x0, #(12 * 8)];
stp d10, d11, [x0, #(14 * 8)];
stp d12, d13, [x0, #(16 * 8)];
stp d14, d15, [x0, #(18 * 8)];
// Move SP in to a temporary to store.
mov x1, sp;
str x1, [x0, #(19 * 8)];
// Return zero to signify this is the SetJump.
mov x0, #0;
ret;
)" ::
: "memory");
}
[[noreturn]]
FEX_NAKED void LongJump(JumpBuf& Buffer, uint64_t Value) {
__asm volatile(R"(
// x0 contains the jumpbuffer
ldp x19, x20, [x0, #( 0 * 8)];
ldp x21, x22, [x0, #( 2 * 8)];
ldp x23, x24, [x0, #( 4 * 8)];
ldp x25, x26, [x0, #( 6 * 8)];
ldp x27, x28, [x0, #( 8 * 8)];
ldp x29, x30, [x0, #(10 * 8)];
// FPRs
ldp d8, d9, [x0, #(12 * 8)];
ldp d10, d11, [x0, #(14 * 8)];
ldp d12, d13, [x0, #(16 * 8)];
ldp d14, d15, [x0, #(18 * 8)];
// Load SP in to temporary then move
ldr x0, [x0, #(19 * 8)];
mov sp, x0;
// Move value in to result register
mov x0, x1;
ret;
)" ::
: "memory");
}
#else
struct JumpBuf {
// Registers to preserve
// RBX, RSP, RBP, R12, R13, R14, R15,
// <return address>
uint64_t Registers[8];
};
__attribute__((naked)) uint64_t SetJump(JumpBuf& Buffer) {
__asm volatile(R"(
.intel_syntax noprefix;
// rdi contains the jumpbuffer
mov [rdi + (0 * 8)], rbx;
mov [rdi + (1 * 8)], rsp;
mov [rdi + (2 * 8)], rbp;
mov [rdi + (3 * 8)], r12;
mov [rdi + (4 * 8)], r13;
mov [rdi + (5 * 8)], r14;
mov [rdi + (6 * 8)], r15;
// Return address is on the stack, load it and store
mov rsi, [rsp];
mov [rdi + (7 * 8)], rsi;
// Return zero to signify this is the SetJump.
mov rax, 0;
ret;
.att_syntax prefix;
)" ::
: "memory");
}
[[noreturn]]
__attribute__((naked)) void LongJump(JumpBuf& Buffer, uint64_t Value) {
__asm volatile(R"(
.intel_syntax noprefix;
// rdi contains the jumpbuffer
mov rbx, [rdi + (0 * 8)];
mov rsp, [rdi + (1 * 8)];
mov rbp, [rdi + (2 * 8)];
mov r12, [rdi + (3 * 8)];
mov r13, [rdi + (4 * 8)];
mov r14, [rdi + (5 * 8)];
mov r15, [rdi + (6 * 8)];
// Move value in to result register
mov rax, rsi;
// Pop the dead return address off the stack
pop rsi;
// Load the original return address from the jumpbuffer
mov rsi, [rdi + (7 * 8)];
// Return using a jump
jmp rsi;
.att_syntax prefix;
)" ::
: "memory");
}
#endif
}; // namespace LongJump
void* InitializeThread(void* Ptr); void* InitializeThread(void* Ptr);
class PThread final : public FEXCore::Threads::Thread { class PThread final : public FEXCore::Threads::Thread {
@@ -396,7 +261,7 @@ namespace PThreads {
return STracker; return STracker;
} }
void SetupLongJump(LongJump::JumpBuf* exit_resolver) { void SetupLongJump(FEXCore::LongJump::JumpBuf* exit_resolver) {
_exit_resolver = exit_resolver; _exit_resolver = exit_resolver;
} }
@@ -404,7 +269,7 @@ namespace PThreads {
void LongJumpExit(FEX::HLE::ThreadStateObject* ThreadObject, uint32_t Status) { void LongJumpExit(FEX::HLE::ThreadStateObject* ThreadObject, uint32_t Status) {
this->Status = Status; this->Status = Status;
this->ThreadObject = ThreadObject; this->ThreadObject = ThreadObject;
LongJump::LongJump(*_exit_resolver, 1); FEXCore::LongJump::LongJump(*_exit_resolver, 1);
FEX_UNREACHABLE; FEX_UNREACHABLE;
} }
@@ -423,7 +288,9 @@ namespace PThreads {
void* UserArg; void* UserArg;
void* Stack {}; void* Stack {};
LongJump::JumpBuf* _exit_resolver {}; // Use FEXCore's LongJump to avoid fortification checks.
// This avoids a false positive since glibc does not understand stack pivots.
FEXCore::LongJump::JumpBuf* _exit_resolver {};
FEX::HLE::ThreadStateObject* ThreadObject {}; FEX::HLE::ThreadStateObject* ThreadObject {};
uint32_t Status {}; uint32_t Status {};
}; };
@@ -434,11 +301,11 @@ namespace PThreads {
PThread* Thread {reinterpret_cast<PThread*>(Ptr)}; PThread* Thread {reinterpret_cast<PThread*>(Ptr)};
StackBase = Thread->GetPivotStack(); StackBase = Thread->GetPivotStack();
STracker = Thread->GetStackTracker(); STracker = Thread->GetStackTracker();
LongJump::JumpBuf exit_resolver {}; FEXCore::LongJump::JumpBuf exit_resolver {};
bool LongJumpExit {}; bool LongJumpExit {};
if (LongJump::SetJump(exit_resolver) == 0) { if (FEXCore::LongJump::SetJump(exit_resolver) == 0) {
Thread->SetupLongJump(&exit_resolver); Thread->SetupLongJump(&exit_resolver);
// Run the user function. // Run the user function.
// `Thread` object is dead after this function returns. // `Thread` object is dead after this function returns.
@@ -117,7 +117,9 @@ auto fcntlHandler = [](FEXCore::Core::CpuStateFrame* Frame, int fd, int cmd, uin
case F_DUPFD_CLOEXEC: case F_DUPFD_CLOEXEC:
case F_GETFD: case F_GETFD:
case F_SETFD: case F_SETFD:
case F_GETFL: break; case F_GETFL:
case F_ADD_SEALS:
case F_GET_SEALS: break;
default: LOGMAN_MSG_A_FMT("Unhandled fcntl64: 0x{:x}", cmd); break; default: LOGMAN_MSG_A_FMT("Unhandled fcntl64: 0x{:x}", cmd); break;
} }
@@ -142,7 +142,7 @@ void RegisterInfo(FEX::HLE::SyscallHandler* Handler) {
uint32_t ShiftAmount {}; uint32_t ShiftAmount {};
if ((Host.totalram >> 32) != 0 || (Host.totalswap >> 32) != 0) { if ((Host.totalram >> 32) != 0 || (Host.totalswap >> 32) != 0) {
while (Host.mem_unit < 4096) { while (Host.mem_unit < FEXCore::Utils::FEX_PAGE_SIZE) {
Host.mem_unit <<= 1; Host.mem_unit <<= 1;
++ShiftAmount; ++ShiftAmount;
} }
@@ -52,7 +52,7 @@ public:
return FEX::HLE::SyscallHandler::GuestMunmap(false, Thread, addr, length); return FEX::HLE::SyscallHandler::GuestMunmap(false, Thread, addr, length);
} }
void RegisterSyscall_32(int SyscallNumber, int32_t HostSyscallNumber, FEXCore::IR::SyscallFlags Flags, void RegisterSyscall_32(int SyscallNumber,
#ifdef DEBUG_STRACE #ifdef DEBUG_STRACE
const fextl::string& TraceFormatString, const fextl::string& TraceFormatString,
#endif #endif
@@ -63,8 +63,6 @@ public:
#endif #endif
Def.Ptr = SyscallHandler; Def.Ptr = SyscallHandler;
Def.NumArgs = ArgumentCount; Def.NumArgs = ArgumentCount;
Def.Flags = Flags;
Def.HostSyscallNumber = HostSyscallNumber;
#ifdef DEBUG_STRACE #ifdef DEBUG_STRACE
Def.StraceFmt = TraceFormatString; Def.StraceFmt = TraceFormatString;
#endif #endif
@@ -87,12 +85,11 @@ fextl::unique_ptr<FEX::HLE::SyscallHandler> CreateHandler(FEXCore::Context::Cont
// Deduces return, args... from the function passed // Deduces return, args... from the function passed
// Does not work with lambas, because they are objects with operator (), not functions // Does not work with lambas, because they are objects with operator (), not functions
template<typename R, typename... Args> template<typename R, typename... Args>
void RegisterSyscall(SyscallHandler* Handler, int SyscallNumber, int32_t HostSyscallNumber, FEXCore::IR::SyscallFlags Flags, void RegisterSyscall(SyscallHandler* Handler, int SyscallNumber, const char* Name, R (*fn)(FEXCore::Core::CpuStateFrame* Frame, Args...)) {
const char* Name, R (*fn)(FEXCore::Core::CpuStateFrame* Frame, Args...)) {
#ifdef DEBUG_STRACE #ifdef DEBUG_STRACE
auto TraceFormatString = fextl::string(Name) + "(" + CollectArgsFmtString<Args...>() + ") = %ld"; auto TraceFormatString = fextl::string(Name) + "(" + CollectArgsFmtString<Args...>() + ") = %ld";
#endif #endif
Handler->RegisterSyscall_32(SyscallNumber, HostSyscallNumber, Flags, Handler->RegisterSyscall_32(SyscallNumber,
#ifdef DEBUG_STRACE #ifdef DEBUG_STRACE
TraceFormatString, TraceFormatString,
#endif #endif
@@ -103,29 +100,14 @@ void RegisterSyscall(SyscallHandler* Handler, int SyscallNumber, int32_t HostSys
// Non-capturing lambdas can be cast to function pointers, but this does not happen on argument matching // Non-capturing lambdas can be cast to function pointers, but this does not happen on argument matching
// This is some glue logic that will cast a lambda and call the base RegisterSyscall implementation // This is some glue logic that will cast a lambda and call the base RegisterSyscall implementation
template<class F> template<class F>
void RegisterSyscall(SyscallHandler* _Handler, int num, int32_t HostSyscallNumber, FEXCore::IR::SyscallFlags Flags, const char* name, F f) { void RegisterSyscall(SyscallHandler* _Handler, int num, const char* name, F f) {
RegisterSyscall(_Handler, num, HostSyscallNumber, Flags, name, +f); RegisterSyscall(_Handler, num, name, +f);
} }
} // namespace FEX::HLE::x32 } // namespace FEX::HLE::x32
// Registers syscall for 32bit only // Registers syscall for 32bit only
#define REGISTER_SYSCALL_IMPL_X32(name, lambda) REGISTER_SYSCALL_IMPL_X32_INTERNAL(name, ~0, FEXCore::IR::SyscallFlags::DEFAULT, lambda) #define REGISTER_SYSCALL_IMPL_X32(name, lambda) \
do { \
#define REGISTER_SYSCALL_IMPL_X32_PASS(name, lambda) \ FEX::HLE::x32::RegisterSyscall(Handler, x32::SYSCALL_x86_##name, #name, lambda); \
REGISTER_SYSCALL_IMPL_X32_INTERNAL(name, SYSCALL_DEF(name), FEXCore::IR::SyscallFlags::DEFAULT, lambda)
#define REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL(name, hostname, lambda) \
REGISTER_SYSCALL_IMPL_X32_INTERNAL(name, SYSCALL_DEF(hostname), FEXCore::IR::SyscallFlags::DEFAULT, lambda)
#define REGISTER_SYSCALL_IMPL_X32_FLAGS(name, flags, lambda) REGISTER_SYSCALL_IMPL_X32_INTERNAL(name, ~0, flags, lambda)
#define REGISTER_SYSCALL_IMPL_X32_PASS_FLAGS(name, flags, lambda) REGISTER_SYSCALL_IMPL_X32_INTERNAL(name, SYSCALL_DEF(name), flags, lambda)
#define REGISTER_SYSCALL_IMPL_X32_PASS_MANUAL_FLAGS(name, hostname, flags, lambda) \
REGISTER_SYSCALL_IMPL_X32_INTERNAL(name, SYSCALL_DEF(hostname), flags, lambda)
#define REGISTER_SYSCALL_IMPL_X32_INTERNAL(name, number, flags, lambda) \
do { \
FEX::HLE::x32::RegisterSyscall(Handler, x32::SYSCALL_x86_##name, number, flags, #name, lambda); \
} while (false) } while (false)
@@ -19,11 +19,10 @@ void RegisterInfo(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
if (Handler->IsHostKernelVersionAtLeast(6, 6, 0)) { if (Handler->IsHostKernelVersionAtLeast(6, 6, 0)) {
REGISTER_SYSCALL_IMPL_X64_FLAGS(map_shadow_stack, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(map_shadow_stack, [](FEXCore::Core::CpuStateFrame* Frame, uint64_t addr, uint64_t size, uint32_t flags) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, uint64_t addr, uint64_t size, uint32_t flags) -> uint64_t { // Claim that shadow stack isn't supported.
// Claim that shadow stack isn't supported. return -EOPNOTSUPP;
return -EOPNOTSUPP; });
});
} else { } else {
REGISTER_SYSCALL_IMPL_X64(map_shadow_stack, UnimplementedSyscallSafe); REGISTER_SYSCALL_IMPL_X64(map_shadow_stack, UnimplementedSyscallSafe);
} }
@@ -27,36 +27,30 @@ namespace FEX::HLE::x64 {
void RegisterMemory(FEX::HLE::SyscallHandler* Handler) { void RegisterMemory(FEX::HLE::SyscallHandler* Handler) {
using namespace FEXCore::IR; using namespace FEXCore::IR;
REGISTER_SYSCALL_IMPL_X64_FLAGS( REGISTER_SYSCALL_IMPL_X64(
mmap, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, mmap, [](FEXCore::Core::CpuStateFrame* Frame, void* addr, size_t length, int prot, int flags, int fd, off_t offset) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, void* addr, size_t length, int prot, int flags, int fd, off_t offset) -> uint64_t {
return (uint64_t)FEX::HLE::_SyscallHandler->GuestMmap(Frame->Thread, addr, length, prot, flags, fd, offset); return (uint64_t)FEX::HLE::_SyscallHandler->GuestMmap(Frame->Thread, addr, length, prot, flags, fd, offset);
}); });
REGISTER_SYSCALL_IMPL_X64_FLAGS(munmap, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(munmap, [](FEXCore::Core::CpuStateFrame* Frame, void* addr, size_t length) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, void* addr, size_t length) -> uint64_t { return FEX::HLE::_SyscallHandler->GuestMunmap(Frame->Thread, addr, length);
return FEX::HLE::_SyscallHandler->GuestMunmap(Frame->Thread, addr, length); });
});
REGISTER_SYSCALL_IMPL_X64_FLAGS( REGISTER_SYSCALL_IMPL_X64(
mremap, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, mremap, [](FEXCore::Core::CpuStateFrame* Frame, void* old_address, size_t old_size, size_t new_size, int flags, void* new_address) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, void* old_address, size_t old_size, size_t new_size, int flags, void* new_address) -> uint64_t {
return FEX::HLE::_SyscallHandler->GuestMremap(true, Frame->Thread, old_address, old_size, new_size, flags, new_address); return FEX::HLE::_SyscallHandler->GuestMremap(true, Frame->Thread, old_address, old_size, new_size, flags, new_address);
}); });
REGISTER_SYSCALL_IMPL_X64_FLAGS(mprotect, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(mprotect, [](FEXCore::Core::CpuStateFrame* Frame, void* addr, size_t len, int prot) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, void* addr, size_t len, int prot) -> uint64_t { return FEX::HLE::_SyscallHandler->GuestMprotect(Frame->Thread, addr, len, prot);
return FEX::HLE::_SyscallHandler->GuestMprotect(Frame->Thread, addr, len, prot); });
});
REGISTER_SYSCALL_IMPL_X64_FLAGS(shmat, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(shmat, ([](FEXCore::Core::CpuStateFrame* Frame, int shmid, const void* shmaddr, int shmflg) -> uint64_t {
([](FEXCore::Core::CpuStateFrame* Frame, int shmid, const void* shmaddr, int shmflg) -> uint64_t { return FEX::HLE::_SyscallHandler->GuestShmat(true, Frame->Thread, shmid, shmaddr, shmflg);
return FEX::HLE::_SyscallHandler->GuestShmat(true, Frame->Thread, shmid, shmaddr, shmflg); }));
}));
REGISTER_SYSCALL_IMPL_X64_FLAGS(shmdt, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, REGISTER_SYSCALL_IMPL_X64(shmdt, [](FEXCore::Core::CpuStateFrame* Frame, const void* shmaddr) -> uint64_t {
[](FEXCore::Core::CpuStateFrame* Frame, const void* shmaddr) -> uint64_t { return FEX::HLE::_SyscallHandler->GuestShmdt(true, Frame->Thread, shmaddr);
return FEX::HLE::_SyscallHandler->GuestShmdt(true, Frame->Thread, shmaddr); });
});
} }
} // namespace FEX::HLE::x64 } // namespace FEX::HLE::x64
@@ -64,9 +64,7 @@ void x64SyscallHandler::RegisterSyscallHandlers() {
const SyscallFunctionDefinition InvalidSyscall { const SyscallFunctionDefinition InvalidSyscall {
.Ptr = reinterpret_cast<void*>(&UnimplementedSyscall), .Ptr = reinterpret_cast<void*>(&UnimplementedSyscall),
.HostSyscallNumber = SYSCALL_DEF(MAX),
.NumArgs = 0, .NumArgs = 0,
.Flags = FEXCore::IR::SyscallFlags::DEFAULT,
#ifdef DEBUG_STRACE #ifdef DEBUG_STRACE
.StraceFmt = "Invalid", .StraceFmt = "Invalid",
#endif #endif
Loaded 100 of 580 files, more files were not shown because too many files have changed in this diff. Show more