Compare commits

...

83 Commits

Author SHA1 Message Date
6a1a03566a T32: Implement load/store single (immediate) (#3186)
* T32: Implement load/store single (immediate)

* tests

* tidy formatting

* address comments
2022-04-21 01:25:43 +02:00
13f5294aa3 Update NpadController.cs (#3284) 2022-04-20 13:22:45 +02:00
9444b4a647 Implement HwOpus multistream functions (#3275)
* Implement HwOpus multistream functions

* Avoid one copy
2022-04-15 23:16:28 +02:00
610fc84f3e ReactiveObject: Handle case when oldValue is null (#3268) 2022-04-15 12:58:27 +02:00
247d26b4b5 ForceDpiAware: X11 implementation (#3269)
* ForceDpiAware: X11 implementation

* address comments
2022-04-10 19:04:22 +02:00
43ebd7a9bb New shader cache implementation (#3194)
* New shader cache implementation

* Remove some debug code

* Take transform feedback varying count into account

* Create shader cache directory if it does not exist + fragment output map related fixes

* Remove debug code

* Only check texture descriptors if the constant buffer is bound

* Also check CPU VA on GetSpanMapped

* Remove more unused code and move cache related code

* XML docs + remove more unused methods

* Better codegen for TransformFeedbackDescriptor.AsSpan

* Support migration from old cache format, remove more unused code

Shader cache rebuild now also rewrites the shared toc and data files

* Fix migration error with BRX shaders

* Add a limit to the async translation queue

 Avoid async translation threads not being able to keep up and the queue growing very large

* Re-create specialization state on recompile

This might be required if a new version of the shader translator requires more or less state, or if there is a bug related to the GPU state access

* Make shader cache more error resilient

* Add some missing XML docs and move GpuAccessor docs to the interface/use inheritdoc

* Address early PR feedback

* Fix rebase

* Remove IRenderer.CompileShader and IShader interface, replace with new ShaderSource struct passed to CreateProgram directly

* Handle some missing exceptions

* Make shader cache purge delete both old and new shader caches

* Register textures on new specialization state

* Translate and compile shaders in forward order (eliminates diffs due to different binding numbers)

* Limit in-flight shader compilation to the maximum number of compilation threads

* Replace ParallelDiskCacheLoader state changed event with a callback function

* Better handling for invalid constant buffer 1 data length

* Do not create the old cache directory structure if the old cache does not exist

* Constant buffer use should be per-stage. This change will invalidate existing new caches (file format version was incremented)

* Replace rectangle texture with just coordinate normalization

* Skip incompatible shaders that are missing texture information, instead of crashing

This is required if we, for example, support new texture instruction to the shader translator, and then they allow access to textures that were not accessed before. In this scenario, the old cache entry is no longer usable

* Fix coordinates normalization on cubemap textures

* Check if title ID is null before combining shader cache path

* More robust constant buffer address validation on spec state

* More robust constant buffer address validation on spec state (2)

* Regenerate shader cache with one stream, rather than one per shader.

* Only create shader cache directory during initialization

* Logging improvements

* Proper shader program disposal

* PR feedback, and add a comment on serialized structs

* XML docs for RegisterTexture

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2022-04-10 10:49:44 -03:00
26a881176e Fix tail merge from block with conditional jump to multiple returns (#3267)
* Fix tail merge from block with conditional jump to multiple returns

* PPTC version bump
2022-04-09 16:56:50 +02:00
e44a43c7e1 Implement VMAD shader instruction and improve InvocationInfo and ISBERD handling (#3251)
* Implement VMAD shader instruction and improve InvocationInfo and ISBERD handling

* Shader cache version bump

* Fix typo
2022-04-08 12:42:39 +02:00
3139a85a2b Allow copy texture views to have mismatching multisample state (#3152) 2022-04-08 11:26:48 +02:00
a4e8bea866 Lop3Expression: Optimize expressions (#3184)
* lut3

* bugfixes

* TruthTable

* false/true -> 0/-1

* add or to expressions

* fix inversions

* increment cache version
2022-04-08 11:17:38 +02:00
6a9e9b5360 Remove save data creation prompt (#3252)
* begone

* review

* mods directory update
2022-04-08 11:09:35 +02:00
952f6f8a65 Calculate vertex buffer size from index buffer type (#3253)
* Calculate vertex buffer size from index buffer type

* We also need to update the size if first vertex changes
2022-04-08 11:02:06 +02:00
d04ba51bb0 amadeus: Improve and fix delay effect processing (#3205)
* amadeus: Improve and fix delay effect processing

This rework the delay effect processing by representing calculation with the appropriate matrix and by unrolling some loop in the code.
This allows better optimization by the JIT while making it more readeable.

Also fix a bug in the Surround code path found while looking back at my notes.

* Remove useless GetHashCode

* Address gdkchan's comments
2022-04-08 10:52:18 +02:00
55ee261363 service: hid: Signal event on AcquireNpadStyleSetUpdateEventHandle (#3247) 2022-04-07 15:43:14 -03:00
4e3a34412e Update to LibHac 0.16.1 (#3263) 2022-04-07 15:18:14 -03:00
3f4fb8f73a amadeus: Update to REV11 (#3230)
This should implement all ABI changes from REV11 on 14.0.0

As Nintendo changed the channel disposition for "legacy" effects (Delay, Reverb and Reverb 3D) to match the standard channel mapping, I took the liberty to just remap to the old disposition for now.
The proper changes will be handled at a later date with a complete rewriting of those 3 effects to be more readable (see https://github.com/Ryujinx/Ryujinx/pull/3205 for the first iteration of it).
2022-04-06 09:12:38 +02:00
56c56aa34d Do not clamp SNorm outputs to the [0, 1] range on OpenGL (#3260) 2022-04-05 18:09:06 -03:00
d4b960d348 Implement primitive restart draw arrays properly on OpenGL (#3256) 2022-04-04 18:43:24 -03:00
b2a225558d Do not force scissor on clear if scissor is disabled (#3258) 2022-04-04 18:30:43 -03:00
0ef0fc044a Small graphics abstraction layer cleanup (#3257) 2022-04-04 18:21:06 -03:00
04bd87ed5a Fix shader textureSize with multisample and buffer textures (#3240)
* Fix shader textureSize with multisample and buffer textures

* Replace out param with tuple return value
2022-04-04 14:43:58 -03:00
5158cdb308 infra: Put SDL2 headless release inside a GUI-less block in PR (#3218)
As title say.
2022-03-26 11:38:35 +01:00
1402d8391d Support NVDEC H264 interlaced video decoding and VIC deinterlacing (#3225)
* Support NVDEC H264 interlaced video decoding and VIC deinterlacing

* Remove unused code
2022-03-23 17:09:32 -03:00
e3b36db71c hle: Some cleanup (#3210)
* hle: Some cleanup

This PR cleaned up a bit the HLE folder and the VirtualFileSystem one, since we use LibHac, we can use some class of it directly instead of duplicate things. The "Content" of VFS folder is removed since it should be handled in the NCM service directly.
A larger cleanup should be done later since there is still be duplicated code here and there.

* Fix Headless.SDL2

* Addresses gdkchan feedback
2022-03-22 20:46:16 +01:00
ba0171d054 Memory.Tests: Make Multithreading test explicit (#3220) 2022-03-21 09:21:05 +01:00
d1146a5af2 Don't restore Viewport 0 if it hasn't been set yet. (#3219)
Fixes a driver crash when starting some games caused by #3217
2022-03-20 14:48:43 -03:00
79408b68c3 De-tile GOB when DMA copying from block linear to pitch kind memory regions (#3207)
* De-tile GOB when DMA copying from block linear to pitch kind memory regions

* XML docs + nits

* Remove using

* No flush for regular buffer copies

* Add back ulong casts, fix regression due to oversight
2022-03-20 13:55:07 -03:00
d461d4f68b Fix OpenGL issues with RTSS overlays and OBS Game Capture (#3217)
OpenGL game overlays and hooks tend to make a lot of assumptions about how games present frames to the screen, since presentation in OpenGL kind of sucks and they would like to have info such as the size of the screen, or if the contents are SRGB rather than linear.

There are two ways of getting this. OBS hooks swap buffers to get a frame for video capture, but it actually checks the bound framebuffer at the time. I made sure that this matches the output framebuffer (the window) so that the output matches the size. RTSS checks the viewport size by default, but this was actually set to the last used viewport by the game, causing the OSD to fly all across the screen depending on how it was used (or res scale). The viewport is now manually set to match the output framebuffer size.

In the case of RTSS, it also loads its resources by destructively setting a pixel pack parameter without regard to what it was set to by the guest application. OpenGL state can be set for a long period of time and is not expected to be set before each call to a method, so randomly changing it isn't great practice. To fix this, I've added a line to set the pixel unpack alignment back to 4 after presentation, which should cover RTSS loading its incredibly ugly font.

- RTSS and overlays that use it should no longer cause certain textures to load incorrectly. (mario kart 8, pokemon legends arceus)
- OBS Game Capture should no longer crop the game output incorrectly, flicker randomly, or capture with incorrect gamma.

This doesn't fix issues with how RTSS reports our frame timings.
2022-03-20 13:37:45 -03:00
b45d30acf8 oslc: Fix condition in GetSaveDataBackupSetting (#3208)
* oslc: Fix condition in GetSaveDataBackupSetting

This PR fixes a condition previously implemented in #3190 where ACNH can't be booted without an existing savedata.
Closes #3206

* Addresses gdkchan feedback
2022-03-20 13:25:29 -03:00
df70442c46 InstEmitMemoryEx: Barrier after write on ordered store (#3193)
* InstEmitMemoryEx: Barrier after write on ordered store

* increment ptc version

* 32
2022-03-19 10:32:35 -03:00
e2ffa5a125 ntc: Implement IEnsureNetworkClockAvailabilityService (#3192)
* ntc: Implement IEnsureNetworkClockAvailabilityService

This PR implement a basic `IEnsureNetworkClockAvailabilityService` checked by RE. It's needed by Splatoon 2 with Guest Internet Access enabled. Game is now playable with this setting.

* Update Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2022-03-15 04:07:07 +01:00
73feac5819 Caching local network info and using an event handler to invalidate as needed (improves menu slow down issue in FE3H) (#2761)
* Update IGeneralService.cs

Fix IPV4 local ip related frame drop in fire emblem by rewriting [CommandHipc(12)]

* Fix IPV4 Local IP Slowdown & Style Fixes

fix a missing space

* Remove unnecessary line

* Fix for hardcoding which index to use

* Replace argument with empty string.

By sending an empty string to Dns.GetHostAddresses("") you get back localhost info only.

* Add caching, undo change in GetCurrentIpAddress

Implement caching and revert the GetCurrentIP() function, speed improvements still present.

* Remove unnecessary using

* Syntax fixes and removing extra lines

Requested changes by AcK77

* Properly unsubscribe from event handler

Adds an unsubscribe in the dispose section of IGeneralService
2022-03-15 03:49:35 +01:00
e5ad1dfa48 Implement S8D24 texture format and tweak depth range detection (#2458) 2022-03-15 03:42:08 +01:00
79becc4b78 Dynamically increase buffer size when resizing (#2861)
* Grow buffers by 1.5x of its size when resizing

* Further restrict the cases where the dynamic expansion is done
2022-03-15 03:33:53 +01:00
223172ac0b Ui: Add option to show/hide console window (Windows-only) (#3170)
* Ui: Add option to show/hide console window (Windows-only)

* Ui: Only display Show Console menu item on Windows

* ConsoleHelper: Handle NULL case

This will never happen

* Address nits

* Address comments

* Address comments 2
2022-03-15 02:35:41 +01:00
8c9633d72f Initialize indexed inputs used on next shader stage (#3198) 2022-03-14 20:02:50 -03:00
1f93fd52d9 Do not initialize geometry shader passthrough attributes (#3196) 2022-03-14 19:35:41 -03:00
aac7bbd378 olsc: Implement GetSaveDataBackupSetting (#3190)
* olsc: Implement GetSaveDataBackupSetting

This PR implement GetSaveDataBackupSetting of OLSC service which is now needed by ACNH 2.0.5. The game is playable as usual if you use the same user profile as the original save file (I don't know if it was the case before), everything is checked by RE.

* addresses gdkchan feedback
2022-03-12 18:38:49 +01:00
bed516bfda Implement rotate stick 90 degrees clockwise (#3084)
* Implement swapping sticks

* Rotate 90 degrees clockwise

Co-authored-by: matesic.darko@gmail.com <Darkman1979>
2022-03-12 18:23:48 +01:00
69b05f9918 Fix GetUserDisableCount NRE (#3187) 2022-03-12 18:12:12 +01:00
fb7c80e928 Limit number of events that can be retrieved from GetDisplayVSyncEvent (#3188)
* Limit number of events that can be retrieved from GetDisplayVSyncEvent

* Cleaning

* Rename openDisplayInfos -> openDisplays
2022-03-12 17:56:19 +01:00
bb2f9df0a1 KThread: Fix GetPsr mask (#3180)
* ExecutionContext: GetPstate / SetPstate

* Put it in NativeContext

* KThread: Fix GetPsr mask

* ExecutionContext: Turn methods into Pstate property

* Address nit
2022-03-11 03:16:32 +01:00
54bfaa125d amadeus: Fix wrong Span usage in CopyHistories (#3181)
Fix a copypasta from the original Amadeus PR causing invalid
CopyHistories output.

Also added a missing size check.

This fix a crash in Mononoke Slashdown
2022-03-07 09:49:29 +01:00
7af9fcbc06 T32: Implement Data Processing (Modified Immediate) instructions (#3178)
* T32: Implement Data Processing (Modified Immediate) instructions

* Update tests

* switch -> lookup table
2022-03-06 22:25:01 +01:00
ee174be57c Mod loading from atmosphere SD directories (#3176)
* initial sd support

* GUI option

* alignment

* review changes
2022-03-06 22:12:01 +01:00
0bcbe32367 Only initialize shader outputs that are actually used on the next stage (#3054)
* Only initialize shader outputs that are actually used on the next stage

* Shader cache version bump
2022-03-06 20:42:13 +01:00
b97ff4da5e A32: Fix ALU immediate instructions (#3179)
* Tests: Add A32 tests for immediate ADC/ADCS/RSC/RSCS/SBC/SBCS

* A32: Fix bug in ADC/ADCS/RSC/RSCS/SBC/SBCS

* CpuTestAluImm32: Add more opcodes

* Increment PTC version
2022-03-05 15:23:10 -03:00
747081d2c7 Decoders: Fix instruction lengths for 16-bit B instructions (#3177) 2022-03-05 16:20:24 +01:00
497199bb50 Decoder: Exit on trapping instructions, and resume execution at trapping instruction (#3153)
* Decoder: Exit on trapping instructions, and resume execution at trapping instruction

* Resume at trapping address

* remove mustExit
2022-03-04 23:16:58 +01:00
bd9ac0fdaa T32: Implement B, B.cond, BL, BLX (#3155)
* Decoders: Make IsThumb a function of OpCode32

* OpCode32: Fix GetPc

* T32: Implement B, B.cond, BL, BLX

* rm usings
2022-03-04 23:05:08 +01:00
ac21abbb9d Preparation for initial Flatpack and FlatHub integration (#3173)
* Preparation for initial Flatpack and FlatHub integration

This integrate some initial changes required for Flatpack and distribution from FlatHub.

Also added some resources that will be used for packaging on Linux.

* Address gdkchan comment
2022-03-04 18:03:16 +01:00
a3dd04deef Implement -p or --profile command line argument (#2947)
* Implement -p or --profile command line argument

* Put command line logic all in Program and reference it elsewhere

* Address feedback
2022-03-02 09:51:37 +01:00
3705c20668 Update LibHac to v0.16.0 (#3159) 2022-02-27 00:52:25 +01:00
7b35ebc64a T32: Implement ALU (shifted register) instructions (#3135)
* T32: Implement ADC, ADD, AND, BIC, CMN, CMP, EOR, MOV, MVN, ORN, ORR, RSB, SBC, SUB, TEQ, TST (shifted register)

* OpCodeTable: Sort T32 list

* Tests: Rename RandomTestCase to PrecomputedThumbTestCase

* T32: Tests for AluRsImm instructions

* fix nit

* fix nit 2
2022-02-22 19:11:28 -03:00
0a24aa6af2 Allow textures to have their data partially mapped (#2629)
* Allow textures to have their data partially mapped

* Explicitly check for invalid memory ranges on the MultiRangeList

* Update GetWritableRegion to also support unmapped ranges
2022-02-22 13:34:16 -03:00
c9c65af59e Perform unscaled 2d engine copy on CPU if source texture isn't in cache. (#3112)
* Initial implementation of fast 2d copy

TODO: Partial copy for mismatching region/size.

* WIP

* Cleanup

* Update Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2022-02-22 11:21:29 -03:00
dc063eac83 ARMeilleure: Implement single stepping (#3133)
* Decoder: Implement SingleInstruction decoder mode

* Translator: Implement Step

* DecoderMode: Rename Normal to MultipleBlocks
2022-02-22 11:11:42 -03:00
ccf23fc629 gui: Fixes the games icon when there is an update (#3148)
* gui: Fixes the games icon when there is a game update

Currently we just load the version of the update, instead of the whole NACP file. This PR fixes that. A little cleanup is made into the code to avoid duplicate things.
(Closes #3039)

* Fix condition
2022-02-22 14:53:39 +01:00
f1460d5494 A32: Fix BLX and BXWritePC (#3151) 2022-02-22 10:41:56 -03:00
644b497df1 Collapse AsSpan().Slice(..) calls into AsSpan(..) (#3145)
* Collapse AsSpan().Slice(..) calls into AsSpan(..)

Less code and a bit faster

* Collapse an Array.Clear(array, 0, array.Length) call to Array.Clear(array)
2022-02-22 10:32:10 -03:00
fb935fd201 Add dedicated ServerBase for FileSystem services (#3142)
This should prevent filesystem services from blocking other services that don't have their own ServerBase. May improve filesystem related stutters in certain titles.

Improves button advanced cutscenes such as Miqol's Request in Xenoblade: DE when the game is on a network share (used to stutter when voice lines played).

Should probably be tested to make sure no mysterious bugs have been unearthed, and to see if any other filesystem related perf issues are improved.
2022-02-19 15:29:11 +01:00
f2087ca29e PPTC version increment (#3139) 2022-02-17 23:52:42 -03:00
92d166ecb7 Enable CPU JIT cache invalidation (#2965)
* Enable CPU JIT cache invalidation

* Invalidate cache on IC IVAU
2022-02-18 02:53:18 +01:00
72e543e946 Prefer texture over textureSize for sampler type (#3132)
* Prefer texture over textureSize for sampler type

* Shader cache version bump
2022-02-18 02:44:46 +01:00
98c838b24c Use BitOperations methods and delete now unused BitUtils methods (#3134)
Replaces BitUtils.CountTrailingZeros/CountLeadingZeros/IsPowerOfTwo with BitOperations methods
2022-02-18 02:35:23 +01:00
63c9c64196 Move kernel syscall logs to new trace log level (#3137) 2022-02-18 02:14:05 +01:00
a113ed0811 Implement/Stub mnpp:app service and some hid calls (#3131)
* Implement/Stub mnpp:app service and some hid calls

This PR Implement/Stub the `mnpp:app` service (closes #3107) accordingly to RE, which seems to do some telemetry for China region only, so everything is stubbed.

This PR fixes some inconsistencies in the hid service too and stub EnableSixAxisSensorUnalteredPassthrough, IsSixAxisSensorUnalteredPassthroughEnabled, LoadSixAxisSensorCalibrationParameter, GetSixAxisSensorIcInformation calls (closes #3123 and closes #3124).

* Addresses Thog review
2022-02-18 02:00:06 +01:00
747876dc67 Decoders: Add IOpCode32HasSetFlags (#3136) 2022-02-18 01:33:43 +01:00
95cc18a7b4 Added trace log level (#3096)
* added trace log level

* use trace log level instead of debug ( #1547)

* alignment #1547

* moved trace logs toggle at the bottom #1547

* bumped config file version #3096

* added migration step #3096

* setting moved to the dev section #1547

* performance warning displayed when trace is enabled #1547
2022-02-17 21:08:07 -03:00
c017c77365 Change ServiceNv map creation logs to the Debug level (#3058) 2022-02-18 00:41:21 +01:00
98e05ee4b7 ARMeilleure: Thumb support (All T16 instructions) (#3105)
* Decoders: Add InITBlock argument

* OpCodeTable: Minor cleanup

* OpCodeTable: Remove existing thumb instruction implementations

* OpCodeTable: Prepare for thumb instructions

* OpCodeTables: Improve thumb fast lookup

* Tests: Prepare for thumb tests

* T16: Implement BX

* T16: Implement LSL/LSR/ASR (imm)

* T16: Implement ADDS, SUBS (reg)

* T16: Implement ADDS, SUBS (3-bit immediate)

* T16: Implement MOVS, CMP, ADDS, SUBS (8-bit immediate)

* T16: Implement ANDS, EORS, LSLS, LSRS, ASRS, ADCS, SBCS, RORS, TST, NEGS, CMP, CMN, ORRS, MULS, BICS, MVNS (low registers)

* T16: Implement ADD, CMP, MOV (high reg)

* T16: Implement BLX (reg)

* T16: Implement LDR (literal)

* T16: Implement {LDR,STR}{,H,B,SB,SH} (register)

* T16: Implement {LDR,STR}{,B,H} (immediate)

* T16: Implement LDR/STR (SP)

* T16: Implement ADR

* T16: Implement Add to SP (immediate)

* T16: Implement ADD/SUB (SP)

* T16: Implement SXTH, SXTB, UXTH, UTXB

* T16: Implement CBZ, CBNZ

* T16: Implement PUSH, POP

* T16: Implement REV, REV16, REVSH

* T16: Implement NOP

* T16: Implement LDM, STM

* T16: Implement SVC

* T16: Implement B (conditional)

* T16: Implement B (unconditional)

* T16: Implement IT

* fixup! T16: Implement ADD/SUB (SP)

* fixup! T16: Implement Add to SP (immediate)

* fixup! T16: Implement IT

* CpuTestThumb: Add randomized tests

* Remove inITBlock argument

* Address nits

* Use index to handle IfThenBlockState

* Reduce line noise

* fixup

* nit
2022-02-17 19:39:45 -03:00
868919e101 misc: Update GtkSharp.Dependencies and speed up initial Windows build (#3128)
Update GtkSharp.Dependencies to fix between menu flickering and enable
the SkipInstallGtk in csproj to avoid downloading unused GTK3 install
locally.
2022-02-17 22:10:48 +01:00
9ca040c0ff Use ReadOnlySpan<byte> compiler optimization for static data (#3130) 2022-02-17 21:38:50 +01:00
7e9011673b Use a basic cubic interpolation for the audren upsampler (#3129)
Before, it was selecting nearest neighbour, which sounded terrible. This is likely temporary til the upsampling algorithm used by the switch is reversed.

Fixes bad audio in Skyward Sword HD.
2022-02-17 20:19:29 +01:00
741db8e43d amadeus: Fix PCMFloat datasource command v1 (#3127)
Really simple copy pasta error here.

Shouldn't affect anything as float support was added at the same REV as
datasource command v2.
2022-02-16 23:55:40 +01:00
3bd357045f Do not allow render targets not explicitly written by the fragment shader to be modified (#3063)
* Do not allow render targets not explicitly written by the fragment shader to be modified

* Shader cache version bump

* Remove blank lines

* Avoid redundant color mask updates

* HostShaderCacheEntry can be null

* Avoid more redundant glColorMask calls

* nit: Mask -> Masks

* Fix currentComponentMask

* More efficient way to update _currentComponentMasks
2022-02-16 23:15:39 +01:00
ab5d77c0c4 amadeus: Fix limiter correctness (#3126)
This fixes missing audio on Nintendo Switch Sports Online Play Test.
2022-02-16 21:38:45 +01:00
7bfb5f79b8 When copying linear textures, DMA should ignore region X/Y (#3121) 2022-02-16 11:13:45 +01:00
8cc2479825 Adjusting how deadzones are calculated (#3079)
* Making deadzones feel nice and smooth + adding rider files to .gitignore

* removing unnecessary parentheses and fixing possibility of divide by 0

* formatting :)

* fixing up ClampAxis

* fixing up ClampAxis
2022-02-16 11:06:52 +01:00
8f35345729 Use Enum and Delegate.CreateDelegate generic overloads (#3111)
* Use Enum generic overloads

* Remove EnumExtensions.cs

* Use Delegate.CreateDelegate generic overloads
2022-02-13 10:50:07 -03:00
ce71f9144e InstEmitMemory32: Literal loads always have word-aligned PC (#3104) 2022-02-11 17:51:03 -03:00
f861f0bca2 Fix missing geometry shader passthrough inputs (#3106)
* Fix missing geometry shader passthrough inputs

* Shader cache version bump
2022-02-11 19:52:20 +01:00
571496d243 Ship SoundIO library only for the specified runtime (#3103)
* Add RuntimeIdentifers properties

For Linux, Windows and OS X x86-64
This ensures that the SoundIO project gets this property when built as a subproject

* Address gdkchan's nit

Merge tags into one
2022-02-11 00:15:13 +01:00
402 changed files with 16221 additions and 4243 deletions

View File

@ -36,15 +36,20 @@ jobs:
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) {
if(art.name.includes('Debug')){
if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}else{
} else if(art.name.includes('headless-sdl2')) {
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
}
hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`;
body += hidden_headless_artifacts;
body += hidden_debug_artifacts;
const {data: comments} = await github.issues.listComments({repo, owner, issue_number});

3
.gitignore vendored
View File

@ -74,6 +74,9 @@ _TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Rider is a Visual Studio alternative
.idea/*
# NCrunch
*.ncrunch*
.*crunch*.local.xml

View File

@ -59,7 +59,7 @@ namespace ARMeilleure.CodeGen.Optimizations
BasicBlock fromPred = from.Predecessors.Count == 1 ? from.Predecessors[0] : null;
// If the block is empty, we can try to append to the predecessor and avoid unnecessary jumps.
if (from.Operations.Count == 0 && fromPred != null)
if (from.Operations.Count == 0 && fromPred != null && fromPred.SuccessorsCount == 1)
{
for (int i = 0; i < fromPred.SuccessorsCount; i++)
{

View File

@ -18,7 +18,7 @@ namespace ARMeilleure.Decoders
// For lower code quality translation, we set a lower limit since we're blocking execution.
private const int MaxInstsPerFunctionLowCq = 500;
public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, bool singleBlock)
public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, DecoderMode dMode)
{
List<Block> blocks = new List<Block>();
@ -38,7 +38,7 @@ namespace ARMeilleure.Decoders
{
block = new Block(blkAddress);
if ((singleBlock && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
{
block.Exit = true;
block.EndAddress = blkAddress;
@ -96,6 +96,12 @@ namespace ARMeilleure.Decoders
}
}
if (dMode == DecoderMode.SingleInstruction)
{
// Only read at most one instruction
limitAddress = currBlock.Address + 1;
}
FillBlock(memory, mode, currBlock, limitAddress);
opsCount += currBlock.OpCodes.Count;
@ -115,7 +121,7 @@ namespace ARMeilleure.Decoders
currBlock.Branch = GetBlock((ulong)op.Immediate);
}
if (!IsUnconditionalBranch(lastOp) || isCall)
if (isCall || !(IsUnconditionalBranch(lastOp) || IsTrap(lastOp)))
{
currBlock.Next = GetBlock(currBlock.EndAddress);
}
@ -143,7 +149,7 @@ namespace ARMeilleure.Decoders
throw new InvalidOperationException($"Decoded a single empty exit block. Entry point = 0x{address:X}.");
}
if (!singleBlock)
if (dMode == DecoderMode.MultipleBlocks)
{
return TailCallRemover.RunPass(address, blocks);
}
@ -195,12 +201,13 @@ namespace ARMeilleure.Decoders
ulong limitAddress)
{
ulong address = block.Address;
int itBlockSize = 0;
OpCode opCode;
do
{
if (address >= limitAddress)
if (address >= limitAddress && itBlockSize == 0)
{
break;
}
@ -210,6 +217,15 @@ namespace ARMeilleure.Decoders
block.OpCodes.Add(opCode);
address += (ulong)opCode.OpCodeSizeInBytes;
if (opCode is OpCodeT16IfThen it)
{
itBlockSize = it.IfThenBlockSize;
}
else if (itBlockSize > 0)
{
itBlockSize--;
}
}
while (!(IsBranch(opCode) || IsException(opCode)));
@ -247,6 +263,11 @@ namespace ARMeilleure.Decoders
// so we must consider such operations as a branch in potential aswell.
if (opCode is IOpCode32Alu opAlu && opAlu.Rd == RegisterAlias.Aarch32Pc)
{
if (opCode is OpCodeT32)
{
return opCode.Instruction.Name != InstName.Tst && opCode.Instruction.Name != InstName.Teq &&
opCode.Instruction.Name != InstName.Cmp && opCode.Instruction.Name != InstName.Cmn;
}
return true;
}
@ -308,9 +329,13 @@ namespace ARMeilleure.Decoders
}
private static bool IsException(OpCode opCode)
{
return IsTrap(opCode) || opCode.Instruction.Name == InstName.Svc;
}
private static bool IsTrap(OpCode opCode)
{
return opCode.Instruction.Name == InstName.Brk ||
opCode.Instruction.Name == InstName.Svc ||
opCode.Instruction.Name == InstName.Trap ||
opCode.Instruction.Name == InstName.Und;
}
@ -345,7 +370,14 @@ namespace ARMeilleure.Decoders
}
else
{
return new OpCode(inst, address, opCode);
if (mode == ExecutionMode.Aarch32Thumb)
{
return new OpCodeT16(inst, address, opCode);
}
else
{
return new OpCode(inst, address, opCode);
}
}
}
}

View File

@ -0,0 +1,9 @@
namespace ARMeilleure.Decoders
{
enum DecoderMode
{
MultipleBlocks,
SingleBlock,
SingleInstruction,
}
}

View File

@ -0,0 +1,9 @@
namespace ARMeilleure.Decoders
{
interface IOpCode32Adr
{
int Rd { get; }
int Immediate { get; }
}
}

View File

@ -1,10 +1,8 @@
namespace ARMeilleure.Decoders
{
interface IOpCode32Alu : IOpCode32
interface IOpCode32Alu : IOpCode32, IOpCode32HasSetFlags
{
int Rd { get; }
int Rn { get; }
bool SetFlags { get; }
}
}

View File

@ -0,0 +1,9 @@
namespace ARMeilleure.Decoders
{
interface IOpCode32AluImm : IOpCode32Alu
{
int Immediate { get; }
bool IsRotated { get; }
}
}

View File

@ -0,0 +1,10 @@
namespace ARMeilleure.Decoders
{
interface IOpCode32AluRsImm : IOpCode32Alu
{
int Rm { get; }
int Immediate { get; }
ShiftType ShiftType { get; }
}
}

View File

@ -0,0 +1,10 @@
namespace ARMeilleure.Decoders
{
interface IOpCode32AluRsReg : IOpCode32Alu
{
int Rm { get; }
int Rs { get; }
ShiftType ShiftType { get; }
}
}

View File

@ -0,0 +1,6 @@
namespace ARMeilleure.Decoders;
interface IOpCode32Exception
{
int Id { get; }
}

View File

@ -0,0 +1,7 @@
namespace ARMeilleure.Decoders
{
interface IOpCode32HasSetFlags
{
bool? SetFlags { get; }
}
}

View File

@ -7,5 +7,9 @@ namespace ARMeilleure.Decoders
bool WBack { get; }
bool IsLoad { get; }
bool Index { get; }
bool Add { get; }
int Immediate { get; }
}
}

View File

@ -9,5 +9,7 @@ namespace ARMeilleure.Decoders
int PostOffset { get; }
bool IsLoad { get; }
int Offset { get; }
}
}

View File

@ -0,0 +1,7 @@
namespace ARMeilleure.Decoders
{
interface IOpCode32MemReg : IOpCode32Mem
{
int Rm { get; }
}
}

View File

@ -18,10 +18,9 @@ namespace ARMeilleure.Decoders
public OpCode(InstDescriptor inst, ulong address, int opCode)
{
Address = address;
RawOpCode = opCode;
Instruction = inst;
Address = address;
RawOpCode = opCode;
RegisterSize = RegisterSize.Int64;
}

View File

@ -13,11 +13,25 @@ namespace ARMeilleure.Decoders
Cond = (Condition)((uint)opCode >> 28);
}
public bool IsThumb()
{
return this is OpCodeT16 || this is OpCodeT32;
}
public uint GetPc()
{
// Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline,
// the PC actually points 2 instructions ahead.
return (uint)Address + (uint)OpCodeSizeInBytes * 2;
if (IsThumb())
{
// PC is ahead by 4 in thumb mode whether or not the current instruction
// is 16 or 32 bit.
return (uint)Address + 4u;
}
else
{
return (uint)Address + 8u;
}
}
}
}

View File

@ -5,7 +5,7 @@ namespace ARMeilleure.Decoders
public int Rd { get; }
public int Rn { get; }
public bool SetFlags { get; }
public bool? SetFlags { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Alu(inst, address, opCode);

View File

@ -2,7 +2,7 @@ using ARMeilleure.Common;
namespace ARMeilleure.Decoders
{
class OpCode32AluImm : OpCode32Alu
class OpCode32AluImm : OpCode32Alu, IOpCode32AluImm
{
public int Immediate { get; }

View File

@ -10,7 +10,7 @@
public bool NHigh { get; }
public bool MHigh { get; }
public bool R { get; }
public bool SetFlags { get; }
public bool? SetFlags { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluMla(inst, address, opCode);

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders
{
class OpCode32AluRsImm : OpCode32Alu
class OpCode32AluRsImm : OpCode32Alu, IOpCode32AluRsImm
{
public int Rm { get; }
public int Immediate { get; }

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders
{
class OpCode32AluRsReg : OpCode32Alu
class OpCode32AluRsReg : OpCode32Alu, IOpCode32AluRsReg
{
public int Rm { get; }
public int Rs { get; }

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders
{
class OpCode32AluUmull : OpCode32
class OpCode32AluUmull : OpCode32, IOpCode32HasSetFlags
{
public int RdLo { get; }
public int RdHi { get; }
@ -10,7 +10,7 @@
public bool NHigh { get; }
public bool MHigh { get; }
public bool SetFlags { get; }
public bool? SetFlags { get; }
public DataOp DataOp { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluUmull(inst, address, opCode);

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders
{
class OpCode32Exception : OpCode32
class OpCode32Exception : OpCode32, IOpCode32Exception
{
public int Id { get; }

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders
{
class OpCode32MemReg : OpCode32Mem
class OpCode32MemReg : OpCode32Mem, IOpCode32MemReg
{
public int Rm { get; }

View File

@ -0,0 +1,24 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16AddSubImm3: OpCodeT16, IOpCode32AluImm
{
public int Rd { get; }
public int Rn { get; }
public bool? SetFlags => null;
public int Immediate { get; }
public bool IsRotated { get; }
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubImm3(inst, address, opCode);
public OpCodeT16AddSubImm3(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 0) & 0x7;
Rn = (opCode >> 3) & 0x7;
Immediate = (opCode >> 6) & 0x7;
IsRotated = false;
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16AddSubReg : OpCodeT16, IOpCode32AluReg
{
public int Rm { get; }
public int Rd { get; }
public int Rn { get; }
public bool? SetFlags => null;
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubReg(inst, address, opCode);
public OpCodeT16AddSubReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 0) & 0x7;
Rn = (opCode >> 3) & 0x7;
Rm = (opCode >> 6) & 0x7;
}
}
}

View File

@ -0,0 +1,23 @@
using ARMeilleure.State;
namespace ARMeilleure.Decoders
{
class OpCodeT16AddSubSp : OpCodeT16, IOpCode32AluImm
{
public int Rd => RegisterAlias.Aarch32Sp;
public int Rn => RegisterAlias.Aarch32Sp;
public bool? SetFlags => false;
public int Immediate { get; }
public bool IsRotated => false;
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubSp(inst, address, opCode);
public OpCodeT16AddSubSp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Immediate = ((opCode >> 0) & 0x7f) << 2;
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16Adr : OpCodeT16, IOpCode32Adr
{
public int Rd { get; }
public bool Add => true;
public int Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16Adr(inst, address, opCode);
public OpCodeT16Adr(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 8) & 7;
int imm = (opCode & 0xff) << 2;
Immediate = (int)(GetPc() & 0xfffffffc) + imm;
}
}
}

View File

@ -1,22 +1,24 @@
namespace ARMeilleure.Decoders
namespace ARMeilleure.Decoders
{
class OpCodeT16AluImm8 : OpCodeT16, IOpCode32Alu
class OpCodeT16AluImm8 : OpCodeT16, IOpCode32AluImm
{
private int _rdn;
public int Rd { get; }
public int Rn { get; }
public int Rd => _rdn;
public int Rn => _rdn;
public bool SetFlags => false;
public bool? SetFlags => null;
public int Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluImm8(inst, address, opCode);
public bool IsRotated { get; }
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluImm8(inst, address, opCode);
public OpCodeT16AluImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 8) & 0x7;
Rn = (opCode >> 8) & 0x7;
Immediate = (opCode >> 0) & 0xff;
_rdn = (opCode >> 8) & 0x7;
IsRotated = false;
}
}
}
}

View File

@ -0,0 +1,24 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16AluImmZero : OpCodeT16, IOpCode32AluImm
{
public int Rd { get; }
public int Rn { get; }
public bool? SetFlags => null;
public int Immediate { get; }
public bool IsRotated { get; }
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluImmZero(inst, address, opCode);
public OpCodeT16AluImmZero(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 0) & 0x7;
Rn = (opCode >> 3) & 0x7;
Immediate = 0;
IsRotated = false;
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16AluRegHigh : OpCodeT16, IOpCode32AluReg
{
public int Rm { get; }
public int Rd { get; }
public int Rn { get; }
public bool? SetFlags => false;
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluRegHigh(inst, address, opCode);
public OpCodeT16AluRegHigh(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = ((opCode >> 0) & 0x7) | ((opCode >> 4) & 0x8);
Rn = ((opCode >> 0) & 0x7) | ((opCode >> 4) & 0x8);
Rm = (opCode >> 3) & 0xf;
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16AluRegLow : OpCodeT16, IOpCode32AluReg
{
public int Rm { get; }
public int Rd { get; }
public int Rn { get; }
public bool? SetFlags => null;
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluRegLow(inst, address, opCode);
public OpCodeT16AluRegLow(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 0) & 0x7;
Rn = (opCode >> 0) & 0x7;
Rm = (opCode >> 3) & 0x7;
}
}
}

View File

@ -0,0 +1,22 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16AluUx : OpCodeT16, IOpCode32AluUx
{
public int Rm { get; }
public int Rd { get; }
public int Rn { get; }
public bool? SetFlags => false;
public int RotateBits => 0;
public bool Add => false;
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluUx(inst, address, opCode);
public OpCodeT16AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 0) & 0x7;
Rm = (opCode >> 3) & 0x7;
}
}
}

View File

@ -0,0 +1,15 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16BImm11 : OpCodeT16, IOpCode32BImm
{
public long Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImm11(inst, address, opCode);
public OpCodeT16BImm11(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
int imm = (opCode << 21) >> 20;
Immediate = GetPc() + imm;
}
}
}

View File

@ -0,0 +1,17 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16BImm8 : OpCodeT16, IOpCode32BImm
{
public long Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImm8(inst, address, opCode);
public OpCodeT16BImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Cond = (Condition)((opCode >> 8) & 0xf);
int imm = (opCode << 24) >> 23;
Immediate = GetPc() + imm;
}
}
}

View File

@ -0,0 +1,19 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16BImmCmp : OpCodeT16
{
public int Rn { get; }
public int Immediate { get; }
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImmCmp(inst, address, opCode);
public OpCodeT16BImmCmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rn = (opCode >> 0) & 0x7;
int imm = ((opCode >> 2) & 0x3e) | ((opCode >> 3) & 0x40);
Immediate = (int)GetPc() + imm;
}
}
}

View File

@ -1,4 +1,4 @@
namespace ARMeilleure.Decoders
namespace ARMeilleure.Decoders
{
class OpCodeT16BReg : OpCodeT16, IOpCode32BReg
{
@ -11,4 +11,4 @@ namespace ARMeilleure.Decoders
Rm = (opCode >> 3) & 0xf;
}
}
}
}

View File

@ -0,0 +1,14 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16Exception : OpCodeT16, IOpCode32Exception
{
public int Id { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16Exception(inst, address, opCode);
public OpCodeT16Exception(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Id = opCode & 0xFF;
}
}
}

View File

@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Reflection.Emit;
namespace ARMeilleure.Decoders
{
class OpCodeT16IfThen : OpCodeT16
{
public Condition[] IfThenBlockConds { get; }
public int IfThenBlockSize { get { return IfThenBlockConds.Length; } }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16IfThen(inst, address, opCode);
public OpCodeT16IfThen(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
List<Condition> conds = new();
int cond = (opCode >> 4) & 0xf;
int mask = opCode & 0xf;
conds.Add((Condition)cond);
while ((mask & 7) != 0)
{
int newLsb = (mask >> 3) & 1;
cond = (cond & 0xe) | newLsb;
mask <<= 1;
conds.Add((Condition)cond);
}
IfThenBlockConds = conds.ToArray();
}
}
}

View File

@ -0,0 +1,58 @@
using ARMeilleure.Instructions;
using System;
namespace ARMeilleure.Decoders
{
class OpCodeT16MemImm5 : OpCodeT16, IOpCode32Mem
{
public int Rt { get; }
public int Rn { get; }
public bool WBack => false;
public bool IsLoad { get; }
public bool Index => true;
public bool Add => true;
public int Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemImm5(inst, address, opCode);
public OpCodeT16MemImm5(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rt = (opCode >> 0) & 7;
Rn = (opCode >> 3) & 7;
switch (inst.Name)
{
case InstName.Ldr:
case InstName.Ldrb:
case InstName.Ldrh:
IsLoad = true;
break;
case InstName.Str:
case InstName.Strb:
case InstName.Strh:
IsLoad = false;
break;
}
switch (inst.Name)
{
case InstName.Str:
case InstName.Ldr:
Immediate = ((opCode >> 6) & 0x1f) << 2;
break;
case InstName.Strb:
case InstName.Ldrb:
Immediate = ((opCode >> 6) & 0x1f);
break;
case InstName.Strh:
case InstName.Ldrh:
Immediate = ((opCode >> 6) & 0x1f) << 1;
break;
default:
throw new InvalidOperationException();
}
}
}
}

View File

@ -0,0 +1,26 @@
using ARMeilleure.State;
namespace ARMeilleure.Decoders
{
class OpCodeT16MemLit : OpCodeT16, IOpCode32Mem
{
public int Rt { get; }
public int Rn => RegisterAlias.Aarch32Pc;
public bool WBack => false;
public bool IsLoad => true;
public bool Index => true;
public bool Add => true;
public int Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemLit(inst, address, opCode);
public OpCodeT16MemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rt = (opCode >> 8) & 7;
Immediate = (opCode & 0xff) << 2;
}
}
}

View File

@ -0,0 +1,34 @@
using ARMeilleure.Instructions;
using System;
using System.Numerics;
namespace ARMeilleure.Decoders
{
class OpCodeT16MemMult : OpCodeT16, IOpCode32MemMult
{
public int Rn { get; }
public int RegisterMask { get; }
public int PostOffset { get; }
public bool IsLoad { get; }
public int Offset { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemMult(inst, address, opCode);
public OpCodeT16MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
RegisterMask = opCode & 0xff;
Rn = (opCode >> 8) & 7;
int regCount = BitOperations.PopCount((uint)RegisterMask);
Offset = 0;
PostOffset = 4 * regCount;
IsLoad = inst.Name switch
{
InstName.Ldm => true,
InstName.Stm => false,
_ => throw new InvalidOperationException()
};
}
}
}

View File

@ -0,0 +1,27 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16MemReg : OpCodeT16, IOpCode32MemReg
{
public int Rm { get; }
public int Rt { get; }
public int Rn { get; }
public bool WBack => false;
public bool IsLoad { get; }
public bool Index => true;
public bool Add => true;
public int Immediate => throw new System.InvalidOperationException();
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemReg(inst, address, opCode);
public OpCodeT16MemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rt = (opCode >> 0) & 7;
Rn = (opCode >> 3) & 7;
Rm = (opCode >> 6) & 7;
IsLoad = ((opCode >> 9) & 7) >= 3;
}
}
}

View File

@ -0,0 +1,28 @@
using ARMeilleure.State;
namespace ARMeilleure.Decoders
{
class OpCodeT16MemSp : OpCodeT16, IOpCode32Mem
{
public int Rt { get; }
public int Rn => RegisterAlias.Aarch32Sp;
public bool WBack => false;
public bool IsLoad { get; }
public bool Index => true;
public bool Add => true;
public int Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemSp(inst, address, opCode);
public OpCodeT16MemSp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rt = (opCode >> 8) & 7;
IsLoad = ((opCode >> 11) & 1) != 0;
Immediate = ((opCode >> 0) & 0xff) << 2;
}
}
}

View File

@ -0,0 +1,42 @@
using ARMeilleure.Instructions;
using ARMeilleure.State;
using System;
using System.Numerics;
namespace ARMeilleure.Decoders
{
class OpCodeT16MemStack : OpCodeT16, IOpCode32MemMult
{
public int Rn => RegisterAlias.Aarch32Sp;
public int RegisterMask { get; }
public int PostOffset { get; }
public bool IsLoad { get; }
public int Offset { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemStack(inst, address, opCode);
public OpCodeT16MemStack(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
int extra = (opCode >> 8) & 1;
int regCount = BitOperations.PopCount((uint)opCode & 0x1ff);
switch (inst.Name)
{
case InstName.Push:
RegisterMask = (opCode & 0xff) | (extra << 14);
IsLoad = false;
Offset = -4 * regCount;
PostOffset = -4 * regCount;
break;
case InstName.Pop:
RegisterMask = (opCode & 0xff) | (extra << 15);
IsLoad = true;
Offset = 0;
PostOffset = 4 * regCount;
break;
default:
throw new InvalidOperationException();
}
}
}
}

View File

@ -0,0 +1,24 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16ShiftImm : OpCodeT16, IOpCode32AluRsImm
{
public int Rd { get; }
public int Rn { get; }
public int Rm { get; }
public int Immediate { get; }
public ShiftType ShiftType { get; }
public bool? SetFlags => null;
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16ShiftImm(inst, address, opCode);
public OpCodeT16ShiftImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 0) & 0x7;
Rm = (opCode >> 3) & 0x7;
Immediate = (opCode >> 6) & 0x1F;
ShiftType = (ShiftType)((opCode >> 11) & 3);
}
}
}

View File

@ -0,0 +1,27 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16ShiftReg : OpCodeT16, IOpCode32AluRsReg
{
public int Rm { get; }
public int Rs { get; }
public int Rd { get; }
public int Rn { get; }
public ShiftType ShiftType { get; }
public bool? SetFlags => null;
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16ShiftReg(inst, address, opCode);
public OpCodeT16ShiftReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 0) & 7;
Rm = (opCode >> 0) & 7;
Rn = (opCode >> 3) & 7;
Rs = (opCode >> 3) & 7;
ShiftType = (ShiftType)(((opCode >> 6) & 1) | ((opCode >> 7) & 2));
}
}
}

View File

@ -0,0 +1,24 @@
using ARMeilleure.State;
namespace ARMeilleure.Decoders
{
class OpCodeT16SpRel : OpCodeT16, IOpCode32AluImm
{
public int Rd { get; }
public int Rn => RegisterAlias.Aarch32Sp;
public bool? SetFlags => false;
public int Immediate { get; }
public bool IsRotated => false;
public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16SpRel(inst, address, opCode);
public OpCodeT16SpRel(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 8) & 0x7;
Immediate = ((opCode >> 0) & 0xff) << 2;
}
}
}

View File

@ -0,0 +1,14 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32 : OpCode32
{
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32(inst, address, opCode);
public OpCodeT32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Cond = Condition.Al;
OpCodeSizeInBytes = 4;
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32Alu : OpCodeT32, IOpCode32Alu
{
public int Rd { get; }
public int Rn { get; }
public bool? SetFlags { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Alu(inst, address, opCode);
public OpCodeT32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 8) & 0xf;
Rn = (opCode >> 16) & 0xf;
SetFlags = ((opCode >> 20) & 1) != 0;
}
}
}

View File

@ -0,0 +1,38 @@
using ARMeilleure.Common;
using System.Runtime.Intrinsics;
namespace ARMeilleure.Decoders
{
class OpCodeT32AluImm : OpCodeT32Alu, IOpCode32AluImm
{
public int Immediate { get; }
public bool IsRotated { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm(inst, address, opCode);
private static readonly Vector128<int> _factor = Vector128.Create(1, 0x00010001, 0x01000100, 0x01010101);
public OpCodeT32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
int imm8 = (opCode >> 0) & 0xff;
int imm3 = (opCode >> 12) & 7;
int imm1 = (opCode >> 26) & 1;
int imm12 = imm8 | (imm3 << 8) | (imm1 << 11);
if ((imm12 >> 10) == 0)
{
Immediate = imm8 * _factor.GetElement((imm12 >> 8) & 3);
IsRotated = false;
}
else
{
int shift = imm12 >> 7;
Immediate = BitUtils.RotateRight(0x80 | (imm12 & 0x7f), shift, 32);
IsRotated = shift != 0;
}
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32AluRsImm : OpCodeT32Alu, IOpCode32AluRsImm
{
public int Rm { get; }
public int Immediate { get; }
public ShiftType ShiftType { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluRsImm(inst, address, opCode);
public OpCodeT32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rm = (opCode >> 0) & 0xf;
Immediate = ((opCode >> 6) & 3) | ((opCode >> 10) & 0x1c);
ShiftType = (ShiftType)((opCode >> 4) & 3);
}
}
}

View File

@ -0,0 +1,29 @@
using ARMeilleure.Instructions;
namespace ARMeilleure.Decoders
{
class OpCodeT32BImm20 : OpCodeT32, IOpCode32BImm
{
public long Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm20(inst, address, opCode);
public OpCodeT32BImm20(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
uint pc = GetPc();
int imm11 = (opCode >> 0) & 0x7ff;
int j2 = (opCode >> 11) & 1;
int j1 = (opCode >> 13) & 1;
int imm6 = (opCode >> 16) & 0x3f;
int s = (opCode >> 26) & 1;
int imm32 = imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19);
imm32 = (imm32 << 13) >> 12;
Immediate = pc + imm32;
Cond = (Condition)((opCode >> 22) & 0xf);
}
}
}

View File

@ -0,0 +1,35 @@
using ARMeilleure.Instructions;
namespace ARMeilleure.Decoders
{
class OpCodeT32BImm24 : OpCodeT32, IOpCode32BImm
{
public long Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm24(inst, address, opCode);
public OpCodeT32BImm24(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
uint pc = GetPc();
if (inst.Name == InstName.Blx)
{
pc &= ~3u;
}
int imm11 = (opCode >> 0) & 0x7ff;
int j2 = (opCode >> 11) & 1;
int j1 = (opCode >> 13) & 1;
int imm10 = (opCode >> 16) & 0x3ff;
int s = (opCode >> 26) & 1;
int i1 = j1 ^ s ^ 1;
int i2 = j2 ^ s ^ 1;
int imm32 = imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23);
imm32 = (imm32 << 9) >> 8;
Immediate = pc + imm32;
}
}
}

View File

@ -0,0 +1,25 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32MemImm12 : OpCodeT32, IOpCode32Mem
{
public int Rt { get; }
public int Rn { get; }
public bool WBack => false;
public bool IsLoad { get; }
public bool Index => true;
public bool Add => true;
public int Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm12(inst, address, opCode);
public OpCodeT32MemImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rt = (opCode >> 12) & 0xf;
Rn = (opCode >> 16) & 0xf;
Immediate = opCode & 0xfff;
IsLoad = ((opCode >> 20) & 1) != 0;
}
}
}

View File

@ -0,0 +1,29 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32MemImm8 : OpCodeT32, IOpCode32Mem
{
public int Rt { get; }
public int Rn { get; }
public bool WBack { get; }
public bool IsLoad { get; }
public bool Index { get; }
public bool Add { get; }
public int Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8(inst, address, opCode);
public OpCodeT32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rt = (opCode >> 12) & 0xf;
Rn = (opCode >> 16) & 0xf;
Index = ((opCode >> 10) & 1) != 0;
Add = ((opCode >> 9) & 1) != 0;
WBack = ((opCode >> 8) & 1) != 0;
Immediate = opCode & 0xff;
IsLoad = ((opCode >> 20) & 1) != 0;
}
}
}

View File

@ -1,7 +1,7 @@
using ARMeilleure.Instructions;
using ARMeilleure.State;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace ARMeilleure.Decoders
{
@ -29,9 +29,9 @@ namespace ARMeilleure.Decoders
}
}
private static List<InstInfo> AllInstA32 = new List<InstInfo>();
private static List<InstInfo> AllInstT32 = new List<InstInfo>();
private static List<InstInfo> AllInstA64 = new List<InstInfo>();
private static List<InstInfo> AllInstA32 = new();
private static List<InstInfo> AllInstT32 = new();
private static List<InstInfo> AllInstA64 = new();
private static InstInfo[][] InstA32FastLookup = new InstInfo[FastLookupSize][];
private static InstInfo[][] InstT32FastLookup = new InstInfo[FastLookupSize][];
@ -628,7 +628,7 @@ namespace ARMeilleure.Decoders
SetA64("0>001110<<0xxxxx011110xxxxxxxxxx", InstName.Zip2_V, InstEmit.Zip2_V, OpCodeSimdReg.Create);
#endregion
#region "OpCode Table (AArch32)"
#region "OpCode Table (AArch32, A32)"
// Base
SetA32("<<<<0010101xxxxxxxxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluImm.Create);
SetA32("<<<<0000101xxxxxxxxxxxxxxxx0xxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluRsImm.Create);
@ -649,7 +649,6 @@ namespace ARMeilleure.Decoders
SetA32("1111101xxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Blx, InstEmit32.Blx, OpCode32BImm.Create);
SetA32("<<<<000100101111111111110011xxxx", InstName.Blx, InstEmit32.Blxr, OpCode32BReg.Create);
SetA32("<<<<000100101111111111110001xxxx", InstName.Bx, InstEmit32.Bx, OpCode32BReg.Create);
SetT32("xxxxxxxxxxxxxxxx010001110xxxx000", InstName.Bx, InstEmit32.Bx, OpCodeT16BReg.Create);
SetA32("11110101011111111111000000011111", InstName.Clrex, InstEmit32.Clrex, OpCode32.Create);
SetA32("<<<<000101101111xxxx11110001xxxx", InstName.Clz, InstEmit32.Clz, OpCode32AluReg.Create);
SetA32("<<<<00110111xxxx0000xxxxxxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluImm.Create);
@ -702,7 +701,6 @@ namespace ARMeilleure.Decoders
SetA32("<<<<0001101x0000xxxxxxxxxxx0xxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluRsImm.Create);
SetA32("<<<<0001101x0000xxxxxxxx0xx1xxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluRsReg.Create);
SetA32("<<<<00110000xxxxxxxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluImm16.Create);
SetT32("xxxxxxxxxxxxxxxx00100xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16AluImm8.Create);
SetA32("<<<<00110100xxxxxxxxxxxxxxxxxxxx", InstName.Movt, InstEmit32.Movt, OpCode32AluImm16.Create);
SetA32("<<<<1110xxx1xxxxxxxx111xxxx1xxxx", InstName.Mrc, InstEmit32.Mrc, OpCode32System.Create);
SetA32("<<<<11000101xxxxxxxx111xxxxxxxxx", InstName.Mrrc, InstEmit32.Mrrc, OpCode32System.Create);
@ -975,12 +973,150 @@ namespace ARMeilleure.Decoders
SetA32("111100111x11<<10xxxx00011xx0xxxx", InstName.Vzip, InstEmit32.Vzip, OpCode32SimdCmpZ.Create);
#endregion
FillFastLookupTable(InstA32FastLookup, AllInstA32);
FillFastLookupTable(InstT32FastLookup, AllInstT32);
FillFastLookupTable(InstA64FastLookup, AllInstA64);
#region "OpCode Table (AArch32, T16)"
SetT16("000<<xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftImm.Create);
SetT16("0001100xxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AddSubReg.Create);
SetT16("0001101xxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT16AddSubReg.Create);
SetT16("0001110xxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AddSubImm3.Create);
SetT16("0001111xxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT16AddSubImm3.Create);
SetT16("00100xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16AluImm8.Create);
SetT16("00101xxxxxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT16AluImm8.Create);
SetT16("00110xxxxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AluImm8.Create);
SetT16("00111xxxxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT16AluImm8.Create);
SetT16("0100000000xxxxxx", InstName.And, InstEmit32.And, OpCodeT16AluRegLow.Create);
SetT16("0100000001xxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT16AluRegLow.Create);
SetT16("0100000010xxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftReg.Create);
SetT16("0100000011xxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftReg.Create);
SetT16("0100000100xxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftReg.Create);
SetT16("0100000101xxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT16AluRegLow.Create);
SetT16("0100000110xxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT16AluRegLow.Create);
SetT16("0100000111xxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftReg.Create);
SetT16("0100001000xxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT16AluRegLow.Create);
SetT16("0100001001xxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT16AluImmZero.Create);
SetT16("0100001010xxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT16AluRegLow.Create);
SetT16("0100001011xxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT16AluRegLow.Create);
SetT16("0100001100xxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT16AluRegLow.Create);
SetT16("0100001101xxxxxx", InstName.Mul, InstEmit32.Mul, OpCodeT16AluRegLow.Create);
SetT16("0100001110xxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT16AluRegLow.Create);
SetT16("0100001111xxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT16AluRegLow.Create);
SetT16("01000100xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AluRegHigh.Create);
SetT16("01000101xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT16AluRegHigh.Create);
SetT16("01000110xxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16AluRegHigh.Create);
SetT16("010001110xxxx000", InstName.Bx, InstEmit32.Bx, OpCodeT16BReg.Create);
SetT16("010001111xxxx000", InstName.Blx, InstEmit32.Blx, OpCodeT16BReg.Create);
SetT16("01001xxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT16MemLit.Create);
SetT16("0101000xxxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT16MemReg.Create);
SetT16("0101001xxxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT16MemReg.Create);
SetT16("0101010xxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT16MemReg.Create);
SetT16("0101011xxxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT16MemReg.Create);
SetT16("0101100xxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT16MemReg.Create);
SetT16("0101101xxxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT16MemReg.Create);
SetT16("0101110xxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT16MemReg.Create);
SetT16("0101111xxxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT16MemReg.Create);
SetT16("01100xxxxxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT16MemImm5.Create);
SetT16("01101xxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT16MemImm5.Create);
SetT16("01110xxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT16MemImm5.Create);
SetT16("01111xxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT16MemImm5.Create);
SetT16("10000xxxxxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT16MemImm5.Create);
SetT16("10001xxxxxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT16MemImm5.Create);
SetT16("10010xxxxxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT16MemSp.Create);
SetT16("10011xxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT16MemSp.Create);
SetT16("10100xxxxxxxxxxx", InstName.Adr, InstEmit32.Adr, OpCodeT16Adr.Create);
SetT16("10101xxxxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16SpRel.Create);
SetT16("101100000xxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AddSubSp.Create);
SetT16("101100001xxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT16AddSubSp.Create);
SetT16("1011001000xxxxxx", InstName.Sxth, InstEmit32.Sxth, OpCodeT16AluUx.Create);
SetT16("1011001001xxxxxx", InstName.Sxtb, InstEmit32.Sxtb, OpCodeT16AluUx.Create);
SetT16("1011001010xxxxxx", InstName.Uxth, InstEmit32.Uxth, OpCodeT16AluUx.Create);
SetT16("1011001011xxxxxx", InstName.Uxtb, InstEmit32.Uxtb, OpCodeT16AluUx.Create);
SetT16("101100x1xxxxxxxx", InstName.Cbz, InstEmit32.Cbz, OpCodeT16BImmCmp.Create);
SetT16("1011010xxxxxxxxx", InstName.Push, InstEmit32.Stm, OpCodeT16MemStack.Create);
SetT16("1011101000xxxxxx", InstName.Rev, InstEmit32.Rev, OpCodeT16AluRegLow.Create);
SetT16("1011101001xxxxxx", InstName.Rev16, InstEmit32.Rev16, OpCodeT16AluRegLow.Create);
SetT16("1011101011xxxxxx", InstName.Revsh, InstEmit32.Revsh, OpCodeT16AluRegLow.Create);
SetT16("101110x1xxxxxxxx", InstName.Cbnz, InstEmit32.Cbnz, OpCodeT16BImmCmp.Create);
SetT16("1011110xxxxxxxxx", InstName.Pop, InstEmit32.Ldm, OpCodeT16MemStack.Create);
SetT16("10111111xxxx0000", InstName.Nop, InstEmit32.Nop, OpCodeT16.Create);
SetT16("10111111xxxx>>>>", InstName.It, InstEmit32.It, OpCodeT16IfThen.Create);
SetT16("11000xxxxxxxxxxx", InstName.Stm, InstEmit32.Stm, OpCodeT16MemMult.Create);
SetT16("11001xxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, OpCodeT16MemMult.Create);
SetT16("1101<<<xxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT16BImm8.Create);
SetT16("11011111xxxxxxxx", InstName.Svc, InstEmit32.Svc, OpCodeT16Exception.Create);
SetT16("11100xxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT16BImm11.Create);
#endregion
#region "OpCode Table (AArch32, T32)"
// Base
SetT32("11101011010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluRsImm.Create);
SetT32("11110x01010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluImm.Create);
SetT32("11101011000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluRsImm.Create);
SetT32("11110x01000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluImm.Create);
SetT32("11101010000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluRsImm.Create);
SetT32("11110x00000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluImm.Create);
SetT32("11110x<<<xxxxxxx10x0xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm20.Create);
SetT32("11110xxxxxxxxxxx10x1xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm24.Create);
SetT32("11101010001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluRsImm.Create);
SetT32("11110x00001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluImm.Create);
SetT32("11110xxxxxxxxxxx11x1xxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, OpCodeT32BImm24.Create);
SetT32("11110xxxxxxxxxxx11x0xxxxxxxxxxx0", InstName.Blx, InstEmit32.Blx, OpCodeT32BImm24.Create);
SetT32("111010110001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluRsImm.Create);
SetT32("11110x010001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluImm.Create);
SetT32("111010111011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluRsImm.Create);
SetT32("11110x011011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluImm.Create);
SetT32("11101010100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluRsImm.Create);
SetT32("11110x00100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluImm.Create);
SetT32("111110000101xxxx<<<<10x1xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
SetT32("111110000101xxxx<<<<1100xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
SetT32("111110000101xxxx<<<<11x1xxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm8.Create);
SetT32("111110001101xxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCodeT32MemImm12.Create);
SetT32("111110000001xxxx<<<<10x1xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
SetT32("111110000001xxxx<<<<1100xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
SetT32("111110000001xxxx<<<<11x1xxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm8.Create);
SetT32("111110001001xxxxxxxxxxxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCodeT32MemImm12.Create);
SetT32("111110000011xxxx<<<<10x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
SetT32("111110000011xxxx<<<<1100xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
SetT32("111110000011xxxx<<<<11x1xxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm8.Create);
SetT32("111110001011xxxxxxxxxxxxxxxxxxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCodeT32MemImm12.Create);
SetT32("111110010001xxxx<<<<10x1xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
SetT32("111110010001xxxx<<<<1100xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
SetT32("111110010001xxxx<<<<11x1xxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm8.Create);
SetT32("111110011001xxxxxxxxxxxxxxxxxxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCodeT32MemImm12.Create);
SetT32("111110010011xxxx<<<<10x1xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
SetT32("111110010011xxxx<<<<1100xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
SetT32("111110010011xxxx<<<<11x1xxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm8.Create);
SetT32("111110011011xxxxxxxxxxxxxxxxxxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCodeT32MemImm12.Create);
SetT32("11101010010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluRsImm.Create);
SetT32("11110x00010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluImm.Create);
SetT32("11101010011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluRsImm.Create);
SetT32("11110x00011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluImm.Create);
SetT32("11101010011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluRsImm.Create);
SetT32("11110x00011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluImm.Create);
SetT32("11101010010x<<<<0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT32AluRsImm.Create);
SetT32("11110x00010x<<<<0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT32AluImm.Create);
SetT32("11101011110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluRsImm.Create);
SetT32("11110x01110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluImm.Create);
SetT32("11101011011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluRsImm.Create);
SetT32("11110x01011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluImm.Create);
SetT32("111110000100xxxxxxxx1<<>xxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT32MemImm8.Create);
SetT32("111110001100xxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit32.Str, OpCodeT32MemImm12.Create);
SetT32("111110000000xxxxxxxx1<<>xxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm8.Create);
SetT32("111110001000xxxxxxxxxxxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCodeT32MemImm12.Create);
SetT32("111110000010xxxxxxxx1<<>xxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm8.Create);
SetT32("111110001010xxxxxxxxxxxxxxxxxxxx", InstName.Strh, InstEmit32.Strh, OpCodeT32MemImm12.Create);
SetT32("11101011101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluRsImm.Create);
SetT32("11110x01101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluImm.Create);
SetT32("111010101001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluRsImm.Create);
SetT32("11110x001001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluImm.Create);
SetT32("111010100001xxxx0xxx1111xxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT32AluRsImm.Create);
SetT32("11110x000001xxxx0xxx1111xxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT32AluImm.Create);
#endregion
FillFastLookupTable(InstA32FastLookup, AllInstA32, ToFastLookupIndexA);
FillFastLookupTable(InstT32FastLookup, AllInstT32, ToFastLookupIndexT);
FillFastLookupTable(InstA64FastLookup, AllInstA64, ToFastLookupIndexA);
}
private static void FillFastLookupTable(InstInfo[][] table, List<InstInfo> allInsts)
private static void FillFastLookupTable(InstInfo[][] table, List<InstInfo> allInsts, Func<int, int> ToFastLookupIndex)
{
List<InstInfo>[] temp = new List<InstInfo>[FastLookupSize];
@ -1011,20 +1147,30 @@ namespace ARMeilleure.Decoders
private static void SetA32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
{
Set(encoding, ExecutionMode.Aarch32Arm, new InstDescriptor(name, emitter), makeOp);
Set(encoding, AllInstA32, new InstDescriptor(name, emitter), makeOp);
}
private static void SetT16(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
{
encoding = "xxxxxxxxxxxxxxxx" + encoding;
Set(encoding, AllInstT32, new InstDescriptor(name, emitter), makeOp);
}
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
{
Set(encoding, ExecutionMode.Aarch32Thumb, new InstDescriptor(name, emitter), makeOp);
string reversedEncoding = encoding.Substring(16) + encoding.Substring(0, 16);
MakeOp reversedMakeOp =
(InstDescriptor inst, ulong address, int opCode)
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
Set(reversedEncoding, AllInstT32, new InstDescriptor(name, emitter), reversedMakeOp);
}
private static void SetA64(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
{
Set(encoding, ExecutionMode.Aarch64, new InstDescriptor(name, emitter), makeOp);
Set(encoding, AllInstA64, new InstDescriptor(name, emitter), makeOp);
}
private static void Set(string encoding, ExecutionMode mode, InstDescriptor inst, MakeOp makeOp)
private static void Set(string encoding, List<InstInfo> list, InstDescriptor inst, MakeOp makeOp)
{
int bit = encoding.Length - 1;
int value = 0;
@ -1073,7 +1219,7 @@ namespace ARMeilleure.Decoders
if (xBits == 0)
{
InsertInst(new InstInfo(xMask, value, inst, makeOp), mode);
list.Add(new InstInfo(xMask, value, inst, makeOp));
return;
}
@ -1089,34 +1235,24 @@ namespace ARMeilleure.Decoders
if (mask != blacklisted)
{
InsertInst(new InstInfo(xMask, value | mask, inst, makeOp), mode);
list.Add(new InstInfo(xMask, value | mask, inst, makeOp));
}
}
}
private static void InsertInst(InstInfo info, ExecutionMode mode)
{
switch (mode)
{
case ExecutionMode.Aarch32Arm: AllInstA32.Add(info); break;
case ExecutionMode.Aarch32Thumb: AllInstT32.Add(info); break;
case ExecutionMode.Aarch64: AllInstA64.Add(info); break;
}
}
public static (InstDescriptor inst, MakeOp makeOp) GetInstA32(int opCode)
{
return GetInstFromList(InstA32FastLookup[ToFastLookupIndex(opCode)], opCode);
return GetInstFromList(InstA32FastLookup[ToFastLookupIndexA(opCode)], opCode);
}
public static (InstDescriptor inst, MakeOp makeOp) GetInstT32(int opCode)
{
return GetInstFromList(InstT32FastLookup[ToFastLookupIndex(opCode)], opCode);
return GetInstFromList(InstT32FastLookup[ToFastLookupIndexT(opCode)], opCode);
}
public static (InstDescriptor inst, MakeOp makeOp) GetInstA64(int opCode)
{
return GetInstFromList(InstA64FastLookup[ToFastLookupIndex(opCode)], opCode);
return GetInstFromList(InstA64FastLookup[ToFastLookupIndexA(opCode)], opCode);
}
private static (InstDescriptor inst, MakeOp makeOp) GetInstFromList(InstInfo[] insts, int opCode)
@ -1132,9 +1268,14 @@ namespace ARMeilleure.Decoders
return (new InstDescriptor(InstName.Und, InstEmit.Und), null);
}
private static int ToFastLookupIndex(int value)
private static int ToFastLookupIndexA(int value)
{
return ((value >> 10) & 0x00F) | ((value >> 18) & 0xFF0);
}
private static int ToFastLookupIndexT(int value)
{
return (value >> 4) & 0xFFF;
}
}
}

View File

@ -1,13 +1,14 @@
// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf
using ARMeilleure.State;
using System;
namespace ARMeilleure.Instructions
{
static class CryptoHelper
{
#region "LookUp Tables"
private static readonly byte[] _sBox = new byte[]
private static ReadOnlySpan<byte> _sBox => new byte[]
{
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
@ -27,7 +28,7 @@ namespace ARMeilleure.Instructions
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
};
private static readonly byte[] _invSBox = new byte[]
private static ReadOnlySpan<byte> _invSBox => new byte[]
{
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
@ -47,7 +48,7 @@ namespace ARMeilleure.Instructions
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
};
private static readonly byte[] _gfMul02 = new byte[]
private static ReadOnlySpan<byte> _gfMul02 => new byte[]
{
0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
@ -67,7 +68,7 @@ namespace ARMeilleure.Instructions
0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5
};
private static readonly byte[] _gfMul03 = new byte[]
private static ReadOnlySpan<byte> _gfMul03 => new byte[]
{
0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11,
0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21,
@ -87,7 +88,7 @@ namespace ARMeilleure.Instructions
0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a
};
private static readonly byte[] _gfMul09 = new byte[]
private static ReadOnlySpan<byte> _gfMul09 => new byte[]
{
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7,
@ -107,7 +108,7 @@ namespace ARMeilleure.Instructions
0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46
};
private static readonly byte[] _gfMul0B = new byte[]
private static ReadOnlySpan<byte> _gfMul0B => new byte[]
{
0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9,
@ -127,7 +128,7 @@ namespace ARMeilleure.Instructions
0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3
};
private static readonly byte[] _gfMul0D = new byte[]
private static ReadOnlySpan<byte> _gfMul0D => new byte[]
{
0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
@ -147,7 +148,7 @@ namespace ARMeilleure.Instructions
0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97
};
private static readonly byte[] _gfMul0E = new byte[]
private static ReadOnlySpan<byte> _gfMul0E => new byte[]
{
0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba,
@ -167,12 +168,12 @@ namespace ARMeilleure.Instructions
0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d
};
private static readonly byte[] _srPerm = new byte[]
private static ReadOnlySpan<byte> _srPerm => new byte[]
{
0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3
};
private static readonly byte[] _isrPerm = new byte[]
private static ReadOnlySpan<byte> _isrPerm => new byte[]
{
0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11
};

View File

@ -20,7 +20,7 @@ namespace ARMeilleure.Instructions
Operand res = context.Add(n, m);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
@ -44,7 +44,7 @@ namespace ARMeilleure.Instructions
res = context.Add(res, carry);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
@ -64,7 +64,7 @@ namespace ARMeilleure.Instructions
Operand res = context.BitwiseAnd(n, m);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
@ -110,7 +110,7 @@ namespace ARMeilleure.Instructions
Operand res = context.BitwiseAnd(n, context.BitwiseNot(m));
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
@ -161,7 +161,7 @@ namespace ARMeilleure.Instructions
Operand res = context.BitwiseExclusiveOr(n, m);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
@ -175,7 +175,7 @@ namespace ARMeilleure.Instructions
Operand m = GetAluM(context);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, m);
}
@ -204,7 +204,7 @@ namespace ARMeilleure.Instructions
Operand res = context.Multiply(n, m);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
@ -219,7 +219,7 @@ namespace ARMeilleure.Instructions
Operand res = context.BitwiseNot(m);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
@ -236,7 +236,24 @@ namespace ARMeilleure.Instructions
Operand res = context.BitwiseOr(n, m);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
EmitAluStore(context, res);
}
public static void Orn(ArmEmitterContext context)
{
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
Operand n = GetAluN(context);
Operand m = GetAluM(context);
Operand res = context.BitwiseOr(n, context.BitwiseNot(m));
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
@ -315,7 +332,7 @@ namespace ARMeilleure.Instructions
res = context.Subtract(res, borrow);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
@ -335,7 +352,7 @@ namespace ARMeilleure.Instructions
Operand res = context.Subtract(m, n);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
@ -359,7 +376,7 @@ namespace ARMeilleure.Instructions
res = context.Subtract(res, borrow);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
@ -420,7 +437,7 @@ namespace ARMeilleure.Instructions
Operand res = context.Subtract(n, m);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
@ -836,7 +853,7 @@ namespace ARMeilleure.Instructions
{
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
EmitGenericAluStoreA32(context, op.Rd, op.SetFlags, value);
EmitGenericAluStoreA32(context, op.Rd, ShouldSetFlags(context), value);
}
}
}

View File

@ -12,6 +12,18 @@ namespace ARMeilleure.Instructions
{
static class InstEmitAluHelper
{
public static bool ShouldSetFlags(ArmEmitterContext context)
{
IOpCode32HasSetFlags op = (IOpCode32HasSetFlags)context.CurrOp;
if (op.SetFlags == null)
{
return !context.IsInIfThenBlock;
}
return op.SetFlags.Value;
}
public static void EmitNZFlagsCheck(ArmEmitterContext context, Operand d)
{
SetFlag(context, PState.NFlag, context.ICompareLess (d, Const(d.Type, 0)));
@ -116,7 +128,7 @@ namespace ARMeilleure.Instructions
{
Debug.Assert(value.Type == OperandType.I32);
if (IsThumb(context.CurrOp))
if (((OpCode32)context.CurrOp).IsThumb())
{
bool isReturn = IsA32Return(context);
if (!isReturn)
@ -183,9 +195,9 @@ namespace ARMeilleure.Instructions
switch (context.CurrOp)
{
// ARM32.
case OpCode32AluImm op:
case IOpCode32AluImm op:
{
if (op.SetFlags && op.IsRotated)
if (ShouldSetFlags(context) && op.IsRotated && setCarry)
{
SetFlag(context, PState.CFlag, Const((uint)op.Immediate >> 31));
}
@ -195,10 +207,8 @@ namespace ARMeilleure.Instructions
case OpCode32AluImm16 op: return Const(op.Immediate);
case OpCode32AluRsImm op: return GetMShiftedByImmediate(context, op, setCarry);
case OpCode32AluRsReg op: return GetMShiftedByReg(context, op, setCarry);
case OpCodeT16AluImm8 op: return Const(op.Immediate);
case IOpCode32AluRsImm op: return GetMShiftedByImmediate(context, op, setCarry);
case IOpCode32AluRsReg op: return GetMShiftedByReg(context, op, setCarry);
case IOpCode32AluReg op: return GetIntA32(context, op.Rm);
@ -249,7 +259,7 @@ namespace ARMeilleure.Instructions
}
// ARM32 helpers.
public static Operand GetMShiftedByImmediate(ArmEmitterContext context, OpCode32AluRsImm op, bool setCarry)
public static Operand GetMShiftedByImmediate(ArmEmitterContext context, IOpCode32AluRsImm op, bool setCarry)
{
Operand m = GetIntA32(context, op.Rm);
@ -267,7 +277,7 @@ namespace ARMeilleure.Instructions
if (shift != 0)
{
setCarry &= op.SetFlags;
setCarry &= ShouldSetFlags(context);
switch (op.ShiftType)
{
@ -305,7 +315,7 @@ namespace ARMeilleure.Instructions
return shift;
}
public static Operand GetMShiftedByReg(ArmEmitterContext context, OpCode32AluRsReg op, bool setCarry)
public static Operand GetMShiftedByReg(ArmEmitterContext context, IOpCode32AluRsReg op, bool setCarry)
{
Operand m = GetIntA32(context, op.Rm);
Operand s = context.ZeroExtend8(OperandType.I32, GetIntA32(context, op.Rs));
@ -314,7 +324,7 @@ namespace ARMeilleure.Instructions
Operand zeroResult = m;
Operand shiftResult = m;
setCarry &= op.SetFlags;
setCarry &= ShouldSetFlags(context);
switch (op.ShiftType)
{

View File

@ -9,18 +9,25 @@ namespace ARMeilleure.Instructions
{
public static void Brk(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.Break));
OpCodeException op = (OpCodeException)context.CurrOp;
string name = nameof(NativeInterface.Break);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id));
context.LoadFromContext();
context.Return(Const(op.Address));
}
public static void Svc(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.SupervisorCall));
}
private static void EmitExceptionCall(ArmEmitterContext context, string name)
{
OpCodeException op = (OpCodeException)context.CurrOp;
string name = nameof(NativeInterface.SupervisorCall);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id));
@ -41,6 +48,8 @@ namespace ARMeilleure.Instructions
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.RawOpCode));
context.LoadFromContext();
context.Return(Const(op.Address));
}
}
}

View File

@ -10,25 +10,32 @@ namespace ARMeilleure.Instructions
{
public static void Svc(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.SupervisorCall));
}
IOpCode32Exception op = (IOpCode32Exception)context.CurrOp;
public static void Trap(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.Break));
}
private static void EmitExceptionCall(ArmEmitterContext context, string name)
{
OpCode32Exception op = (OpCode32Exception)context.CurrOp;
string name = nameof(NativeInterface.SupervisorCall);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id));
context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id));
context.LoadFromContext();
Translator.EmitSynchronization(context);
}
public static void Trap(ArmEmitterContext context)
{
IOpCode32Exception op = (IOpCode32Exception)context.CurrOp;
string name = nameof(NativeInterface.Break);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id));
context.LoadFromContext();
context.Return(Const(context.CurrOp.Address));
}
}
}

View File

@ -34,7 +34,7 @@ namespace ARMeilleure.Instructions
uint pc = op.GetPc();
bool isThumb = IsThumb(context.CurrOp);
bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
uint currentPc = isThumb
? pc | 1
@ -61,17 +61,17 @@ namespace ARMeilleure.Instructions
Operand addr = context.Copy(GetIntA32(context, op.Rm));
Operand bitOne = context.BitwiseAnd(addr, Const(1));
bool isThumb = IsThumb(context.CurrOp);
bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
uint currentPc = isThumb
? pc | 1
? (pc - 2) | 1
: pc - 4;
SetIntA32(context, GetBankedRegisterAlias(context.Mode, RegisterAlias.Aarch32Lr), Const(currentPc));
SetFlag(context, PState.TFlag, bitOne);
EmitVirtualCall(context, addr);
EmitBxWritePc(context, addr);
}
public static void Bx(ArmEmitterContext context)
@ -80,5 +80,32 @@ namespace ARMeilleure.Instructions
EmitBxWritePc(context, GetIntA32(context, op.Rm), op.Rm);
}
public static void Cbnz(ArmEmitterContext context) => EmitCb(context, onNotZero: true);
public static void Cbz(ArmEmitterContext context) => EmitCb(context, onNotZero: false);
private static void EmitCb(ArmEmitterContext context, bool onNotZero)
{
OpCodeT16BImmCmp op = (OpCodeT16BImmCmp)context.CurrOp;
Operand value = GetIntOrZR(context, op.Rn);
Operand lblTarget = context.GetLabel((ulong)op.Immediate);
if (onNotZero)
{
context.BranchIfTrue(lblTarget, value);
}
else
{
context.BranchIfFalse(lblTarget, value);
}
}
public static void It(ArmEmitterContext context)
{
OpCodeT16IfThen op = (OpCodeT16IfThen)context.CurrOp;
context.SetIfThenBlockState(op.IfThenBlockConds);
}
}
}

View File

@ -10,11 +10,6 @@ namespace ARMeilleure.Instructions
{
static class InstEmitHelper
{
public static bool IsThumb(OpCode op)
{
return op is OpCodeT16;
}
public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type)
{
Operand value = GetIntOrZR(context, rm);
@ -47,6 +42,20 @@ namespace ARMeilleure.Instructions
}
}
public static Operand GetIntA32AlignedPC(ArmEmitterContext context, int regIndex)
{
if (regIndex == RegisterAlias.Aarch32Pc)
{
OpCode32 op = (OpCode32)context.CurrOp;
return Const((int)(op.GetPc() & 0xfffffffc));
}
else
{
return Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32);
}
}
public static Operand GetVecA32(int regIndex)
{
return Register(regIndex, RegisterType.Vector, OperandType.V128);
@ -172,7 +181,7 @@ namespace ARMeilleure.Instructions
SetFlag(context, PState.TFlag, mode);
Operand addr = context.ConditionalSelect(mode, pc, context.BitwiseAnd(pc, Const(~3)));
Operand addr = context.ConditionalSelect(mode, context.BitwiseAnd(pc, Const(~1)), context.BitwiseAnd(pc, Const(~3)));
InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn);
}

View File

@ -32,7 +32,7 @@ namespace ARMeilleure.Instructions
public static void Ldm(ArmEmitterContext context)
{
OpCode32MemMult op = (OpCode32MemMult)context.CurrOp;
IOpCode32MemMult op = (IOpCode32MemMult)context.CurrOp;
Operand n = GetIntA32(context, op.Rn);
@ -95,7 +95,7 @@ namespace ARMeilleure.Instructions
public static void Stm(ArmEmitterContext context)
{
OpCode32MemMult op = (OpCode32MemMult)context.CurrOp;
IOpCode32MemMult op = (IOpCode32MemMult)context.CurrOp;
Operand n = context.Copy(GetIntA32(context, op.Rn));
@ -151,9 +151,9 @@ namespace ARMeilleure.Instructions
private static void EmitLoadOrStore(ArmEmitterContext context, int size, AccessType accType)
{
OpCode32Mem op = (OpCode32Mem)context.CurrOp;
IOpCode32Mem op = (IOpCode32Mem)context.CurrOp;
Operand n = context.Copy(GetIntA32(context, op.Rn));
Operand n = context.Copy(GetIntA32AlignedPC(context, op.Rn));
Operand m = GetMemM(context, setCarry: false);
Operand temp = default;
@ -255,5 +255,11 @@ namespace ARMeilleure.Instructions
}
}
}
public static void Adr(ArmEmitterContext context)
{
IOpCode32Adr op = (IOpCode32Adr)context.CurrOp;
SetIntA32(context, op.Rd, Const(op.Immediate));
}
}
}

View File

@ -130,11 +130,6 @@ namespace ARMeilleure.Instructions
bool ordered = (accType & AccessType.Ordered) != 0;
bool exclusive = (accType & AccessType.Exclusive) != 0;
if (ordered)
{
EmitBarrier(context);
}
Operand address = context.Copy(GetIntOrSP(context, op.Rn));
Operand t = GetIntOrZR(context, op.Rt);
@ -163,6 +158,11 @@ namespace ARMeilleure.Instructions
{
EmitStoreExclusive(context, address, t, exclusive, op.Size, op.Rs, a32: false);
}
if (ordered)
{
EmitBarrier(context);
}
}
private static void EmitBarrier(ArmEmitterContext context)

View File

@ -146,13 +146,13 @@ namespace ARMeilleure.Instructions
var exclusive = (accType & AccessType.Exclusive) != 0;
var ordered = (accType & AccessType.Ordered) != 0;
if (ordered)
{
EmitBarrier(context);
}
if ((accType & AccessType.Load) != 0)
{
if (ordered)
{
EmitBarrier(context);
}
if (size == DWordSizeLog2)
{
// Keep loads atomic - make the call to get the whole region and then decompose it into parts
@ -219,6 +219,11 @@ namespace ARMeilleure.Instructions
Operand value = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt));
EmitStoreExclusive(context, address, value, exclusive, size, op.Rd, a32: true);
}
if (ordered)
{
EmitBarrier(context);
}
}
}

View File

@ -549,9 +549,9 @@ namespace ARMeilleure.Instructions
{
case OpCode32MemRsImm op: return GetMShiftedByImmediate(context, op, setCarry);
case OpCode32MemReg op: return GetIntA32(context, op.Rm);
case IOpCode32MemReg op: return GetIntA32(context, op.Rm);
case OpCode32Mem op: return Const(op.Immediate);
case IOpCode32Mem op: return Const(op.Immediate);
case OpCode32SimdMemImm op: return Const(op.Immediate);

View File

@ -33,7 +33,7 @@ namespace ARMeilleure.Instructions
Operand res = context.Add(a, context.Multiply(n, m));
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
@ -250,13 +250,13 @@ namespace ARMeilleure.Instructions
Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32)));
Operand lo = context.ConvertI64ToI32(res);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
EmitGenericAluStoreA32(context, op.RdHi, op.SetFlags, hi);
EmitGenericAluStoreA32(context, op.RdLo, op.SetFlags, lo);
EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi);
EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo);
}
public static void Smulw_(ArmEmitterContext context)
@ -320,13 +320,13 @@ namespace ARMeilleure.Instructions
Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32)));
Operand lo = context.ConvertI64ToI32(res);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
EmitGenericAluStoreA32(context, op.RdHi, op.SetFlags, hi);
EmitGenericAluStoreA32(context, op.RdLo, op.SetFlags, lo);
EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi);
EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo);
}
private static void EmitMlal(ArmEmitterContext context, bool signed)
@ -356,13 +356,13 @@ namespace ARMeilleure.Instructions
Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32)));
Operand lo = context.ConvertI64ToI32(res);
if (op.SetFlags)
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
EmitGenericAluStoreA32(context, op.RdHi, op.SetFlags, hi);
EmitGenericAluStoreA32(context, op.RdLo, op.SetFlags, lo);
EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi);
EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo);
}
private static void UpdateQFlag(ArmEmitterContext context, Operand q)

View File

@ -12,7 +12,8 @@ namespace ARMeilleure.Instructions
{
static partial class InstEmit
{
private const int DczSizeLog2 = 4;
private const int DczSizeLog2 = 4; // Log2 size in words
public const int DczSizeInBytes = 4 << DczSizeLog2;
public static void Hint(ArmEmitterContext context)
{
@ -87,7 +88,7 @@ namespace ARMeilleure.Instructions
// DC ZVA
Operand t = GetIntOrZR(context, op.Rt);
for (long offset = 0; offset < (4 << DczSizeLog2); offset += 8)
for (long offset = 0; offset < DczSizeInBytes; offset += 8)
{
Operand address = context.Add(t, Const(offset));
@ -98,7 +99,12 @@ namespace ARMeilleure.Instructions
}
// No-op
case 0b11_011_0111_1110_001: //DC CIVAC
case 0b11_011_0111_1110_001: // DC CIVAC
break;
case 0b11_011_0111_0101_001: // IC IVAU
Operand target = Register(op.Rt, RegisterType.Integer, OperandType.I64);
context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)), target);
break;
}
}

View File

@ -48,6 +48,7 @@ namespace ARMeilleure.Instructions
Extr,
Hint,
Isb,
It,
Ldar,
Ldaxp,
Ldaxr,
@ -512,6 +513,8 @@ namespace ARMeilleure.Instructions
Mvn,
Pkh,
Pld,
Pop,
Push,
Rev,
Revsh,
Rsb,

View File

@ -242,6 +242,11 @@ namespace ARMeilleure.Instructions
return (ulong)function.FuncPtr.ToInt64();
}
public static void InvalidateCacheLine(ulong address)
{
Context.Translator.InvalidateJitCacheRegion(address, InstEmit.DczSizeInBytes);
}
public static bool CheckSynchronization()
{
Statistics.PauseTimer();

View File

@ -824,7 +824,7 @@ namespace ARMeilleure.Instructions
return (ulong)(size - 1);
}
private static readonly byte[] ClzNibbleTbl = { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
private static ReadOnlySpan<byte> ClzNibbleTbl => new byte[] { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
public static ulong CountLeadingZeros(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.).
{

View File

@ -191,7 +191,7 @@ namespace ARMeilleure.Signal
// Is the fault address within this tracked region?
Operand inRange = context.BitwiseAnd(
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
context.ICompare(faultAddress, rangeEndAddress, Comparison.Less)
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI)
);
// Only call tracking if in range.

View File

@ -43,6 +43,12 @@ namespace ARMeilleure.State
public long TpidrEl0 { get; set; }
public long Tpidr { get; set; }
public uint Pstate
{
get => _nativeContext.GetPstate();
set => _nativeContext.SetPstate(value);
}
public FPCR Fpcr { get; set; }
public FPSR Fpsr { get; set; }
public FPCR StandardFpcrValue => (Fpcr & (FPCR.Ahp)) | FPCR.Dn | FPCR.Fz;

View File

@ -95,6 +95,25 @@ namespace ARMeilleure.State
GetStorage().Flags[(int)flag] = value ? 1u : 0u;
}
public unsafe uint GetPstate()
{
uint value = 0;
for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++)
{
value |= GetStorage().Flags[flag] != 0 ? 1u << flag : 0u;
}
return value;
}
public unsafe void SetPstate(uint value)
{
for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++)
{
uint bit = 1u << flag;
GetStorage().Flags[flag] = (value & bit) == bit ? 1u : 0u;
}
}
public unsafe bool GetFPStateFlag(FPState flag)
{
if ((uint)flag >= RegisterConsts.FpFlagsCount)

View File

@ -54,6 +54,11 @@ namespace ARMeilleure.Translation
public bool HighCq { get; }
public Aarch32Mode Mode { get; }
private int _ifThenBlockStateIndex = 0;
private Condition[] _ifThenBlockState = { };
public bool IsInIfThenBlock => _ifThenBlockStateIndex < _ifThenBlockState.Length;
public Condition CurrentIfThenBlockCond => _ifThenBlockState[_ifThenBlockStateIndex];
public ArmEmitterContext(
IMemoryManager memory,
EntryTable<uint> countTable,
@ -196,5 +201,19 @@ namespace ARMeilleure.Translation
return default;
}
public void SetIfThenBlockState(Condition[] state)
{
_ifThenBlockState = state;
_ifThenBlockStateIndex = 0;
}
public void AdvanceIfThenBlockState()
{
if (IsInIfThenBlock)
{
_ifThenBlockStateIndex++;
}
}
}
}

View File

@ -114,6 +114,7 @@ namespace ARMeilleure.Translation
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpscr))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));

View File

@ -0,0 +1,756 @@
using System;
using System.Collections.Generic;
namespace ARMeilleure.Translation
{
/// <summary>
/// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges.
/// </summary>
/// <typeparam name="K">Key</typeparam>
/// <typeparam name="V">Value</typeparam>
public class IntervalTree<K, V> where K : IComparable<K>
{
private const int ArrayGrowthSize = 32;
private const bool Black = true;
private const bool Red = false;
private IntervalTreeNode<K, V> _root = null;
private int _count = 0;
public int Count => _count;
public IntervalTree() { }
#region Public Methods
/// <summary>
/// Gets the values of the interval whose key is <paramref name="key"/>.
/// </summary>
/// <param name="key">Key of the node value to get</param>
/// <param name="value">Value with the given <paramref name="key"/></param>
/// <returns>True if the key is on the dictionary, false otherwise</returns>
public bool TryGet(K key, out V value)
{
IntervalTreeNode<K, V> node = GetNode(key);
if (node == null)
{
value = default;
return false;
}
value = node.Value;
return true;
}
/// <summary>
/// Returns the start addresses of the intervals whose start and end keys overlap the given range.
/// </summary>
/// <param name="start">Start of the range</param>
/// <param name="end">End of the range</param>
/// <param name="overlaps">Overlaps array to place results in</param>
/// <param name="overlapCount">Index to start writing results into the array. Defaults to 0</param>
/// <returns>Number of intervals found</returns>
public int Get(K start, K end, ref K[] overlaps, int overlapCount = 0)
{
GetValues(_root, start, end, ref overlaps, ref overlapCount);
return overlapCount;
}
/// <summary>
/// Adds a new interval into the tree whose start is <paramref name="start"/>, end is <paramref name="end"/> and value is <paramref name="value"/>.
/// </summary>
/// <param name="start">Start of the range to add</param>
/// <param name="end">End of the range to insert</param>
/// <param name="value">Value to add</param>
/// <param name="updateFactoryCallback">Optional factory used to create a new value if <paramref name="start"/> is already on the tree</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
/// <returns>True if the value was added, false if the start key was already in the dictionary</returns>
public bool AddOrUpdate(K start, K end, V value, Func<K, V, V> updateFactoryCallback)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
return BSTInsert(start, end, value, updateFactoryCallback, out IntervalTreeNode<K, V> node);
}
/// <summary>
/// Gets an existing or adds a new interval into the tree whose start is <paramref name="start"/>, end is <paramref name="end"/> and value is <paramref name="value"/>.
/// </summary>
/// <param name="start">Start of the range to add</param>
/// <param name="end">End of the range to insert</param>
/// <param name="value">Value to add</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
/// <returns><paramref name="value"/> if <paramref name="start"/> is not yet on the tree, or the existing value otherwise</returns>
public V GetOrAdd(K start, K end, V value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
BSTInsert(start, end, value, null, out IntervalTreeNode<K, V> node);
return node.Value;
}
/// <summary>
/// Removes a value from the tree, searching for it with <paramref name="key"/>.
/// </summary>
/// <param name="key">Key of the node to remove</param>
/// <returns>Number of deleted values</returns>
public int Remove(K key)
{
int removed = Delete(key);
_count -= removed;
return removed;
}
/// <summary>
/// Adds all the nodes in the dictionary into <paramref name="list"/>.
/// </summary>
/// <returns>A list of all values sorted by Key Order</returns>
public List<V> AsList()
{
List<V> list = new List<V>();
AddToList(_root, list);
return list;
}
#endregion
#region Private Methods (BST)
/// <summary>
/// Adds all values that are children of or contained within <paramref name="node"/> into <paramref name="list"/>, in Key Order.
/// </summary>
/// <param name="node">The node to search for values within</param>
/// <param name="list">The list to add values to</param>
private void AddToList(IntervalTreeNode<K, V> node, List<V> list)
{
if (node == null)
{
return;
}
AddToList(node.Left, list);
list.Add(node.Value);
AddToList(node.Right, list);
}
/// <summary>
/// Retrieve the node reference whose key is <paramref name="key"/>, or null if no such node exists.
/// </summary>
/// <param name="key">Key of the node to get</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
/// <returns>Node reference in the tree</returns>
private IntervalTreeNode<K, V> GetNode(K key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
IntervalTreeNode<K, V> node = _root;
while (node != null)
{
int cmp = key.CompareTo(node.Start);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
return node;
}
}
return null;
}
/// <summary>
/// Retrieve all values that overlap the given start and end keys.
/// </summary>
/// <param name="start">Start of the range</param>
/// <param name="end">End of the range</param>
/// <param name="overlaps">Overlaps array to place results in</param>
/// <param name="overlapCount">Overlaps count to update</param>
private void GetValues(IntervalTreeNode<K, V> node, K start, K end, ref K[] overlaps, ref int overlapCount)
{
if (node == null || start.CompareTo(node.Max) >= 0)
{
return;
}
GetValues(node.Left, start, end, ref overlaps, ref overlapCount);
bool endsOnRight = end.CompareTo(node.Start) > 0;
if (endsOnRight)
{
if (start.CompareTo(node.End) < 0)
{
if (overlaps.Length >= overlapCount)
{
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
}
overlaps[overlapCount++] = node.Start;
}
GetValues(node.Right, start, end, ref overlaps, ref overlapCount);
}
}
/// <summary>
/// Propagate an increase in max value starting at the given node, heading up the tree.
/// This should only be called if the max increases - not for rebalancing or removals.
/// </summary>
/// <param name="node">The node to start propagating from</param>
private void PropagateIncrease(IntervalTreeNode<K, V> node)
{
K max = node.Max;
IntervalTreeNode<K, V> ptr = node;
while ((ptr = ptr.Parent) != null)
{
if (max.CompareTo(ptr.Max) > 0)
{
ptr.Max = max;
}
else
{
break;
}
}
}
/// <summary>
/// Propagate recalculating max value starting at the given node, heading up the tree.
/// This fully recalculates the max value from all children when there is potential for it to decrease.
/// </summary>
/// <param name="node">The node to start propagating from</param>
private void PropagateFull(IntervalTreeNode<K, V> node)
{
IntervalTreeNode<K, V> ptr = node;
do
{
K max = ptr.End;
if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0)
{
max = ptr.Left.Max;
}
if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0)
{
max = ptr.Right.Max;
}
ptr.Max = max;
} while ((ptr = ptr.Parent) != null);
}
/// <summary>
/// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key.
/// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than <paramref name="start"/>, and all children in the right subtree are greater than <paramref name="start"/>.
/// Each node can contain multiple values, and has an end address which is the maximum of all those values.
/// Post insertion, the "max" value of the node and all parents are updated.
/// </summary>
/// <param name="start">Start of the range to insert</param>
/// <param name="end">End of the range to insert</param>
/// <param name="value">Value to insert</param>
/// <param name="updateFactoryCallback">Optional factory used to create a new value if <paramref name="start"/> is already on the tree</param>
/// <param name="outNode">Node that was inserted or modified</param>
/// <returns>True if <paramref name="start"/> was not yet on the tree, false otherwise</returns>
private bool BSTInsert(K start, K end, V value, Func<K, V, V> updateFactoryCallback, out IntervalTreeNode<K, V> outNode)
{
IntervalTreeNode<K, V> parent = null;
IntervalTreeNode<K, V> node = _root;
while (node != null)
{
parent = node;
int cmp = start.CompareTo(node.Start);
if (cmp < 0)
{
node = node.Left;
}
else if (cmp > 0)
{
node = node.Right;
}
else
{
outNode = node;
if (updateFactoryCallback != null)
{
// Replace
node.Value = updateFactoryCallback(start, node.Value);
int endCmp = end.CompareTo(node.End);
if (endCmp > 0)
{
node.End = end;
if (end.CompareTo(node.Max) > 0)
{
node.Max = end;
PropagateIncrease(node);
RestoreBalanceAfterInsertion(node);
}
}
else if (endCmp < 0)
{
node.End = end;
PropagateFull(node);
}
}
return false;
}
}
IntervalTreeNode<K, V> newNode = new IntervalTreeNode<K, V>(start, end, value, parent);
if (newNode.Parent == null)
{
_root = newNode;
}
else if (start.CompareTo(parent.Start) < 0)
{
parent.Left = newNode;
}
else
{
parent.Right = newNode;
}
PropagateIncrease(newNode);
_count++;
RestoreBalanceAfterInsertion(newNode);
outNode = newNode;
return true;
}
/// <summary>
/// Removes the value from the dictionary after searching for it with <paramref name="key">.
/// </summary>
/// <param name="key">Key to search for</param>
/// <returns>Number of deleted values</returns>
private int Delete(K key)
{
IntervalTreeNode<K, V> nodeToDelete = GetNode(key);
if (nodeToDelete == null)
{
return 0;
}
IntervalTreeNode<K, V> replacementNode;
if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null)
{
replacementNode = nodeToDelete;
}
else
{
replacementNode = PredecessorOf(nodeToDelete);
}
IntervalTreeNode<K, V> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
if (ParentOf(replacementNode) == null)
{
_root = tmp;
}
else if (replacementNode == LeftOf(ParentOf(replacementNode)))
{
ParentOf(replacementNode).Left = tmp;
}
else
{
ParentOf(replacementNode).Right = tmp;
}
if (replacementNode != nodeToDelete)
{
nodeToDelete.Start = replacementNode.Start;
nodeToDelete.Value = replacementNode.Value;
nodeToDelete.End = replacementNode.End;
nodeToDelete.Max = replacementNode.Max;
}
PropagateFull(replacementNode);
if (tmp != null && ColorOf(replacementNode) == Black)
{
RestoreBalanceAfterRemoval(tmp);
}
return 1;
}
/// <summary>
/// Returns the node with the largest key where <paramref name="node"/> is considered the root node.
/// </summary>
/// <param name="node">Root Node</param>
/// <returns>Node with the maximum key in the tree of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> Maximum(IntervalTreeNode<K, V> node)
{
IntervalTreeNode<K, V> tmp = node;
while (tmp.Right != null)
{
tmp = tmp.Right;
}
return tmp;
}
/// <summary>
/// Finds the node whose key is immediately less than <paramref name="node"/>.
/// </summary>
/// <param name="node">Node to find the predecessor of</param>
/// <returns>Predecessor of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> PredecessorOf(IntervalTreeNode<K, V> node)
{
if (node.Left != null)
{
return Maximum(node.Left);
}
IntervalTreeNode<K, V> parent = node.Parent;
while (parent != null && node == parent.Left)
{
node = parent;
parent = parent.Parent;
}
return parent;
}
#endregion
#region Private Methods (RBL)
private void RestoreBalanceAfterRemoval(IntervalTreeNode<K, V> balanceNode)
{
IntervalTreeNode<K, V> ptr = balanceNode;
while (ptr != _root && ColorOf(ptr) == Black)
{
if (ptr == LeftOf(ParentOf(ptr)))
{
IntervalTreeNode<K, V> sibling = RightOf(ParentOf(ptr));
if (ColorOf(sibling) == Red)
{
SetColor(sibling, Black);
SetColor(ParentOf(ptr), Red);
RotateLeft(ParentOf(ptr));
sibling = RightOf(ParentOf(ptr));
}
if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black)
{
SetColor(sibling, Red);
ptr = ParentOf(ptr);
}
else
{
if (ColorOf(RightOf(sibling)) == Black)
{
SetColor(LeftOf(sibling), Black);
SetColor(sibling, Red);
RotateRight(sibling);
sibling = RightOf(ParentOf(ptr));
}
SetColor(sibling, ColorOf(ParentOf(ptr)));
SetColor(ParentOf(ptr), Black);
SetColor(RightOf(sibling), Black);
RotateLeft(ParentOf(ptr));
ptr = _root;
}
}
else
{
IntervalTreeNode<K, V> sibling = LeftOf(ParentOf(ptr));
if (ColorOf(sibling) == Red)
{
SetColor(sibling, Black);
SetColor(ParentOf(ptr), Red);
RotateRight(ParentOf(ptr));
sibling = LeftOf(ParentOf(ptr));
}
if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black)
{
SetColor(sibling, Red);
ptr = ParentOf(ptr);
}
else
{
if (ColorOf(LeftOf(sibling)) == Black)
{
SetColor(RightOf(sibling), Black);
SetColor(sibling, Red);
RotateLeft(sibling);
sibling = LeftOf(ParentOf(ptr));
}
SetColor(sibling, ColorOf(ParentOf(ptr)));
SetColor(ParentOf(ptr), Black);
SetColor(LeftOf(sibling), Black);
RotateRight(ParentOf(ptr));
ptr = _root;
}
}
}
SetColor(ptr, Black);
}
private void RestoreBalanceAfterInsertion(IntervalTreeNode<K, V> balanceNode)
{
SetColor(balanceNode, Red);
while (balanceNode != null && balanceNode != _root && ColorOf(ParentOf(balanceNode)) == Red)
{
if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode))))
{
IntervalTreeNode<K, V> sibling = RightOf(ParentOf(ParentOf(balanceNode)));
if (ColorOf(sibling) == Red)
{
SetColor(ParentOf(balanceNode), Black);
SetColor(sibling, Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
balanceNode = ParentOf(ParentOf(balanceNode));
}
else
{
if (balanceNode == RightOf(ParentOf(balanceNode)))
{
balanceNode = ParentOf(balanceNode);
RotateLeft(balanceNode);
}
SetColor(ParentOf(balanceNode), Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
RotateRight(ParentOf(ParentOf(balanceNode)));
}
}
else
{
IntervalTreeNode<K, V> sibling = LeftOf(ParentOf(ParentOf(balanceNode)));
if (ColorOf(sibling) == Red)
{
SetColor(ParentOf(balanceNode), Black);
SetColor(sibling, Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
balanceNode = ParentOf(ParentOf(balanceNode));
}
else
{
if (balanceNode == LeftOf(ParentOf(balanceNode)))
{
balanceNode = ParentOf(balanceNode);
RotateRight(balanceNode);
}
SetColor(ParentOf(balanceNode), Black);
SetColor(ParentOf(ParentOf(balanceNode)), Red);
RotateLeft(ParentOf(ParentOf(balanceNode)));
}
}
}
SetColor(_root, Black);
}
private void RotateLeft(IntervalTreeNode<K, V> node)
{
if (node != null)
{
IntervalTreeNode<K, V> right = RightOf(node);
node.Right = LeftOf(right);
if (node.Right != null)
{
node.Right.Parent = node;
}
IntervalTreeNode<K, V> nodeParent = ParentOf(node);
right.Parent = nodeParent;
if (nodeParent == null)
{
_root = right;
}
else if (node == LeftOf(nodeParent))
{
nodeParent.Left = right;
}
else
{
nodeParent.Right = right;
}
right.Left = node;
node.Parent = right;
PropagateFull(node);
}
}
private void RotateRight(IntervalTreeNode<K, V> node)
{
if (node != null)
{
IntervalTreeNode<K, V> left = LeftOf(node);
node.Left = RightOf(left);
if (node.Left != null)
{
node.Left.Parent = node;
}
IntervalTreeNode<K, V> nodeParent = ParentOf(node);
left.Parent = nodeParent;
if (nodeParent == null)
{
_root = left;
}
else if (node == RightOf(nodeParent))
{
nodeParent.Right = left;
}
else
{
nodeParent.Left = left;
}
left.Right = node;
node.Parent = left;
PropagateFull(node);
}
}
#endregion
#region Safety-Methods
// These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions.
/// <summary>
/// Returns the color of <paramref name="node"/>, or Black if it is null.
/// </summary>
/// <param name="node">Node</param>
/// <returns>The boolean color of <paramref name="node"/>, or black if null</returns>
private static bool ColorOf(IntervalTreeNode<K, V> node)
{
return node == null || node.Color;
}
/// <summary>
/// Sets the color of <paramref name="node"/> node to <paramref name="color"/>.
/// <br></br>
/// This method does nothing if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to set the color of</param>
/// <param name="color">Color (Boolean)</param>
private static void SetColor(IntervalTreeNode<K, V> node, bool color)
{
if (node != null)
{
node.Color = color;
}
}
/// <summary>
/// This method returns the left node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to retrieve the left child from</param>
/// <returns>Left child of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> LeftOf(IntervalTreeNode<K, V> node)
{
return node?.Left;
}
/// <summary>
/// This method returns the right node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to retrieve the right child from</param>
/// <returns>Right child of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> RightOf(IntervalTreeNode<K, V> node)
{
return node?.Right;
}
/// <summary>
/// Returns the parent node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
/// </summary>
/// <param name="node">Node to retrieve the parent from</param>
/// <returns>Parent of <paramref name="node"/></returns>
private static IntervalTreeNode<K, V> ParentOf(IntervalTreeNode<K, V> node)
{
return node?.Parent;
}
#endregion
public bool ContainsKey(K key)
{
return GetNode(key) != null;
}
public void Clear()
{
_root = null;
_count = 0;
}
}
/// <summary>
/// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V.
/// </summary>
/// <typeparam name="K">Key type of the node</typeparam>
/// <typeparam name="V">Value type of the node</typeparam>
internal class IntervalTreeNode<K, V>
{
internal bool Color = true;
internal IntervalTreeNode<K, V> Left = null;
internal IntervalTreeNode<K, V> Right = null;
internal IntervalTreeNode<K, V> Parent = null;
/// <summary>
/// The start of the range.
/// </summary>
internal K Start;
/// <summary>
/// The end of the range.
/// </summary>
internal K End;
/// <summary>
/// The maximum end value of this node and all its children.
/// </summary>
internal K Max;
/// <summary>
/// Value stored on this node.
/// </summary>
internal V Value;
public IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
{
this.Start = start;
this.End = end;
this.Max = end;
this.Value = value;
this.Parent = parent;
}
}
}

View File

@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 3061; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 3267; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
@ -585,7 +585,7 @@ namespace ARMeilleure.Translation.PTC
translator.RegisterFunction(infoEntry.Address, func);
bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, func);
bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, infoEntry.GuestSize, func);
Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique.");
}
@ -815,7 +815,7 @@ namespace ARMeilleure.Translation.PTC
TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq);
bool isAddressUnique = translator.Functions.TryAdd(address, func);
bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func);
Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique.");

View File

@ -96,7 +96,7 @@ namespace ARMeilleure.Translation.PTC
return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
}
internal static ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(ConcurrentDictionary<ulong, TranslatedFunction> funcs)
internal static ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache<TranslatedFunction> funcs)
{
var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();

View File

@ -113,7 +113,7 @@ namespace ARMeilleure.Translation
}
}
Array.Clear(localDefs, 0, localDefs.Length);
Array.Clear(localDefs);
}
// Second pass, rename variables with definitions on different blocks.

View File

@ -49,7 +49,7 @@ namespace ARMeilleure.Translation
private readonly AutoResetEvent _backgroundTranslatorEvent;
private readonly ReaderWriterLock _backgroundTranslatorLock;
internal ConcurrentDictionary<ulong, TranslatedFunction> Functions { get; }
internal TranslatorCache<TranslatedFunction> Functions { get; }
internal AddressTable<ulong> FunctionTable { get; }
internal EntryTable<uint> CountTable { get; }
internal TranslatorStubs Stubs { get; }
@ -75,7 +75,7 @@ namespace ARMeilleure.Translation
JitCache.Initialize(allocator);
CountTable = new EntryTable<uint>();
Functions = new ConcurrentDictionary<ulong, TranslatedFunction>();
Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = new AddressTable<ulong>(for64Bits ? Levels64Bit : Levels32Bit);
Stubs = new TranslatorStubs(this);
@ -93,12 +93,12 @@ namespace ARMeilleure.Translation
{
_backgroundTranslatorLock.AcquireReaderLock(Timeout.Infinite);
if (_backgroundStack.TryPop(out RejitRequest request) &&
if (_backgroundStack.TryPop(out RejitRequest request) &&
_backgroundSet.TryRemove(request.Address, out _))
{
TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true);
Functions.AddOrUpdate(request.Address, func, (key, oldFunc) =>
Functions.AddOrUpdate(request.Address, func.GuestSize, func, (key, oldFunc) =>
{
EnqueueForDeletion(key, oldFunc);
return func;
@ -196,7 +196,7 @@ namespace ARMeilleure.Translation
}
}
public ulong ExecuteSingle(State.ExecutionContext context, ulong address)
private ulong ExecuteSingle(State.ExecutionContext context, ulong address)
{
TranslatedFunction func = GetOrTranslate(address, context.ExecutionMode);
@ -209,13 +209,24 @@ namespace ARMeilleure.Translation
return nextAddr;
}
public ulong Step(State.ExecutionContext context, ulong address)
{
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
address = func.Execute(context);
EnqueueForDeletion(address, func);
return address;
}
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
{
if (!Functions.TryGetValue(address, out TranslatedFunction func))
{
func = Translate(address, mode, highCq: false);
TranslatedFunction oldFunc = Functions.GetOrAdd(address, func);
TranslatedFunction oldFunc = Functions.GetOrAdd(address, func.GuestSize, func);
if (oldFunc != func)
{
@ -242,7 +253,7 @@ namespace ARMeilleure.Translation
}
}
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq)
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false)
{
var context = new ArmEmitterContext(
Memory,
@ -255,7 +266,7 @@ namespace ARMeilleure.Translation
Logger.StartPass(PassName.Decoding);
Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleBlock: false);
Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleStep ? DecoderMode.SingleInstruction : DecoderMode.MultipleBlocks);
Logger.EndPass(PassName.Decoding);
@ -285,14 +296,14 @@ namespace ARMeilleure.Translation
var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
if (context.HasPtc)
if (context.HasPtc && !singleStep)
{
options |= CompilerOptions.Relocatable;
}
CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options);
if (context.HasPtc)
if (context.HasPtc && !singleStep)
{
Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize);
@ -380,6 +391,13 @@ namespace ARMeilleure.Translation
Operand lblPredicateSkip = default;
if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al)
{
lblPredicateSkip = Label();
InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, context.CurrentIfThenBlockCond.Invert());
}
if (opCode is OpCode32 op && op.Cond < Condition.Al)
{
lblPredicateSkip = Label();
@ -400,6 +418,11 @@ namespace ARMeilleure.Translation
{
context.MarkLabel(lblPredicateSkip);
}
if (context.IsInIfThenBlock && opCode.Instruction.Name != InstName.It)
{
context.AdvanceIfThenBlockState();
}
}
}
}
@ -459,7 +482,24 @@ namespace ARMeilleure.Translation
// If rejit is running, stop it as it may be trying to rejit a function on the invalidated region.
ClearRejitQueue(allowRequeue: true);
// TODO: Completely remove functions overlapping the specified range from the cache.
ulong[] overlapAddresses = Array.Empty<ulong>();
int overlapsCount = Functions.GetOverlaps(address, size, ref overlapAddresses);
for (int index = 0; index < overlapsCount; index++)
{
ulong overlapAddress = overlapAddresses[index];
if (Functions.TryGetValue(overlapAddress, out TranslatedFunction overlap))
{
Functions.Remove(overlapAddress);
Volatile.Write(ref FunctionTable.GetValue(overlapAddress), FunctionTable.Fill);
EnqueueForDeletion(overlapAddress, overlap);
}
}
// TODO: Remove overlapping functions from the JitCache aswell.
// This should be done safely, with a mechanism to ensure the function is not being executed.
}
internal void EnqueueForRejit(ulong guestAddress, ExecutionMode mode)
@ -481,7 +521,9 @@ namespace ARMeilleure.Translation
// Ensure no attempt will be made to compile new functions due to rejit.
ClearRejitQueue(allowRequeue: false);
foreach (var func in Functions.Values)
List<TranslatedFunction> functions = Functions.AsList();
foreach (var func in functions)
{
JitCache.Unmap(func.FuncPtr);

View File

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Threading;
namespace ARMeilleure.Translation
{
internal class TranslatorCache<T>
{
private readonly IntervalTree<ulong, T> _tree;
private readonly ReaderWriterLock _treeLock;
public int Count => _tree.Count;
public TranslatorCache()
{
_tree = new IntervalTree<ulong, T>();
_treeLock = new ReaderWriterLock();
}
public bool TryAdd(ulong address, ulong size, T value)
{
return AddOrUpdate(address, size, value, null);
}
public bool AddOrUpdate(ulong address, ulong size, T value, Func<ulong, T, T> updateFactoryCallback)
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback);
_treeLock.ReleaseWriterLock();
return result;
}
public T GetOrAdd(ulong address, ulong size, T value)
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
value = _tree.GetOrAdd(address, address + size, value);
_treeLock.ReleaseWriterLock();
return value;
}
public bool Remove(ulong address)
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
bool removed = _tree.Remove(address) != 0;
_treeLock.ReleaseWriterLock();
return removed;
}
public void Clear()
{
_treeLock.AcquireWriterLock(Timeout.Infinite);
_tree.Clear();
_treeLock.ReleaseWriterLock();
}
public bool ContainsKey(ulong address)
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
bool result = _tree.ContainsKey(address);
_treeLock.ReleaseReaderLock();
return result;
}
public bool TryGetValue(ulong address, out T value)
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
bool result = _tree.TryGet(address, out value);
_treeLock.ReleaseReaderLock();
return result;
}
public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps)
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
int count = _tree.Get(address, address + size, ref overlaps);
_treeLock.ReleaseReaderLock();
return count;
}
public List<T> AsList()
{
_treeLock.AcquireReaderLock(Timeout.Infinite);
List<T> list = _tree.AsList();
_treeLock.ReleaseReaderLock();
return list;
}
}
}

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>

View File

@ -18,8 +18,10 @@
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Audio.Renderer.Utils.Math;
using System;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.Command
@ -45,7 +47,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private const int FixedPointPrecision = 14;
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId)
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
{
Enabled = true;
NodeId = nodeId;
@ -63,9 +65,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
}
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
// TODO: Update delay processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
{
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
@ -78,133 +85,148 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float input = inputBuffer[i] * 64;
float delayLineValue = state.DelayLines[0].Read();
float lowPassResult = (input * inGain + delayLineValue * feedbackGain) * state.LowPassBaseGain + state.LowPassZ[0] * state.LowPassFeedbackGain;
float temp = input * inGain + delayLineValue * feedbackGain;
state.LowPassZ[0] = lowPassResult;
state.DelayLines[0].Update(lowPassResult);
state.UpdateLowPassFilter(ref temp, 1);
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelayStereo(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
const ushort channelCount = 2;
Span<float> channelInput = stackalloc float[channelCount];
Span<float> delayLineValues = stackalloc float[channelCount];
Span<float> temp = stackalloc float[channelCount];
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix2x2 delayFeedback = new Matrix2x2(delayFeedbackBaseGain , delayFeedbackCrossGain,
delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
{
for (int j = 0; j < channelCount; j++)
Vector2 channelInput = new Vector2
{
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
delayLineValues[j] = state.DelayLines[j].Read();
}
X = *((float*)inputBuffers[0] + i) * 64,
Y = *((float*)inputBuffers[1] + i) * 64,
};
temp[0] = channelInput[0] * inGain + delayLineValues[1] * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
temp[1] = channelInput[1] * inGain + delayLineValues[0] * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
for (int j = 0; j < channelCount; j++)
Vector2 delayLineValues = new Vector2()
{
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
X = state.DelayLines[0].Read(),
Y = state.DelayLines[1].Read(),
};
state.LowPassZ[j] = lowPassResult;
state.DelayLines[j].Update(lowPassResult);
Vector2 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
}
state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelayQuadraphonic(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
const ushort channelCount = 4;
Span<float> channelInput = stackalloc float[channelCount];
Span<float> delayLineValues = stackalloc float[channelCount];
Span<float> temp = stackalloc float[channelCount];
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix4x4 delayFeedback = new Matrix4x4(delayFeedbackBaseGain , delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
delayFeedbackCrossGain, delayFeedbackBaseGain , 0.0f , delayFeedbackCrossGain,
delayFeedbackCrossGain, 0.0f , delayFeedbackBaseGain , delayFeedbackCrossGain,
0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++)
{
for (int j = 0; j < channelCount; j++)
Vector4 channelInput = new Vector4
{
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
delayLineValues[j] = state.DelayLines[j].Read();
}
X = *((float*)inputBuffers[0] + i) * 64,
Y = *((float*)inputBuffers[1] + i) * 64,
Z = *((float*)inputBuffers[2] + i) * 64,
W = *((float*)inputBuffers[3] + i) * 64
};
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
temp[1] = channelInput[1] * inGain + (delayLineValues[0] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
for (int j = 0; j < channelCount; j++)
Vector4 delayLineValues = new Vector4()
{
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
X = state.DelayLines[0].Read(),
Y = state.DelayLines[1].Read(),
Z = state.DelayLines[2].Read(),
W = state.DelayLines[3].Read()
};
state.LowPassZ[j] = lowPassResult;
state.DelayLines[j].Update(lowPassResult);
Vector4 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
}
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
*((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
*((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelaySurround(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
{
const ushort channelCount = 6;
Span<float> channelInput = stackalloc float[channelCount];
Span<float> delayLineValues = stackalloc float[channelCount];
Span<float> temp = stackalloc float[channelCount];
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain , 0.0f , 0.0f , 0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain,
0.0f , delayFeedbackBaseGain , 0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f ,
delayFeedbackCrossGain, 0.0f , delayFeedbackBaseGain , delayFeedbackCrossGain, 0.0f , 0.0f ,
0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain , 0.0f , 0.0f ,
delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f , 0.0f , delayFeedbackBaseGain , 0.0f ,
0.0f , 0.0f , 0.0f , 0.0f , 0.0f , feedbackGain);
for (int i = 0; i < sampleCount; i++)
{
for (int j = 0; j < channelCount; j++)
Vector6 channelInput = new Vector6
{
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
delayLineValues[j] = state.DelayLines[j].Read();
}
X = *((float*)inputBuffers[0] + i) * 64,
Y = *((float*)inputBuffers[1] + i) * 64,
Z = *((float*)inputBuffers[2] + i) * 64,
W = *((float*)inputBuffers[3] + i) * 64,
V = *((float*)inputBuffers[4] + i) * 64,
U = *((float*)inputBuffers[5] + i) * 64
};
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[4]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
temp[1] = channelInput[1] * inGain + (delayLineValues[4] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
temp[4] = channelInput[4] * inGain + (delayLineValues[0] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[4] * delayFeedbackBaseGain;
temp[5] = channelInput[5] * inGain + delayLineValues[5] * delayFeedbackBaseGain;
for (int j = 0; j < channelCount; j++)
Vector6 delayLineValues = new Vector6
{
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
X = state.DelayLines[0].Read(),
Y = state.DelayLines[1].Read(),
Z = state.DelayLines[2].Read(),
W = state.DelayLines[3].Read(),
V = state.DelayLines[4].Read(),
U = state.DelayLines[5].Read()
};
state.LowPassZ[j] = lowPassResult;
state.DelayLines[j].Update(lowPassResult);
Vector6 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
}
state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
*((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
*((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
*((float*)outputBuffers[4] + i) = (channelInput.V * dryGain + delayLineValues.V * outGain) / 64;
*((float*)outputBuffers[5] + i) = (channelInput.U * dryGain + delayLineValues.U * outGain) / 64;
}
}

View File

@ -100,9 +100,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float inputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
float sampleInputMax = Math.Abs(inputSample);
float inputCoefficient = Parameter.ReleaseCoefficient;
@ -131,7 +133,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
*((float*)outputBuffers[channelIndex] + sampleIndex) = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
delayedSample = inputSample;

View File

@ -111,9 +111,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float inputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
float sampleInputMax = Math.Abs(inputSample);
float inputCoefficient = Parameter.ReleaseCoefficient;
@ -142,7 +144,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
*((float*)outputBuffers[channelIndex] + sampleIndex) = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
delayedSample = inputSample;

View File

@ -76,7 +76,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation
{
SourceSampleRate = SampleRate,
SampleFormat = SampleFormat.PcmInt16,
SampleFormat = SampleFormat.PcmFloat,
Pitch = Pitch,
DecodingBehaviour = DecodingBehaviour,
ExtraParameter = 0,

View File

@ -63,7 +63,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private Reverb3dParameter _parameter;
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId)
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
{
Enabled = true;
IsEffectEnabled = isEnabled;
@ -80,6 +80,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
}
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
// TODO: Update reverb 3d processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -194,7 +199,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (isSurround)
{
*((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.BackLeftDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
*((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.FrontCenterDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
}
}
}

View File

@ -66,7 +66,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private const int FixedPointPrecision = 14;
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported)
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
{
Enabled = true;
IsEffectEnabled = isEnabled;
@ -85,6 +85,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
IsLongSizePreDelaySupported = isLongSizePreDelaySupported;
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
// TODO: Update reverb processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -214,7 +219,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (isSurround)
{
outputValues[4] += state.BackLeftDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
outputValues[4] += state.FrontCenterDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
}
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)

View File

@ -445,5 +445,39 @@ namespace Ryujinx.Audio.Renderer.Dsp
ToIntSlow(output, input, sampleCount);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RemapLegacyChannelEffectMappingToChannelResourceMapping(bool isSupported, Span<ushort> bufferIndices)
{
if (!isSupported && bufferIndices.Length == 6)
{
ushort backLeft = bufferIndices[2];
ushort backRight = bufferIndices[3];
ushort frontCenter = bufferIndices[4];
ushort lowFrequency = bufferIndices[5];
bufferIndices[2] = frontCenter;
bufferIndices[3] = lowFrequency;
bufferIndices[4] = backLeft;
bufferIndices[5] = backRight;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RemapChannelResourceMappingToLegacy(bool isSupported, Span<ushort> bufferIndices)
{
if (isSupported && bufferIndices.Length == 6)
{
ushort frontCenter = bufferIndices[2];
ushort lowFrequency = bufferIndices[3];
ushort backLeft = bufferIndices[4];
ushort backRight = bufferIndices[5];
bufferIndices[2] = backLeft;
bufferIndices[3] = backRight;
bufferIndices[4] = frontCenter;
bufferIndices[5] = lowFrequency;
}
}
}
}

View File

@ -600,19 +600,42 @@ namespace Ryujinx.Audio.Renderer.Dsp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ResampleForUpsampler(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float ratio, ref float fraction, int sampleCount)
{
// TODO: use a bandwidth filter to have better resampling.
// Currently a simple cubic interpolation, assuming duplicated values at edges.
// TODO: Discover and use algorithm that the switch uses.
int inputBufferIndex = 0;
int maxIndex = inputBuffer.Length - 1;
int cubicEnd = inputBuffer.Length - 3;
for (int i = 0; i < sampleCount; i++)
{
float outputData = inputBuffer[inputBufferIndex];
float s0, s1, s2, s3;
if (fraction > 1.0f)
s1 = inputBuffer[inputBufferIndex];
if (inputBufferIndex == 0 || inputBufferIndex > cubicEnd)
{
outputData = inputBuffer[inputBufferIndex + 1];
// Clamp interplation values at the ends of the input buffer.
s0 = inputBuffer[Math.Max(0, inputBufferIndex - 1)];
s2 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 1)];
s3 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 2)];
}
else
{
s0 = inputBuffer[inputBufferIndex - 1];
s2 = inputBuffer[inputBufferIndex + 1];
s3 = inputBuffer[inputBufferIndex + 2];
}
outputBuffer[i] = outputData;
float a = s3 - s2 - s0 + s1;
float b = s0 - s1 - a;
float c = s2 - s0;
float d = s1;
float f2 = fraction * fraction;
float f3 = f2 * fraction;
outputBuffer[i] = a * f3 + b * f2 + c * fraction + d;
fraction += ratio;
inputBufferIndex += (int)MathF.Truncate(fraction);

View File

@ -17,6 +17,7 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
@ -43,7 +44,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
{
DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax);
DelayLines[i].SetDelay(parameter.DelayTime);
LowPassZ[0] = 0;
}
UpdateParameter(ref parameter);
@ -69,5 +69,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision);
LowPassBaseGain = 1.0f - LowPassFeedbackGain;
}
public void UpdateLowPassFilter(ref float tempRawRef, uint channelCount)
{
for (int i = 0; i < channelCount; i++)
{
float lowPassResult = LowPassFeedbackGain * LowPassZ[i] + Unsafe.Add(ref tempRawRef, i) * LowPassBaseGain;
LowPassZ[i] = lowPassResult;
DelayLines[i].Update(lowPassResult);
}
}
}
}

View File

@ -37,6 +37,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
DectectorAverage.AsSpan().Fill(0.0f);
CompressionGain.AsSpan().Fill(1.0f);
DelayedSampleBufferPosition.AsSpan().Fill(0);
DelayedSampleBuffer.AsSpan().Fill(0.0f);
UpdateParameter(ref parameter);
}

View File

@ -34,7 +34,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
public DecayDelay[] DecayDelays1 { get; }
public DecayDelay[] DecayDelays2 { get; }
public IDelayLine PreDelayLine { get; }
public IDelayLine BackLeftDelayLine { get; }
public IDelayLine FrontCenterDelayLine { get; }
public float DryGain { get; private set; }
public uint[] EarlyDelayTime { get; private set; }
public float PreviousPreDelayValue { get; set; }
@ -69,7 +69,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
}
PreDelayLine = new DelayLine3d(sampleRate, 400);
BackLeftDelayLine = new DelayLine3d(sampleRate, 5);
FrontCenterDelayLine = new DelayLine3d(sampleRate, 5);
UpdateParameter(ref parameter);
}

View File

@ -97,7 +97,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
public DelayLine[] FdnDelayLines { get; }
public DecayDelay[] DecayDelays { get; }
public DelayLine PreDelayLine { get; }
public DelayLine BackLeftDelayLine { get; }
public DelayLine FrontCenterDelayLine { get; }
public uint[] EarlyDelayTime { get; }
public float[] EarlyGain { get; }
public uint PreDelayLineDelayTime { get; private set; }
@ -112,12 +112,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
private ReadOnlySpan<float> GetFdnDelayTimesByLateMode(ReverbLateMode lateMode)
{
return FdnDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
return FdnDelayTimes.AsSpan((int)lateMode * 4, 4);
}
private ReadOnlySpan<float> GetDecayDelayTimesByLateMode(ReverbLateMode lateMode)
{
return DecayDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
return DecayDelayTimes.AsSpan((int)lateMode * 4, 4);
}
public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported)
@ -149,7 +149,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
}
PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax);
BackLeftDelayLine = new DelayLine(sampleRate, 5.0f);
FrontCenterDelayLine = new DelayLine(sampleRate, 5.0f);
UpdateParameter(ref parameter);
}

View File

@ -363,6 +363,9 @@ namespace Ryujinx.Audio.Renderer.Server
case 4:
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount);
break;
case 5:
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion5(_sampleCount, _mixBufferCount);
break;
default:
throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}.");
}

View File

@ -107,10 +107,18 @@ namespace Ryujinx.Audio.Renderer.Server
/// <remarks>This was added in system update 13.0.0</remarks>
public const int Revision10 = 10 << 24;
/// <summary>
/// REV11:
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
/// </summary>
/// <remarks>This was added in system update 14.0.0</remarks>
public const int Revision11 = 11 << 24;
/// <summary>
/// Last revision supported by the implementation.
/// </summary>
public const int LastRevision = Revision10;
public const int LastRevision = Revision11;
/// <summary>
/// Target revision magic supported by the implementation.
@ -366,12 +374,26 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
}
/// <summary>
/// Check if the audio renderer should support new channel resource mapping for 5.1 on Delay, Reverb and Reverb 3D effects.
/// </summary>
/// <returns>True if the audio renderer support new channel resource mapping for 5.1.</returns>
public bool IsNewEffectChannelMappingSupported()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
}
/// <summary>
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
/// </summary>
/// <returns>The version of the <see cref="ICommandProcessingTimeEstimator"/>.</returns>
public int GetCommandProcessingTimeEstimatorVersion()
{
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11))
{
return 5;
}
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10))
{
return 4;

Some files were not shown because too many files have changed in this diff Show More