mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2026-03-13 15:38:40 +00:00
Compare commits
315 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2e4c8f98e | ||
|
|
b53e7ffd46 | ||
|
|
ac66643346 | ||
|
|
21e88f17f6 | ||
|
|
5626f2ca1c | ||
|
|
402f05b8ef | ||
|
|
fb27042e01 | ||
|
|
69a9de33d3 | ||
|
|
bba51c2eeb | ||
|
|
fc26189fe1 | ||
|
|
a40c90e7dd | ||
|
|
f864a49014 | ||
|
|
ecbf303266 | ||
|
|
b3bf05356b | ||
|
|
cb4b58052f | ||
|
|
f8cdd5f484 | ||
|
|
22202be394 | ||
|
|
17ba217940 | ||
|
|
aae4595bdb | ||
|
|
880fd3cfcb | ||
|
|
f679f25e08 | ||
|
|
c2709b3bdd | ||
|
|
2b6e81deea | ||
|
|
7271f1b18e | ||
|
|
5fda543f84 | ||
|
|
95c06de4c1 | ||
|
|
49c63ea077 | ||
|
|
531da8a1c0 | ||
|
|
5cbdfbc7a4 | ||
|
|
e0544dd9c7 | ||
|
|
aa784c3e5e | ||
|
|
9205077590 | ||
|
|
0ed40c7175 | ||
|
|
40d47b7aa2 | ||
|
|
ec0bb74968 | ||
|
|
42f7f98666 | ||
|
|
95bad6995c | ||
|
|
3d42995822 | ||
|
|
9095941fd1 | ||
|
|
ba71141bdc | ||
|
|
0a0675a7f6 | ||
|
|
a7c6e6a8cf | ||
|
|
0bc8151c7e | ||
|
|
40c17673f5 | ||
|
|
a8950d6ac4 | ||
|
|
162798b026 | ||
|
|
1b28ecd63e | ||
|
|
895d9b53bc | ||
|
|
0e06aace45 | ||
|
|
adf4ebcd60 | ||
|
|
470a8031a4 | ||
|
|
5440d4ad5c | ||
|
|
dde208b480 | ||
|
|
4c3d2d5d75 | ||
|
|
fab11ba3f1 | ||
|
|
332891b5ff | ||
|
|
7df4fcada7 | ||
|
|
d6698680be | ||
|
|
e5c9838b0b | ||
|
|
f8ec878796 | ||
|
|
9ff21f9ab6 | ||
|
|
aa021085cf | ||
|
|
1f5d881860 | ||
|
|
1f664100bd | ||
|
|
1f5e1ffa80 | ||
|
|
264438ff19 | ||
|
|
3b8ac1641a | ||
|
|
4250732353 | ||
|
|
4d1579acbf | ||
|
|
6279f5e430 | ||
|
|
b7d2bff6aa | ||
|
|
7c327fecb3 | ||
|
|
cc1a933a2f | ||
|
|
dd574146fb | ||
|
|
2c94ac455e | ||
|
|
e18d258fa0 | ||
|
|
36f10df775 | ||
|
|
680e548022 | ||
|
|
21c4176157 | ||
|
|
3b4ff2d6d9 | ||
|
|
12504f280c | ||
|
|
250fc51374 | ||
|
|
206e0882c2 | ||
|
|
609abc8b9b | ||
|
|
cee7121058 | ||
|
|
cd124bda58 | ||
|
|
9f12e50a54 | ||
|
|
097562bc6c | ||
|
|
db4242c5dc | ||
|
|
4dd77316f7 | ||
|
|
3f98369a17 | ||
|
|
c26aeefe03 | ||
|
|
666e05f5cb | ||
|
|
8d9d508dc7 | ||
|
|
e27f5522e2 | ||
|
|
add2a9d151 | ||
|
|
9e50dd99d7 | ||
|
|
0dec91bb42 | ||
|
|
d9b63353b0 | ||
|
|
eabd0ec93f | ||
|
|
138d5dc64a | ||
|
|
3e68a87d63 | ||
|
|
69b6ef7a4a | ||
|
|
40e87c634e | ||
|
|
79d1c190db | ||
|
|
2bc88467eb | ||
|
|
baf8752e74 | ||
|
|
d5e4378aea | ||
|
|
6dbcdfea47 | ||
|
|
c5258cf082 | ||
|
|
5c89e22bb9 | ||
|
|
11ecff2ff0 | ||
|
|
4c3f09644a | ||
|
|
e187a8870a | ||
|
|
a64fee29dc | ||
|
|
9ef94c8292 | ||
|
|
915d6d044c | ||
|
|
a4780ab33b | ||
|
|
a947a45d81 | ||
|
|
9db73f74cf | ||
|
|
a1efd87c45 | ||
|
|
49be977588 | ||
|
|
c95be55091 | ||
|
|
63dedbda86 | ||
|
|
c532118d94 | ||
|
|
52d6f2e656 | ||
|
|
c9bc4eaf58 | ||
|
|
3249f8ff41 | ||
|
|
1b41b285ac | ||
|
|
f5a6f45b27 | ||
|
|
210557951b | ||
|
|
4c2d9ff3ff | ||
|
|
8198b99935 | ||
|
|
460f96967d | ||
|
|
7ca779a26d | ||
|
|
b5032b3c91 | ||
|
|
f0a3dff136 | ||
|
|
f659dcb9d8 | ||
|
|
a34fb0e939 | ||
|
|
21ce8a9b80 | ||
|
|
9ecbee8032 | ||
|
|
80519af67d | ||
|
|
26e30faff3 | ||
|
|
0992310b76 | ||
|
|
009c1101d2 | ||
|
|
ba95ee54ab | ||
|
|
4ce4299ca2 | ||
|
|
17620d18db | ||
|
|
9f1cf6458c | ||
|
|
67b4e63cff | ||
|
|
c05c688ee8 | ||
|
|
b2623dc27d | ||
|
|
5131b71437 | ||
|
|
7870423671 | ||
|
|
b72916fbc1 | ||
|
|
da073fce61 | ||
|
|
1fc90e57d2 | ||
|
|
eafcc314a9 | ||
|
|
6e9bd4de13 | ||
|
|
05a41b31bc | ||
|
|
eed17f963e | ||
|
|
c09c0c002d | ||
|
|
d56d335c0b | ||
|
|
23c844b2aa | ||
|
|
81691b9e37 | ||
|
|
2dc422bc14 | ||
|
|
a80fa5e33f | ||
|
|
954e995321 | ||
|
|
dad9ab6bb6 | ||
|
|
f0562b9c75 | ||
|
|
b8556530f2 | ||
|
|
4f3af839be | ||
|
|
155736c986 | ||
|
|
dba908dc78 | ||
|
|
ecee34a50c | ||
|
|
9b5a0c3889 | ||
|
|
80b4972139 | ||
|
|
5d85468302 | ||
|
|
9b1cc2cec6 | ||
|
|
e691622f0a | ||
|
|
f663a5cd38 | ||
|
|
f7c2e867f4 | ||
|
|
cedd200745 | ||
|
|
58207685c0 | ||
|
|
095ad923ad | ||
|
|
f07ae7d53f | ||
|
|
c308f09722 | ||
|
|
f1eef29409 | ||
|
|
1f8d66db7c | ||
|
|
c3a5716a95 | ||
|
|
1f1e2a7f03 | ||
|
|
e54f9dc4b4 | ||
|
|
edfd4d70c0 | ||
|
|
fc43aecbbd | ||
|
|
58d7a1fe97 | ||
|
|
7aa430f1a5 | ||
|
|
6bf460e104 | ||
|
|
efb135b74c | ||
|
|
a707842e14 | ||
|
|
a5a9b9bc8b | ||
|
|
17078ad929 | ||
|
|
32450d45de | ||
|
|
ed7a0474c6 | ||
|
|
fe9c49949a | ||
|
|
052b23c83c | ||
|
|
e4f68592c3 | ||
|
|
1dcd44b94f | ||
|
|
61b1ce252f | ||
|
|
5f38086f94 | ||
|
|
7bae440d3a | ||
|
|
f1943fd0b6 | ||
|
|
ec8d4f3af5 | ||
|
|
b3f0978869 | ||
|
|
f614d2c435 | ||
|
|
40c9416097 | ||
|
|
618c8edc79 | ||
|
|
99fc4fa61b | ||
|
|
f6d5499a16 | ||
|
|
26bf13a65d | ||
|
|
96cf242bcf | ||
|
|
59755818ef | ||
|
|
f8beeeb7d3 | ||
|
|
cb250162cb | ||
|
|
7528f94536 | ||
|
|
43081c16c4 | ||
|
|
780627e7b0 | ||
|
|
9044cb38d1 | ||
|
|
a53cfdab78 | ||
|
|
c7f9962dde | ||
|
|
296c4a3d01 | ||
|
|
e7cf4e6eaf | ||
|
|
a1a4771ac1 | ||
|
|
2fd819613f | ||
|
|
ad6ff6ce99 | ||
|
|
dc30d94852 | ||
|
|
4f293f8cbe | ||
|
|
32a1cd83fd | ||
|
|
e3d0ccf8d5 | ||
|
|
c14844d12c | ||
|
|
7fea26e97e | ||
|
|
7b7f62c776 | ||
|
|
423dbc8888 | ||
|
|
6adf15e479 | ||
|
|
2747f12591 | ||
|
|
a47824f961 | ||
|
|
8474d52778 | ||
|
|
dd7a924596 | ||
|
|
a76eaf9a9a | ||
|
|
009e6bcd1b | ||
|
|
eb2cc159fa | ||
|
|
bb89e36fd8 | ||
|
|
de3134adbe | ||
|
|
36d53819a4 | ||
|
|
ae4324032a | ||
|
|
f449895e6d | ||
|
|
410be95ab6 | ||
|
|
cff9046fc7 | ||
|
|
86fd0643c2 | ||
|
|
43a83a401e | ||
|
|
f0e27a23a5 | ||
|
|
e68650237d | ||
|
|
1faff14e73 | ||
|
|
784cf9d594 | ||
|
|
64263c5218 | ||
|
|
065c4e520d | ||
|
|
139a930407 | ||
|
|
719dc97bbd | ||
|
|
41bba5310a | ||
|
|
8071c8c8c0 | ||
|
|
b402b4e7f6 | ||
|
|
93df366b2c | ||
|
|
cd3a15aea5 | ||
|
|
070136b3f7 | ||
|
|
08ab47c6c0 | ||
|
|
85faa9d8fa | ||
|
|
dca5b14493 | ||
|
|
4d2c8e2a44 | ||
|
|
8fa248ceb4 | ||
|
|
30862b5ffd | ||
|
|
9f57747c57 | ||
|
|
fe29a2ff6e | ||
|
|
e9a173e00c | ||
|
|
a11784fcbf | ||
|
|
fd36c8deca | ||
|
|
70638340b3 | ||
|
|
4b495f3333 | ||
|
|
934b5a64e5 | ||
|
|
cee667b491 | ||
|
|
94a64f2aea | ||
|
|
2355c2af62 | ||
|
|
5e0f8e8738 | ||
|
|
d16288a2a8 | ||
|
|
600f86dc7b | ||
|
|
7210c17c5e | ||
|
|
a16854e55a | ||
|
|
3e455a90a1 | ||
|
|
e4413542b2 | ||
|
|
8c720783f5 | ||
|
|
8734ea9dd4 | ||
|
|
c586e6d2b7 | ||
|
|
3a4eeb77fe | ||
|
|
51b3953cfc | ||
|
|
610eecc1c1 | ||
|
|
492056abf6 | ||
|
|
ee6e682ab4 | ||
|
|
6f60e102a2 | ||
|
|
eeb2af9953 | ||
|
|
550747eac6 | ||
|
|
3ffceab1fb | ||
|
|
b9f2a96595 | ||
|
|
cbaa845f5d | ||
|
|
9e2681f2d7 | ||
|
|
81fae0d1a6 | ||
|
|
38519f3b9a | ||
|
|
7f27aabbd0 |
@@ -63,6 +63,10 @@ dotnet_code_quality_unused_parameters = all:suggestion
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# Namespace preferences
|
||||
csharp_style_namespace_declarations = block_scoped:warning
|
||||
resharper_csharp_namespace_body = block_scoped
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
csharp_style_var_for_built_in_types = false:silent
|
||||
|
||||
61
.gitattributes
vendored
61
.gitattributes
vendored
@@ -1,63 +1,4 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
* text=auto eol=lf
|
||||
|
||||
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,43 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Something doesn't work correctly in Ryujinx. Game-specific issues should be posted at https://github.com/Ryujinx/Ryujinx-Games-List instead, unless it is a provable regression.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Bug Report
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ Lines between [ ] (square brackets) should be removed before posting. ]
|
||||
|
||||
### What's the issue you encountered?
|
||||
|
||||
[ Describe the issue in detail and what you were doing beforehand. ]</br>
|
||||
[ Did you make any changes related to Ryujinx itself? ]</br>
|
||||
[ If so, make sure to include details relating to what exactly you changed. ]
|
||||
|
||||
### How can the issue be reproduced?
|
||||
|
||||
[ Include a detailed step by step process for recreating your issue. ]
|
||||
|
||||
### Log file
|
||||
|
||||
[ Logs files can be found under ``Logs`` folder in Ryujinx program folder. ]</br>
|
||||
[ If you don't include a crash report in instances of crash related issues, we will ask you one to provide one. ]
|
||||
|
||||
### Environment?
|
||||
|
||||
- Ryujinx version: 1.0.X</br>
|
||||
[ Replace X's with the Ryujinx version at time of crash. ]
|
||||
- Game version: X.X.X</br>
|
||||
[ Replace X's with the game version at time of crash. ]
|
||||
- System Specs:
|
||||
- OS: *(e.g. Windows 10)*
|
||||
- CPU: *(e.g. i7-6700)*
|
||||
- GPU: *(e.g. NVIDIA RTX 2070)*
|
||||
- RAM: *(e.g. 16GiB)*
|
||||
- Applied Mods : [ Yes (Which ones) / No ]
|
||||
|
||||
### Additional context?
|
||||
|
||||
Additional info about your environment:</br>
|
||||
[ Any other information relevant to your issue. ]
|
||||
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]"
|
||||
labels: bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Description of the issue
|
||||
description: What's the issue you encountered?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: How can the issue be reproduced?
|
||||
placeholder: Describe each step as precisely as possible
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log file
|
||||
description: A log file will help our developers to better diagnose and fix the issue.
|
||||
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. You can drag and drop the log on to the text area
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: OS
|
||||
placeholder: "e.g. Windows 10"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: ryujinx-version
|
||||
attributes:
|
||||
label: Ryujinx version
|
||||
placeholder: "e.g. 1.0.470"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: game-version
|
||||
attributes:
|
||||
label: Game version
|
||||
placeholder: "e.g. 1.1.1"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: cpu
|
||||
attributes:
|
||||
label: CPU
|
||||
placeholder: "e.g. i7-6700"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: gpu
|
||||
attributes:
|
||||
label: GPU
|
||||
placeholder: "e.g. NVIDIA RTX 2070"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: ram
|
||||
attributes:
|
||||
label: RAM
|
||||
placeholder: "e.g. 16GB"
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: mods
|
||||
attributes:
|
||||
label: List of applied mods
|
||||
placeholder: You can list applied mods here.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context?
|
||||
description: |
|
||||
- Additional info about your environment:
|
||||
- Any other information relevant to your issue.
|
||||
validations:
|
||||
required: false
|
||||
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature for Ryujinx.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Feature Request
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ If you do not have the information needed for a section, replace its contents with "Unknown". ]</br>
|
||||
[ Lines between [ ] (square brackets) are to be removed before posting. ]</br>
|
||||
|
||||
[ Please search for existing [feature requests](https://github.com/Ryujinx/Ryujinx/issues) before you make your own request. ]</br>
|
||||
[ Duplicate requests will be marked as such and you will be referred to the original request. ]
|
||||
|
||||
### What feature are you suggesting?
|
||||
#### Overview:
|
||||
- [ Include the basic, high-level concepts for this feature here. ]
|
||||
|
||||
#### Smaller Details:
|
||||
- [ These may include specific methods of implementation etc. ]
|
||||
|
||||
#### Nature of Request:
|
||||
[ Remove all that do not apply to your request. ]
|
||||
- Addition
|
||||
- [ Ex: Addition of certain original features or features from other community projects. ]
|
||||
- [ If you are suggesting porting features or including features from other projects, include what license they are distributed under and what, if any libraries those project use. ]
|
||||
- Change
|
||||
- Removal
|
||||
- [Ex: Removal of certain features or implementation due to a specific issue/bug or because of low quality code, etc.]
|
||||
|
||||
### Why would this feature be useful?
|
||||
[ If this is a feature for an end-user, how does it benefit the end-user? ]</br>
|
||||
[ If this feature is for developers, what does it add to Ryujinx that did not already exist? ]
|
||||
30
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
30
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature for Ryujinx.
|
||||
title: "[Feature Request]"
|
||||
body:
|
||||
- type: textarea
|
||||
id: overview
|
||||
attributes:
|
||||
label: Overview
|
||||
description: Include the basic, high-level concepts for this feature here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: Smaller details
|
||||
description: These may include specific methods of implementation etc.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
label: Nature of request
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: Why would this feature be useful?
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: Missing CPU Instruction
|
||||
about: CPU Instruction is missing in Ryujinx.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Missing CPU Instruction
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ If you do not have the information needed for a section, replace its contents with "Unknown". ]</br>
|
||||
[ Lines between [ ] (square brackets) are to be removed before posting. ]
|
||||
|
||||
[ Please search for existing [missing CPU instruction](https://github.com/Ryujinx/Ryujinx/issues) before you make your own issue. ]</br>
|
||||
[ See the following [issue](https://github.com/Ryujinx/Ryujinx/issues/1405) as an example ]</br>
|
||||
[ Duplicate issue will be marked as such and you will be referred to the original request. ]
|
||||
|
||||
### What CPU instruction is missing?
|
||||
|
||||
Requires the *INSTRUCTION* instruction.</br>
|
||||
[ Replace *INSTRUCTION* by the instruction name, e.g. VADDL.U16 ]
|
||||
|
||||
```
|
||||
*
|
||||
```
|
||||
[ Add the undefined instruction error message in the above code block ]
|
||||
|
||||
### Instruction name
|
||||
```
|
||||
*
|
||||
```
|
||||
[ Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block ]
|
||||
|
||||
### Required by:
|
||||
[ Add our (games list database)[https://github.com/Ryujinx/Ryujinx-Games-List/issues] links of games who require this instruction ]
|
||||
26
.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Missing CPU Instruction
|
||||
description: CPU Instruction is missing in Ryujinx.
|
||||
title: "[CPU]"
|
||||
labels: [cpu, not-implemented]
|
||||
body:
|
||||
- type: textarea
|
||||
id: instruction
|
||||
attributes:
|
||||
label: CPU instruction
|
||||
description: What CPU instruction is missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: name
|
||||
attributes:
|
||||
label: Instruction name
|
||||
description: Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: required
|
||||
attributes:
|
||||
label: Required by
|
||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
|
||||
validations:
|
||||
required: true
|
||||
35
.github/ISSUE_TEMPLATE/missing_service_call.md
vendored
35
.github/ISSUE_TEMPLATE/missing_service_call.md
vendored
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Missing Service Call
|
||||
about: Service call is missing in Ryujinx.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Missing Service Call
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ If you do not have the information needed for a section, replace its contents with "Unknown". ]</br>
|
||||
[ Lines between [ ] (square brackets) are to be removed before posting. ]
|
||||
|
||||
[ Please search for existing [missing service call](https://github.com/Ryujinx/Ryujinx/issues) before you make your own issue. ]</br>
|
||||
[ See the following [issue](https://github.com/Ryujinx/Ryujinx/issues/1431) as an example ]</br>
|
||||
[ Duplicate issue will be marked as such and you will be referred to the original request. ]
|
||||
|
||||
### What service call is missing?
|
||||
|
||||
*SERVICE* *INTERFACE*: *NUMBER* (*NAME*) is not implemented.</br>
|
||||
[ Replace *SERVICE* by the service name, e.g. appletAE ]</br>
|
||||
[ Replace *INTERFACE* by the interface name, e.g. IAllSystemAppletProxiesService ]</br>
|
||||
[ Replace *NUMBER* by the call number, e.g. 100 ]</br>
|
||||
[ Replace *NAME* by the call name, e.g. OpenSystemAppletProxy ]</br>
|
||||
[ e.g. appletAE IAllSystemAppletProxiesService: 100 (OpenSystemAppletProxy) ]
|
||||
|
||||
[ Add related links to the specific call from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) ]
|
||||
|
||||
### Service description
|
||||
```
|
||||
*
|
||||
```
|
||||
[ Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block ]
|
||||
|
||||
### Required by:
|
||||
[ Add our (games list database)[https://github.com/Ryujinx/Ryujinx-Games-List/issues] links of games who require this call ]
|
||||
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
Normal file
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Missing Service Call
|
||||
description: Service call is missing in Ryujinx.
|
||||
labels: not-implemented
|
||||
body:
|
||||
- type: textarea
|
||||
id: instruction
|
||||
attributes:
|
||||
label: Service call
|
||||
description: What service call is missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: name
|
||||
attributes:
|
||||
label: Service description
|
||||
description: Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: required
|
||||
attributes:
|
||||
label: Required by
|
||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this service.
|
||||
validations:
|
||||
required: true
|
||||
98
.github/workflows/build.yml
vendored
98
.github/workflows/build.yml
vendored
@@ -18,10 +18,16 @@ on:
|
||||
- '*.yml'
|
||||
- 'README.md'
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1.0"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.os }} (${{ matrix.configuration }})
|
||||
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 35
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
@@ -33,7 +39,7 @@ jobs:
|
||||
RELEASE_ZIP_OS_NAME: linux_x64
|
||||
|
||||
- os: macOS-latest
|
||||
OS_NAME: MacOS x64
|
||||
OS_NAME: macOS x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: osx-x64
|
||||
RELEASE_ZIP_OS_NAME: osx_x64
|
||||
|
||||
@@ -43,47 +49,107 @@ jobs:
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
|
||||
fail-fast: false
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1.0"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Get git short hash
|
||||
id: git_short_hash
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
|
||||
- name: Test
|
||||
run: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained true
|
||||
if: github.event_name == 'pull_request'
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
|
||||
- name: Publish Ryujinx.Headless.SDL2
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained true
|
||||
if: github.event_name == 'pull_request'
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
|
||||
- name: Publish Ryujinx.Ava
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Ava --self-contained true
|
||||
if: github.event_name == 'pull_request'
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
|
||||
- name: Set executable bit
|
||||
run: |
|
||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
|
||||
chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh
|
||||
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
|
||||
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish_sdl2_headless
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
|
||||
- name: Upload Ryujinx.Ava artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish_ava
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
|
||||
build_macos:
|
||||
name: macOS Universal (${{ matrix.configuration }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 35
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [ Debug, Release ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Setup LLVM 14
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 14
|
||||
|
||||
- name: Install rcodesign
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
|
||||
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
||||
rm apple-codesign.tar.gz
|
||||
mv rcodesign $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get git short hash
|
||||
id: git_short_hash
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish macOS
|
||||
run: |
|
||||
./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
|
||||
- name: Upload Ryujinx.Ava artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish_ava/*.tar.gz"
|
||||
if: github.event_name == 'pull_request'
|
||||
172
.github/workflows/flatpak.yml
vendored
Normal file
172
.github/workflows/flatpak.yml
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
name: Flatpak release job
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ryujinx_version:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
|
||||
concurrency: flatpak-release
|
||||
|
||||
jobs:
|
||||
release:
|
||||
timeout-minutes: 35
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||
GIT_COMMITTER_NAME: "RyujinxBot"
|
||||
GIT_COMMITTER_EMAIL: "61127645+RyujinxBot@users.noreply.github.com"
|
||||
RYUJINX_PROJECT_FILE: "src/Ryujinx/Ryujinx.csproj"
|
||||
NUGET_SOURCES_DESTDIR: "nuget-sources"
|
||||
RYUJINX_VERSION: "${{ inputs.ryujinx_version }}"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: Ryujinx
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
global-json-file: Ryujinx/global.json
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
working-directory: Ryujinx
|
||||
run: |
|
||||
echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: flathub/org.ryujinx.Ryujinx
|
||||
token: ${{ secrets.RYUJINX_BOT_PAT }}
|
||||
submodules: recursive
|
||||
path: flathub
|
||||
|
||||
- name: Install dependencies
|
||||
run: python -m pip install PyYAML lxml
|
||||
|
||||
- name: Restore Nuget packages
|
||||
run: dotnet restore Ryujinx/${{ env.RYUJINX_PROJECT_FILE }}
|
||||
|
||||
- name: Generate nuget_sources.json
|
||||
shell: python
|
||||
run: |
|
||||
from pathlib import Path
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import os
|
||||
|
||||
sources = []
|
||||
|
||||
for path in Path(os.environ['NUGET_PACKAGES']).glob('**/*.nupkg.sha512'):
|
||||
name = path.parent.parent.name
|
||||
version = path.parent.name
|
||||
filename = '{}.{}.nupkg'.format(name, version)
|
||||
url = 'https://api.nuget.org/v3-flatcontainer/{}/{}/{}'.format(name, version, filename)
|
||||
|
||||
with path.open() as fp:
|
||||
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
|
||||
|
||||
sources.append({
|
||||
'type': 'file',
|
||||
'url': url,
|
||||
'sha512': sha512,
|
||||
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
|
||||
'dest-filename': filename,
|
||||
})
|
||||
|
||||
with open('flathub/nuget_sources.json', 'w') as fp:
|
||||
json.dump(sources, fp, indent=4)
|
||||
|
||||
- name: Update flatpak metadata
|
||||
id: metadata
|
||||
env:
|
||||
RYUJINX_GIT_HASH: ${{ steps.version_info.outputs.git_hash }}
|
||||
shell: python
|
||||
run: |
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from lxml import etree
|
||||
|
||||
|
||||
# Ensure we don't destroy multiline strings
|
||||
def str_presenter(dumper, data):
|
||||
if len(data.splitlines()) > 1:
|
||||
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
|
||||
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
|
||||
|
||||
|
||||
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)
|
||||
|
||||
yaml_file = "flathub/org.ryujinx.Ryujinx.yml"
|
||||
xml_file = "flathub/org.ryujinx.Ryujinx.appdata.xml"
|
||||
|
||||
with open(yaml_file, "r") as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
for source in data["modules"][0]["sources"]:
|
||||
if type(source) is str:
|
||||
continue
|
||||
if (
|
||||
source["type"] == "git"
|
||||
and source["url"] == "https://github.com/Ryujinx/Ryujinx.git"
|
||||
):
|
||||
source["commit"] = os.environ['RYUJINX_GIT_HASH']
|
||||
|
||||
is_same_version = data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] == os.environ['RYUJINX_VERSION']
|
||||
|
||||
with open(os.environ['GITHUB_OUTPUT'], "a") as gh_out:
|
||||
if is_same_version:
|
||||
gh_out.write(f"commit_message=Retry update to {os.environ['RYUJINX_VERSION']}")
|
||||
else:
|
||||
gh_out.write(f"commit_message=Update to {os.environ['RYUJINX_VERSION']}")
|
||||
|
||||
if not is_same_version:
|
||||
data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] = os.environ['RYUJINX_VERSION']
|
||||
|
||||
with open(yaml_file, "w") as f:
|
||||
yaml.safe_dump(data, f, sort_keys=False)
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(xml_file, parser)
|
||||
|
||||
root = tree.getroot()
|
||||
|
||||
releases = root.find("releases")
|
||||
|
||||
element = etree.Element("release")
|
||||
element.set("version", os.environ['RYUJINX_VERSION'])
|
||||
element.set("date", datetime.now().date().isoformat())
|
||||
releases.insert(0, element)
|
||||
|
||||
# Ensure 4 spaces
|
||||
etree.indent(root, space=" ")
|
||||
|
||||
with open(xml_file, "wb") as f:
|
||||
f.write(
|
||||
etree.tostring(
|
||||
tree,
|
||||
pretty_print=True,
|
||||
encoding="UTF-8",
|
||||
doctype='<?xml version="1.0" encoding="UTF-8"?>',
|
||||
)
|
||||
)
|
||||
|
||||
- name: Push flatpak update
|
||||
working-directory: flathub
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.metadata.outputs.commit_message }}
|
||||
run: |
|
||||
git config user.name "${{ env.GIT_COMMITTER_NAME }}"
|
||||
git config user.email "${{ env.GIT_COMMITTER_EMAIL }}"
|
||||
git add .
|
||||
git commit -m "$COMMIT_MESSAGE"
|
||||
git push origin master
|
||||
3
.github/workflows/nightly_pr_comment.yml
vendored
3
.github/workflows/nightly_pr_comment.yml
vendored
@@ -7,6 +7,7 @@ jobs:
|
||||
pr_comment:
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 35
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
@@ -65,4 +66,4 @@ jobs:
|
||||
} else {
|
||||
core.info(`Creating a comment`);
|
||||
await github.rest.issues.createComment({repo, owner, issue_number, body});
|
||||
}
|
||||
}
|
||||
174
.github/workflows/release.yml
vendored
174
.github/workflows/release.yml
vendored
@@ -13,76 +13,116 @@ on:
|
||||
|
||||
concurrency: release
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
name: Create tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
|
||||
sha: context.sha
|
||||
})
|
||||
|
||||
release:
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||
name: Release ${{ matrix.OS_NAME }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 35
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
OS_NAME: Linux x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: linux-x64
|
||||
RELEASE_ZIP_OS_NAME: linux_x64
|
||||
|
||||
- os: windows-latest
|
||||
OS_NAME: Windows x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win10-x64
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Create output dir
|
||||
run: "mkdir release_output"
|
||||
- name: Publish Windows
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
|
||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
|
||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
pushd publish_windows
|
||||
pushd publish_gtk
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
||||
popd
|
||||
|
||||
pushd publish_windows_sdl2_headless
|
||||
pushd publish_sdl2_headless
|
||||
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
||||
popd
|
||||
|
||||
pushd publish_windows_ava
|
||||
pushd publish_ava
|
||||
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
- name: Publish Linux
|
||||
run: |
|
||||
dotnet publish -c Release -r linux-x64 -o ./publish_linux/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
|
||||
dotnet publish -c Release -r linux-x64 -o ./publish_linux_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r linux-x64 -o ./publish_linux_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
pushd publish_linux
|
||||
pushd publish_gtk
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
popd
|
||||
|
||||
pushd publish_linux_sdl2_headless
|
||||
pushd publish_sdl2_headless
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
|
||||
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
popd
|
||||
|
||||
pushd publish_linux_ava
|
||||
pushd publish_ava
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava
|
||||
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
popd
|
||||
shell: bash
|
||||
@@ -93,10 +133,78 @@ jobs:
|
||||
name: ${{ steps.version_info.outputs.build_version }}
|
||||
artifacts: "release_output/*.tar.gz,release_output/*.zip"
|
||||
tag: ${{ steps.version_info.outputs.build_version }}
|
||||
body: "For more informations about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
|
||||
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
|
||||
omitBodyDuringUpdate: true
|
||||
allowUpdates: true
|
||||
removeArtifacts: true
|
||||
replacesArtifacts: true
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 35
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Setup LLVM 14
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 14
|
||||
|
||||
- name: Install rcodesign
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
|
||||
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
||||
rm apple-codesign.tar.gz
|
||||
mv rcodesign $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Publish macOS
|
||||
run: |
|
||||
./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
||||
|
||||
- name: Pushing new release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: ${{ steps.version_info.outputs.build_version }}
|
||||
artifacts: "publish_ava/*.tar.gz"
|
||||
tag: ${{ steps.version_info.outputs.build_version }}
|
||||
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
|
||||
omitBodyDuringUpdate: true
|
||||
allowUpdates: true
|
||||
replacesArtifacts: true
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
flatpak_release:
|
||||
uses: ./.github/workflows/flatpak.yml
|
||||
needs: release
|
||||
with:
|
||||
ryujinx_version: "1.1.${{ github.run_number }}"
|
||||
secrets: inherit
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -170,3 +170,6 @@ launchSettings.json
|
||||
|
||||
# NetCore Publishing Profiles
|
||||
PublishProfiles/
|
||||
|
||||
# Glade backup files
|
||||
*.glade~
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,56 +0,0 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.CodeGen.Unwinding;
|
||||
using ARMeilleure.Translation.Cache;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ARMeilleure.CodeGen
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a compiled function.
|
||||
/// </summary>
|
||||
readonly struct CompiledFunction
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the machine code of the <see cref="CompiledFunction"/>.
|
||||
/// </summary>
|
||||
public byte[] Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Unwinding.UnwindInfo"/> of the <see cref="CompiledFunction"/>.
|
||||
/// </summary>
|
||||
public UnwindInfo UnwindInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Linking.RelocInfo"/> of the <see cref="CompiledFunction"/>.
|
||||
/// </summary>
|
||||
public RelocInfo RelocInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CompiledFunction"/> struct with the specified machine code,
|
||||
/// unwind info and relocation info.
|
||||
/// </summary>
|
||||
/// <param name="code">Machine code</param>
|
||||
/// <param name="unwindInfo">Unwind info</param>
|
||||
/// <param name="relocInfo">Relocation info</param>
|
||||
internal CompiledFunction(byte[] code, UnwindInfo unwindInfo, RelocInfo relocInfo)
|
||||
{
|
||||
Code = code;
|
||||
UnwindInfo = unwindInfo;
|
||||
RelocInfo = relocInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the <see cref="CompiledFunction"/> onto the <see cref="JitCache"/> and returns a delegate of type
|
||||
/// <typeparamref name="T"/> pointing to the mapped function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of delegate</typeparam>
|
||||
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
|
||||
public T Map<T>()
|
||||
{
|
||||
IntPtr codePtr = JitCache.Map(this);
|
||||
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(codePtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using System;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Optimizations
|
||||
{
|
||||
static class ConstantFolding
|
||||
{
|
||||
public static void RunPass(Operation operation)
|
||||
{
|
||||
if (operation.Destination == default || operation.SourcesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AreAllSourcesConstant(operation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OperandType type = operation.Destination.Type;
|
||||
|
||||
switch (operation.Instruction)
|
||||
{
|
||||
case Instruction.Add:
|
||||
if (operation.GetSource(0).Relocatable ||
|
||||
operation.GetSource(1).Relocatable)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => x + y);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => x + y);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.BitwiseAnd:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => x & y);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => x & y);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.BitwiseExclusiveOr:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => x ^ y);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => x ^ y);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.BitwiseNot:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => ~x);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateUnaryI64(operation, (x) => ~x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.BitwiseOr:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => x | y);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => x | y);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.ConvertI64ToI32:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Copy:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => x);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateUnaryI64(operation, (x) => x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Divide:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => y != 0 ? x / y : 0);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => y != 0 ? x / y : 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.DivideUI:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => y != 0 ? (int)((uint)x / (uint)y) : 0);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => y != 0 ? (long)((ulong)x / (ulong)y) : 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Multiply:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => x * y);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => x * y);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Negate:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => -x);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateUnaryI64(operation, (x) => -x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.ShiftLeft:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => x << y);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => x << (int)y);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.ShiftRightSI:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => x >> y);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => x >> (int)y);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.ShiftRightUI:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => (int)((uint)x >> y));
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => (long)((ulong)x >> (int)y));
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.SignExtend16:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => (short)x);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateUnaryI64(operation, (x) => (short)x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.SignExtend32:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => x);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateUnaryI64(operation, (x) => (int)x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.SignExtend8:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => (sbyte)x);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateUnaryI64(operation, (x) => (sbyte)x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.ZeroExtend16:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => (ushort)x);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateUnaryI64(operation, (x) => (ushort)x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.ZeroExtend32:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => x);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateUnaryI64(operation, (x) => (uint)x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.ZeroExtend8:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateUnaryI32(operation, (x) => (byte)x);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateUnaryI64(operation, (x) => (byte)x);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Subtract:
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
EvaluateBinaryI32(operation, (x, y) => x - y);
|
||||
}
|
||||
else if (type == OperandType.I64)
|
||||
{
|
||||
EvaluateBinaryI64(operation, (x, y) => x - y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AreAllSourcesConstant(Operation operation)
|
||||
{
|
||||
for (int index = 0; index < operation.SourcesCount; index++)
|
||||
{
|
||||
Operand srcOp = operation.GetSource(index);
|
||||
|
||||
if (srcOp.Kind != OperandKind.Constant)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void EvaluateUnaryI32(Operation operation, Func<int, int> op)
|
||||
{
|
||||
int x = operation.GetSource(0).AsInt32();
|
||||
|
||||
operation.TurnIntoCopy(Const(op(x)));
|
||||
}
|
||||
|
||||
private static void EvaluateUnaryI64(Operation operation, Func<long, long> op)
|
||||
{
|
||||
long x = operation.GetSource(0).AsInt64();
|
||||
|
||||
operation.TurnIntoCopy(Const(op(x)));
|
||||
}
|
||||
|
||||
private static void EvaluateBinaryI32(Operation operation, Func<int, int, int> op)
|
||||
{
|
||||
int x = operation.GetSource(0).AsInt32();
|
||||
int y = operation.GetSource(1).AsInt32();
|
||||
|
||||
operation.TurnIntoCopy(Const(op(x, y)));
|
||||
}
|
||||
|
||||
private static void EvaluateBinaryI64(Operation operation, Func<long, long, long> op)
|
||||
{
|
||||
long x = operation.GetSource(0).AsInt64();
|
||||
long y = operation.GetSource(1).AsInt64();
|
||||
|
||||
operation.TurnIntoCopy(Const(op(x, y)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Optimizations
|
||||
{
|
||||
static class Optimizer
|
||||
{
|
||||
public static void RunPass(ControlFlowGraph cfg)
|
||||
{
|
||||
// Scratch buffer used to store uses.
|
||||
Span<Operation> buffer = default;
|
||||
|
||||
bool modified;
|
||||
|
||||
do
|
||||
{
|
||||
modified = false;
|
||||
|
||||
for (BasicBlock block = cfg.Blocks.Last; block != null; block = block.ListPrevious)
|
||||
{
|
||||
Operation node;
|
||||
Operation prevNode;
|
||||
|
||||
for (node = block.Operations.Last; node != default; node = prevNode)
|
||||
{
|
||||
prevNode = node.ListPrevious;
|
||||
|
||||
if (IsUnused(node))
|
||||
{
|
||||
RemoveNode(block, node);
|
||||
|
||||
modified = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (node.Instruction == Instruction.Phi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ConstantFolding.RunPass(node);
|
||||
Simplification.RunPass(node);
|
||||
|
||||
if (DestIsLocalVar(node))
|
||||
{
|
||||
if (IsPropagableCompare(node))
|
||||
{
|
||||
modified |= PropagateCompare(ref buffer, node);
|
||||
|
||||
if (modified && IsUnused(node))
|
||||
{
|
||||
RemoveNode(block, node);
|
||||
}
|
||||
}
|
||||
else if (IsPropagableCopy(node))
|
||||
{
|
||||
PropagateCopy(ref buffer, node);
|
||||
|
||||
RemoveNode(block, node);
|
||||
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (modified);
|
||||
}
|
||||
|
||||
public static void RemoveUnusedNodes(ControlFlowGraph cfg)
|
||||
{
|
||||
bool modified;
|
||||
|
||||
do
|
||||
{
|
||||
modified = false;
|
||||
|
||||
for (BasicBlock block = cfg.Blocks.Last; block != null; block = block.ListPrevious)
|
||||
{
|
||||
Operation node;
|
||||
Operation prevNode;
|
||||
|
||||
for (node = block.Operations.Last; node != default; node = prevNode)
|
||||
{
|
||||
prevNode = node.ListPrevious;
|
||||
|
||||
if (IsUnused(node))
|
||||
{
|
||||
RemoveNode(block, node);
|
||||
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (modified);
|
||||
}
|
||||
|
||||
private static Span<Operation> GetUses(ref Span<Operation> buffer, Operand operand)
|
||||
{
|
||||
ReadOnlySpan<Operation> uses = operand.Uses;
|
||||
|
||||
if (buffer.Length < uses.Length)
|
||||
{
|
||||
buffer = Allocators.Default.AllocateSpan<Operation>((uint)uses.Length);
|
||||
}
|
||||
|
||||
uses.CopyTo(buffer);
|
||||
|
||||
return buffer.Slice(0, uses.Length);
|
||||
}
|
||||
|
||||
private static bool PropagateCompare(ref Span<Operation> buffer, Operation compOp)
|
||||
{
|
||||
// Try to propagate Compare operations into their BranchIf uses, when these BranchIf uses are in the form
|
||||
// of:
|
||||
//
|
||||
// - BranchIf %x, 0x0, Equal ;; i.e BranchIfFalse %x
|
||||
// - BranchIf %x, 0x0, NotEqual ;; i.e BranchIfTrue %x
|
||||
//
|
||||
// The commutative property of Equal and NotEqual is taken into consideration as well.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// %x = Compare %a, %b, comp
|
||||
// BranchIf %x, 0x0, NotEqual
|
||||
//
|
||||
// =>
|
||||
//
|
||||
// BranchIf %a, %b, comp
|
||||
|
||||
static bool IsZeroBranch(Operation operation, out Comparison compType)
|
||||
{
|
||||
compType = Comparison.Equal;
|
||||
|
||||
if (operation.Instruction != Instruction.BranchIf)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand src1 = operation.GetSource(0);
|
||||
Operand src2 = operation.GetSource(1);
|
||||
Operand comp = operation.GetSource(2);
|
||||
|
||||
compType = (Comparison)comp.AsInt32();
|
||||
|
||||
return (src1.Kind == OperandKind.Constant && src1.Value == 0) ||
|
||||
(src2.Kind == OperandKind.Constant && src2.Value == 0);
|
||||
}
|
||||
|
||||
bool modified = false;
|
||||
|
||||
Operand dest = compOp.Destination;
|
||||
Operand src1 = compOp.GetSource(0);
|
||||
Operand src2 = compOp.GetSource(1);
|
||||
Operand comp = compOp.GetSource(2);
|
||||
|
||||
Comparison compType = (Comparison)comp.AsInt32();
|
||||
|
||||
Span<Operation> uses = GetUses(ref buffer, dest);
|
||||
|
||||
foreach (Operation use in uses)
|
||||
{
|
||||
// If operation is a BranchIf and has a constant value 0 in its RHS or LHS source operands.
|
||||
if (IsZeroBranch(use, out Comparison otherCompType))
|
||||
{
|
||||
Comparison propCompType;
|
||||
|
||||
if (otherCompType == Comparison.NotEqual)
|
||||
{
|
||||
propCompType = compType;
|
||||
}
|
||||
else if (otherCompType == Comparison.Equal)
|
||||
{
|
||||
propCompType = compType.Invert();
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
use.SetSource(0, src1);
|
||||
use.SetSource(1, src2);
|
||||
use.SetSource(2, Const((int)propCompType));
|
||||
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
private static void PropagateCopy(ref Span<Operation> buffer, Operation copyOp)
|
||||
{
|
||||
// Propagate copy source operand to all uses of the destination operand.
|
||||
Operand dest = copyOp.Destination;
|
||||
Operand source = copyOp.GetSource(0);
|
||||
|
||||
Span<Operation> uses = GetUses(ref buffer, dest);
|
||||
|
||||
foreach (Operation use in uses)
|
||||
{
|
||||
for (int index = 0; index < use.SourcesCount; index++)
|
||||
{
|
||||
if (use.GetSource(index) == dest)
|
||||
{
|
||||
use.SetSource(index, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveNode(BasicBlock block, Operation node)
|
||||
{
|
||||
// Remove a node from the nodes list, and also remove itself
|
||||
// from all the use lists on the operands that this node uses.
|
||||
block.Operations.Remove(node);
|
||||
|
||||
for (int index = 0; index < node.SourcesCount; index++)
|
||||
{
|
||||
node.SetSource(index, default);
|
||||
}
|
||||
|
||||
Debug.Assert(node.Destination == default || node.Destination.UsesCount == 0);
|
||||
|
||||
node.Destination = default;
|
||||
}
|
||||
|
||||
private static bool IsUnused(Operation node)
|
||||
{
|
||||
return DestIsLocalVar(node) && node.Destination.UsesCount == 0 && !HasSideEffects(node);
|
||||
}
|
||||
|
||||
private static bool DestIsLocalVar(Operation node)
|
||||
{
|
||||
return node.Destination != default && node.Destination.Kind == OperandKind.LocalVariable;
|
||||
}
|
||||
|
||||
private static bool HasSideEffects(Operation node)
|
||||
{
|
||||
return node.Instruction == Instruction.Call
|
||||
|| node.Instruction == Instruction.Tailcall
|
||||
|| node.Instruction == Instruction.CompareAndSwap
|
||||
|| node.Instruction == Instruction.CompareAndSwap16
|
||||
|| node.Instruction == Instruction.CompareAndSwap8;
|
||||
}
|
||||
|
||||
private static bool IsPropagableCompare(Operation operation)
|
||||
{
|
||||
return operation.Instruction == Instruction.Compare;
|
||||
}
|
||||
|
||||
private static bool IsPropagableCopy(Operation operation)
|
||||
{
|
||||
if (operation.Instruction != Instruction.Copy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return operation.Destination.Type == operation.GetSource(0).Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,394 +0,0 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
unsafe readonly struct LiveInterval : IComparable<LiveInterval>
|
||||
{
|
||||
public const int NotFound = -1;
|
||||
|
||||
private struct Data
|
||||
{
|
||||
public int End;
|
||||
public int SpillOffset;
|
||||
|
||||
public LiveRange FirstRange;
|
||||
public LiveRange PrevRange;
|
||||
public LiveRange CurrRange;
|
||||
|
||||
public LiveInterval Parent;
|
||||
|
||||
public UseList Uses;
|
||||
public LiveIntervalList Children;
|
||||
|
||||
public Operand Local;
|
||||
public Register Register;
|
||||
|
||||
public bool IsFixed;
|
||||
}
|
||||
|
||||
private readonly Data* _data;
|
||||
|
||||
private ref int End => ref _data->End;
|
||||
private ref LiveRange FirstRange => ref _data->FirstRange;
|
||||
private ref LiveRange CurrRange => ref _data->CurrRange;
|
||||
private ref LiveRange PrevRange => ref _data->PrevRange;
|
||||
private ref LiveInterval Parent => ref _data->Parent;
|
||||
private ref UseList Uses => ref _data->Uses;
|
||||
private ref LiveIntervalList Children => ref _data->Children;
|
||||
|
||||
public Operand Local => _data->Local;
|
||||
public ref Register Register => ref _data->Register;
|
||||
public ref int SpillOffset => ref _data->SpillOffset;
|
||||
|
||||
public bool IsFixed => _data->IsFixed;
|
||||
public bool IsEmpty => FirstRange == default;
|
||||
public bool IsSplit => Children.Count != 0;
|
||||
public bool IsSpilled => SpillOffset != -1;
|
||||
|
||||
public int UsesCount => Uses.Count;
|
||||
|
||||
public LiveInterval(Operand local = default, LiveInterval parent = default)
|
||||
{
|
||||
_data = Allocators.LiveIntervals.Allocate<Data>();
|
||||
*_data = default;
|
||||
|
||||
_data->IsFixed = false;
|
||||
_data->Local = local;
|
||||
|
||||
Parent = parent == default ? this : parent;
|
||||
Uses = new UseList();
|
||||
Children = new LiveIntervalList();
|
||||
|
||||
FirstRange = default;
|
||||
CurrRange = default;
|
||||
PrevRange = default;
|
||||
|
||||
SpillOffset = -1;
|
||||
}
|
||||
|
||||
public LiveInterval(Register register) : this(local: default, parent: default)
|
||||
{
|
||||
_data->IsFixed = true;
|
||||
|
||||
Register = register;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
PrevRange = default;
|
||||
CurrRange = FirstRange;
|
||||
}
|
||||
|
||||
public void Forward(int position)
|
||||
{
|
||||
LiveRange prev = PrevRange;
|
||||
LiveRange curr = CurrRange;
|
||||
|
||||
while (curr != default && curr.Start < position && !curr.Overlaps(position))
|
||||
{
|
||||
prev = curr;
|
||||
curr = curr.Next;
|
||||
}
|
||||
|
||||
PrevRange = prev;
|
||||
CurrRange = curr;
|
||||
}
|
||||
|
||||
public int GetStart()
|
||||
{
|
||||
Debug.Assert(!IsEmpty, "Empty LiveInterval cannot have a start position.");
|
||||
|
||||
return FirstRange.Start;
|
||||
}
|
||||
|
||||
public void SetStart(int position)
|
||||
{
|
||||
if (FirstRange != default)
|
||||
{
|
||||
Debug.Assert(position != FirstRange.End);
|
||||
|
||||
FirstRange.Start = position;
|
||||
}
|
||||
else
|
||||
{
|
||||
FirstRange = new LiveRange(position, position + 1);
|
||||
End = position + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetEnd()
|
||||
{
|
||||
Debug.Assert(!IsEmpty, "Empty LiveInterval cannot have an end position.");
|
||||
|
||||
return End;
|
||||
}
|
||||
|
||||
public void AddRange(int start, int end)
|
||||
{
|
||||
Debug.Assert(start < end, $"Invalid range start position {start}, {end}");
|
||||
|
||||
if (FirstRange != default)
|
||||
{
|
||||
// If the new range ends exactly where the first range start, then coalesce together.
|
||||
if (end == FirstRange.Start)
|
||||
{
|
||||
FirstRange.Start = start;
|
||||
|
||||
return;
|
||||
}
|
||||
// If the new range is already contained, then coalesce together.
|
||||
else if (FirstRange.Overlaps(start, end))
|
||||
{
|
||||
FirstRange.Start = Math.Min(FirstRange.Start, start);
|
||||
FirstRange.End = Math.Max(FirstRange.End, end);
|
||||
End = Math.Max(End, end);
|
||||
|
||||
Debug.Assert(FirstRange.Next == default || !FirstRange.Overlaps(FirstRange.Next));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FirstRange = new LiveRange(start, end, FirstRange);
|
||||
End = Math.Max(End, end);
|
||||
|
||||
Debug.Assert(FirstRange.Next == default || !FirstRange.Overlaps(FirstRange.Next));
|
||||
}
|
||||
|
||||
public void AddUsePosition(int position)
|
||||
{
|
||||
Uses.Add(position);
|
||||
}
|
||||
|
||||
public bool Overlaps(int position)
|
||||
{
|
||||
LiveRange curr = CurrRange;
|
||||
|
||||
while (curr != default && curr.Start <= position)
|
||||
{
|
||||
if (curr.Overlaps(position))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
curr = curr.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Overlaps(LiveInterval other)
|
||||
{
|
||||
return GetOverlapPosition(other) != NotFound;
|
||||
}
|
||||
|
||||
public int GetOverlapPosition(LiveInterval other)
|
||||
{
|
||||
LiveRange a = CurrRange;
|
||||
LiveRange b = other.CurrRange;
|
||||
|
||||
while (a != default)
|
||||
{
|
||||
while (b != default && b.Start < a.Start)
|
||||
{
|
||||
if (a.Overlaps(b))
|
||||
{
|
||||
return a.Start;
|
||||
}
|
||||
|
||||
b = b.Next;
|
||||
}
|
||||
|
||||
if (b == default)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (a.Overlaps(b))
|
||||
{
|
||||
return a.Start;
|
||||
}
|
||||
|
||||
a = a.Next;
|
||||
}
|
||||
|
||||
return NotFound;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<LiveInterval> SplitChildren()
|
||||
{
|
||||
return Parent.Children.Span;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<int> UsePositions()
|
||||
{
|
||||
return Uses.Span;
|
||||
}
|
||||
|
||||
public int FirstUse()
|
||||
{
|
||||
return Uses.FirstUse;
|
||||
}
|
||||
|
||||
public int NextUseAfter(int position)
|
||||
{
|
||||
return Uses.NextUse(position);
|
||||
}
|
||||
|
||||
public LiveInterval Split(int position)
|
||||
{
|
||||
LiveInterval result = new(Local, Parent);
|
||||
result.End = End;
|
||||
|
||||
LiveRange prev = PrevRange;
|
||||
LiveRange curr = CurrRange;
|
||||
|
||||
while (curr != default && curr.Start < position && !curr.Overlaps(position))
|
||||
{
|
||||
prev = curr;
|
||||
curr = curr.Next;
|
||||
}
|
||||
|
||||
if (curr.Start >= position)
|
||||
{
|
||||
prev.Next = default;
|
||||
|
||||
result.FirstRange = curr;
|
||||
|
||||
End = prev.End;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.FirstRange = new LiveRange(position, curr.End, curr.Next);
|
||||
|
||||
curr.End = position;
|
||||
curr.Next = default;
|
||||
|
||||
End = curr.End;
|
||||
}
|
||||
|
||||
result.Uses = Uses.Split(position);
|
||||
|
||||
AddSplitChild(result);
|
||||
|
||||
Debug.Assert(!IsEmpty, "Left interval is empty after split.");
|
||||
Debug.Assert(!result.IsEmpty, "Right interval is empty after split.");
|
||||
|
||||
// Make sure the iterator in the new split is pointing to the start.
|
||||
result.Reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void AddSplitChild(LiveInterval child)
|
||||
{
|
||||
Debug.Assert(!child.IsEmpty, "Trying to insert an empty interval.");
|
||||
|
||||
Parent.Children.Add(child);
|
||||
}
|
||||
|
||||
public LiveInterval GetSplitChild(int position)
|
||||
{
|
||||
if (Overlaps(position))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
foreach (LiveInterval splitChild in SplitChildren())
|
||||
{
|
||||
if (splitChild.Overlaps(position))
|
||||
{
|
||||
return splitChild;
|
||||
}
|
||||
else if (splitChild.GetStart() > position)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool TrySpillWithSiblingOffset()
|
||||
{
|
||||
foreach (LiveInterval splitChild in SplitChildren())
|
||||
{
|
||||
if (splitChild.IsSpilled)
|
||||
{
|
||||
Spill(splitChild.SpillOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Spill(int offset)
|
||||
{
|
||||
SpillOffset = offset;
|
||||
}
|
||||
|
||||
public int CompareTo(LiveInterval interval)
|
||||
{
|
||||
if (FirstRange == default || interval.FirstRange == default)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return GetStart().CompareTo(interval.GetStart());
|
||||
}
|
||||
|
||||
public bool Equals(LiveInterval interval)
|
||||
{
|
||||
return interval._data == _data;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is LiveInterval interval && Equals(interval);
|
||||
}
|
||||
|
||||
public static bool operator ==(LiveInterval a, LiveInterval b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(LiveInterval a, LiveInterval b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine((IntPtr)_data);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
LiveInterval self = this;
|
||||
|
||||
IEnumerable<string> GetRanges()
|
||||
{
|
||||
LiveRange curr = self.CurrRange;
|
||||
|
||||
while (curr != default)
|
||||
{
|
||||
if (curr == self.CurrRange)
|
||||
{
|
||||
yield return "*" + curr;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return curr.ToString();
|
||||
}
|
||||
|
||||
curr = curr.Next;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(", ", GetRanges());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
readonly struct RegisterMasks
|
||||
{
|
||||
public int IntAvailableRegisters { get; }
|
||||
public int VecAvailableRegisters { get; }
|
||||
public int IntCallerSavedRegisters { get; }
|
||||
public int VecCallerSavedRegisters { get; }
|
||||
public int IntCalleeSavedRegisters { get; }
|
||||
public int VecCalleeSavedRegisters { get; }
|
||||
|
||||
public RegisterMasks(
|
||||
int intAvailableRegisters,
|
||||
int vecAvailableRegisters,
|
||||
int intCallerSavedRegisters,
|
||||
int vecCallerSavedRegisters,
|
||||
int intCalleeSavedRegisters,
|
||||
int vecCalleeSavedRegisters)
|
||||
{
|
||||
IntAvailableRegisters = intAvailableRegisters;
|
||||
VecAvailableRegisters = vecAvailableRegisters;
|
||||
IntCallerSavedRegisters = intCallerSavedRegisters;
|
||||
VecCallerSavedRegisters = vecCallerSavedRegisters;
|
||||
IntCalleeSavedRegisters = intCalleeSavedRegisters;
|
||||
VecCalleeSavedRegisters = vecCalleeSavedRegisters;
|
||||
}
|
||||
|
||||
public int GetAvailableRegisters(RegisterType type)
|
||||
{
|
||||
if (type == RegisterType.Integer)
|
||||
{
|
||||
return IntAvailableRegisters;
|
||||
}
|
||||
else if (type == RegisterType.Vector)
|
||||
{
|
||||
return VecAvailableRegisters;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Invalid register type \"{type}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,291 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
partial class Assembler
|
||||
{
|
||||
public static bool SupportsVexPrefix(X86Instruction inst)
|
||||
{
|
||||
return _instTable[(int)inst].Flags.HasFlag(InstructionFlags.Vex);
|
||||
}
|
||||
|
||||
private const int BadOp = 0;
|
||||
|
||||
[Flags]
|
||||
private enum InstructionFlags
|
||||
{
|
||||
None = 0,
|
||||
RegOnly = 1 << 0,
|
||||
Reg8Src = 1 << 1,
|
||||
Reg8Dest = 1 << 2,
|
||||
RexW = 1 << 3,
|
||||
Vex = 1 << 4,
|
||||
|
||||
PrefixBit = 16,
|
||||
PrefixMask = 7 << PrefixBit,
|
||||
Prefix66 = 1 << PrefixBit,
|
||||
PrefixF3 = 2 << PrefixBit,
|
||||
PrefixF2 = 4 << PrefixBit
|
||||
}
|
||||
|
||||
private readonly struct InstructionInfo
|
||||
{
|
||||
public int OpRMR { get; }
|
||||
public int OpRMImm8 { get; }
|
||||
public int OpRMImm32 { get; }
|
||||
public int OpRImm64 { get; }
|
||||
public int OpRRM { get; }
|
||||
|
||||
public InstructionFlags Flags { get; }
|
||||
|
||||
public InstructionInfo(
|
||||
int opRMR,
|
||||
int opRMImm8,
|
||||
int opRMImm32,
|
||||
int opRImm64,
|
||||
int opRRM,
|
||||
InstructionFlags flags)
|
||||
{
|
||||
OpRMR = opRMR;
|
||||
OpRMImm8 = opRMImm8;
|
||||
OpRMImm32 = opRMImm32;
|
||||
OpRImm64 = opRImm64;
|
||||
OpRRM = opRRM;
|
||||
Flags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static InstructionInfo[] _instTable;
|
||||
|
||||
static Assembler()
|
||||
{
|
||||
_instTable = new InstructionInfo[(int)X86Instruction.Count];
|
||||
|
||||
// Name RM/R RM/I8 RM/I32 R/I64 R/RM Flags
|
||||
Add(X86Instruction.Add, new InstructionInfo(0x00000001, 0x00000083, 0x00000081, BadOp, 0x00000003, InstructionFlags.None));
|
||||
Add(X86Instruction.Addpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Addps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Addsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Addss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Aesdec, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38de, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Aesdeclast, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38df, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Aesenc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38dc, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Aesenclast, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38dd, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Aesimc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38db, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.And, new InstructionInfo(0x00000021, 0x04000083, 0x04000081, BadOp, 0x00000023, InstructionFlags.None));
|
||||
Add(X86Instruction.Andnpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f55, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Andnps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f55, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Andpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f54, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Andps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f54, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Blendvpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3815, InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Blendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3814, InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Bsr, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbd, InstructionFlags.None));
|
||||
Add(X86Instruction.Bswap, new InstructionInfo(0x00000fc8, BadOp, BadOp, BadOp, BadOp, InstructionFlags.RegOnly));
|
||||
Add(X86Instruction.Call, new InstructionInfo(0x020000ff, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Cmovcc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f40, InstructionFlags.None));
|
||||
Add(X86Instruction.Cmp, new InstructionInfo(0x00000039, 0x07000083, 0x07000081, BadOp, 0x0000003b, InstructionFlags.None));
|
||||
Add(X86Instruction.Cmppd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Cmpps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Cmpsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Cmpss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Cmpxchg, new InstructionInfo(0x00000fb1, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Cmpxchg16b, new InstructionInfo(0x01000fc7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.RexW));
|
||||
Add(X86Instruction.Cmpxchg8, new InstructionInfo(0x00000fb0, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Reg8Src));
|
||||
Add(X86Instruction.Comisd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2f, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Comiss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2f, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Crc32, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38f1, InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Crc32_16, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38f1, InstructionFlags.PrefixF2 | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Crc32_8, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38f0, InstructionFlags.PrefixF2 | InstructionFlags.Reg8Src));
|
||||
Add(X86Instruction.Cvtdq2pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe6, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Cvtdq2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5b, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Cvtpd2dq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe6, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Cvtpd2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Cvtps2dq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5b, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Cvtps2pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Cvtsd2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2d, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Cvtsd2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Cvtsi2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Cvtsi2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Cvtss2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Cvtss2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2d, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Div, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x060000f7, InstructionFlags.None));
|
||||
Add(X86Instruction.Divpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Divps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Divsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Divss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Gf2p8affineqb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3ace, InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Haddpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f7c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Haddps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f7c, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Idiv, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x070000f7, InstructionFlags.None));
|
||||
Add(X86Instruction.Imul, new InstructionInfo(BadOp, 0x0000006b, 0x00000069, BadOp, 0x00000faf, InstructionFlags.None));
|
||||
Add(X86Instruction.Imul128, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x050000f7, InstructionFlags.None));
|
||||
Add(X86Instruction.Insertps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a21, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Jmp, new InstructionInfo(0x040000ff, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Ldmxcsr, new InstructionInfo(0x02000fae, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Lea, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x0000008d, InstructionFlags.None));
|
||||
Add(X86Instruction.Maxpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Maxps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Maxsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Maxss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Minpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Minps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Minsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Minss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Mov, new InstructionInfo(0x00000089, BadOp, 0x000000c7, 0x000000b8, 0x0000008b, InstructionFlags.None));
|
||||
Add(X86Instruction.Mov16, new InstructionInfo(0x00000089, BadOp, 0x000000c7, BadOp, 0x0000008b, InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Mov8, new InstructionInfo(0x00000088, 0x000000c6, BadOp, BadOp, 0x0000008a, InstructionFlags.Reg8Src | InstructionFlags.Reg8Dest));
|
||||
Add(X86Instruction.Movd, new InstructionInfo(0x00000f7e, BadOp, BadOp, BadOp, 0x00000f6e, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Movdqu, new InstructionInfo(0x00000f7f, BadOp, BadOp, BadOp, 0x00000f6f, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Movhlps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f12, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Movlhps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f16, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Movq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f7e, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Movsd, new InstructionInfo(0x00000f11, BadOp, BadOp, BadOp, 0x00000f10, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Movss, new InstructionInfo(0x00000f11, BadOp, BadOp, BadOp, 0x00000f10, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Movsx16, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbf, InstructionFlags.None));
|
||||
Add(X86Instruction.Movsx32, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000063, InstructionFlags.None));
|
||||
Add(X86Instruction.Movsx8, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbe, InstructionFlags.Reg8Src));
|
||||
Add(X86Instruction.Movzx16, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fb7, InstructionFlags.None));
|
||||
Add(X86Instruction.Movzx8, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fb6, InstructionFlags.Reg8Src));
|
||||
Add(X86Instruction.Mul128, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x040000f7, InstructionFlags.None));
|
||||
Add(X86Instruction.Mulpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Mulps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Mulsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Mulss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Neg, new InstructionInfo(0x030000f7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Not, new InstructionInfo(0x020000f7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Or, new InstructionInfo(0x00000009, 0x01000083, 0x01000081, BadOp, 0x0000000b, InstructionFlags.None));
|
||||
Add(X86Instruction.Paddb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffc, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Paddd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffe, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Paddq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fd4, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Paddw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffd, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Palignr, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a0f, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pand, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fdb, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pandn, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fdf, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pavgb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe0, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pavgw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe3, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3810, InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pclmulqdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a44, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pcmpeqb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f74, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pcmpeqd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f76, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pcmpeqq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3829, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pcmpeqw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f75, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pcmpgtb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f64, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pcmpgtd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f66, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pcmpgtq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3837, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pcmpgtw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f65, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pextrb, new InstructionInfo(0x000f3a14, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pextrd, new InstructionInfo(0x000f3a16, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pextrq, new InstructionInfo(0x000f3a16, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.RexW | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pextrw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc5, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pinsrb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a20, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pinsrd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a22, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pinsrq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a22, InstructionFlags.Vex | InstructionFlags.RexW | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pinsrw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc4, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmaxsb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmaxsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383d, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmaxsw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fee, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmaxub, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fde, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmaxud, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383f, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmaxuw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383e, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pminsb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3838, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pminsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3839, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pminsw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fea, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pminub, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fda, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pminud, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383b, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pminuw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383a, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmovsxbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3820, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmovsxdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3825, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmovsxwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3823, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmovzxbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3830, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmovzxdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3835, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmovzxwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3833, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmulld, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3840, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pmullw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fd5, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pop, new InstructionInfo(0x0000008f, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Popcnt, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fb8, InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Por, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000feb, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pshufb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3800, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pshufd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f70, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pslld, new InstructionInfo(BadOp, 0x06000f72, BadOp, BadOp, 0x00000ff2, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Pslldq, new InstructionInfo(BadOp, 0x07000f73, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psllq, new InstructionInfo(BadOp, 0x06000f73, BadOp, BadOp, 0x00000ff3, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psllw, new InstructionInfo(BadOp, 0x06000f71, BadOp, BadOp, 0x00000ff1, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psrad, new InstructionInfo(BadOp, 0x04000f72, BadOp, BadOp, 0x00000fe2, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psraw, new InstructionInfo(BadOp, 0x04000f71, BadOp, BadOp, 0x00000fe1, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psrld, new InstructionInfo(BadOp, 0x02000f72, BadOp, BadOp, 0x00000fd2, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psrlq, new InstructionInfo(BadOp, 0x02000f73, BadOp, BadOp, 0x00000fd3, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psrldq, new InstructionInfo(BadOp, 0x03000f73, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psrlw, new InstructionInfo(BadOp, 0x02000f71, BadOp, BadOp, 0x00000fd1, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psubb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ff8, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psubd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffa, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psubq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffb, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Psubw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ff9, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Punpckhbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f68, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Punpckhdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f6a, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Punpckhqdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f6d, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Punpckhwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f69, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Punpcklbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f60, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Punpckldq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f62, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Punpcklqdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f6c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Punpcklwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f61, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Push, new InstructionInfo(BadOp, 0x0000006a, 0x00000068, BadOp, 0x060000ff, InstructionFlags.None));
|
||||
Add(X86Instruction.Pxor, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fef, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Rcpps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f53, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Rcpss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f53, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Ror, new InstructionInfo(0x010000d3, 0x010000c1, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Roundpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a09, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Roundps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a08, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Roundsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a0b, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Roundss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a0a, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Rsqrtps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f52, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Rsqrtss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f52, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Sar, new InstructionInfo(0x070000d3, 0x070000c1, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Setcc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f90, InstructionFlags.Reg8Dest));
|
||||
Add(X86Instruction.Sha256Msg1, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38cc, InstructionFlags.None));
|
||||
Add(X86Instruction.Sha256Msg2, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38cd, InstructionFlags.None));
|
||||
Add(X86Instruction.Sha256Rnds2, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38cb, InstructionFlags.None));
|
||||
Add(X86Instruction.Shl, new InstructionInfo(0x040000d3, 0x040000c1, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Shr, new InstructionInfo(0x050000d3, 0x050000c1, BadOp, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Shufpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc6, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Shufps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc6, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Sqrtpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Sqrtps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Sqrtsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Sqrtss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Stmxcsr, new InstructionInfo(0x03000fae, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Sub, new InstructionInfo(0x00000029, 0x05000083, 0x05000081, BadOp, 0x0000002b, InstructionFlags.None));
|
||||
Add(X86Instruction.Subpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Subps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Subsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex | InstructionFlags.PrefixF2));
|
||||
Add(X86Instruction.Subss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex | InstructionFlags.PrefixF3));
|
||||
Add(X86Instruction.Test, new InstructionInfo(0x00000085, BadOp, 0x000000f7, BadOp, BadOp, InstructionFlags.None));
|
||||
Add(X86Instruction.Unpckhpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f15, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Unpckhps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f15, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Unpcklpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f14, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Unpcklps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f14, InstructionFlags.Vex));
|
||||
Add(X86Instruction.Vblendvpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4b, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vblendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4a, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vcvtph2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3813, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vcvtps2ph, new InstructionInfo(0x000f3a1d, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfnmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfnmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfnmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||
Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None));
|
||||
Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));
|
||||
|
||||
static void Add(X86Instruction inst, in InstructionInfo info)
|
||||
{
|
||||
_instTable[(int)inst] = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
class CodeGenContext
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly Operand[] _blockLabels;
|
||||
|
||||
public int StreamOffset => (int)_stream.Length;
|
||||
|
||||
public AllocationResult AllocResult { get; }
|
||||
|
||||
public Assembler Assembler { get; }
|
||||
public BasicBlock CurrBlock { get; private set; }
|
||||
|
||||
public int CallArgsRegionSize { get; }
|
||||
public int XmmSaveRegionSize { get; }
|
||||
|
||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
_blockLabels = new Operand[blocksCount];
|
||||
|
||||
AllocResult = allocResult;
|
||||
Assembler = new Assembler(_stream, relocatable);
|
||||
|
||||
CallArgsRegionSize = GetCallArgsRegionSize(allocResult, maxCallArgs, out int xmmSaveRegionSize);
|
||||
XmmSaveRegionSize = xmmSaveRegionSize;
|
||||
}
|
||||
|
||||
private static int GetCallArgsRegionSize(AllocationResult allocResult, int maxCallArgs, out int xmmSaveRegionSize)
|
||||
{
|
||||
// We need to add 8 bytes to the total size, as the call to this function already pushed 8 bytes (the
|
||||
// return address).
|
||||
int intMask = CallingConvention.GetIntCalleeSavedRegisters() & allocResult.IntUsedRegisters;
|
||||
int vecMask = CallingConvention.GetVecCalleeSavedRegisters() & allocResult.VecUsedRegisters;
|
||||
|
||||
xmmSaveRegionSize = BitOperations.PopCount((uint)vecMask) * 16;
|
||||
|
||||
int calleeSaveRegionSize = BitOperations.PopCount((uint)intMask) * 8 + xmmSaveRegionSize + 8;
|
||||
|
||||
int argsCount = maxCallArgs;
|
||||
|
||||
if (argsCount < 0)
|
||||
{
|
||||
// When the function has no calls, argsCount is -1. In this case, we don't need to allocate the shadow
|
||||
// space.
|
||||
argsCount = 0;
|
||||
}
|
||||
else if (argsCount < 4)
|
||||
{
|
||||
// The ABI mandates that the space for at least 4 arguments is reserved on the stack (this is called
|
||||
// shadow space).
|
||||
argsCount = 4;
|
||||
}
|
||||
|
||||
// TODO: Align XMM save region to 16 bytes because unwinding on Windows requires it.
|
||||
int frameSize = calleeSaveRegionSize + allocResult.SpillRegionSize;
|
||||
|
||||
// TODO: Instead of always multiplying by 16 (the largest possible size of a variable, since a V128 has 16
|
||||
// bytes), we should calculate the exact size consumed by the arguments passed to the called functions on
|
||||
// the stack.
|
||||
int callArgsAndFrameSize = frameSize + argsCount * 16;
|
||||
|
||||
// Ensure that the Stack Pointer will be aligned to 16 bytes.
|
||||
callArgsAndFrameSize = (callArgsAndFrameSize + 0xf) & ~0xf;
|
||||
|
||||
return callArgsAndFrameSize - frameSize;
|
||||
}
|
||||
|
||||
public void EnterBlock(BasicBlock block)
|
||||
{
|
||||
Assembler.MarkLabel(GetLabel(block));
|
||||
|
||||
CurrBlock = block;
|
||||
}
|
||||
|
||||
public void JumpTo(BasicBlock target)
|
||||
{
|
||||
Assembler.Jmp(GetLabel(target));
|
||||
}
|
||||
|
||||
public void JumpTo(X86Condition condition, BasicBlock target)
|
||||
{
|
||||
Assembler.Jcc(condition, GetLabel(target));
|
||||
}
|
||||
|
||||
private Operand GetLabel(BasicBlock block)
|
||||
{
|
||||
ref Operand label = ref _blockLabels[block.Index];
|
||||
|
||||
if (label == default)
|
||||
{
|
||||
label = Operand.Factory.Label();
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,89 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
static class HardwareCapabilities
|
||||
{
|
||||
static HardwareCapabilities()
|
||||
{
|
||||
if (!X86Base.IsSupported)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(int maxNum, _, _, _) = X86Base.CpuId(0x00000000, 0x00000000);
|
||||
|
||||
(_, _, int ecx1, int edx1) = X86Base.CpuId(0x00000001, 0x00000000);
|
||||
FeatureInfo1Edx = (FeatureFlags1Edx)edx1;
|
||||
FeatureInfo1Ecx = (FeatureFlags1Ecx)ecx1;
|
||||
|
||||
if (maxNum >= 7)
|
||||
{
|
||||
(_, int ebx7, int ecx7, _) = X86Base.CpuId(0x00000007, 0x00000000);
|
||||
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
|
||||
FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FeatureFlags1Edx
|
||||
{
|
||||
Sse = 1 << 25,
|
||||
Sse2 = 1 << 26
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FeatureFlags1Ecx
|
||||
{
|
||||
Sse3 = 1 << 0,
|
||||
Pclmulqdq = 1 << 1,
|
||||
Ssse3 = 1 << 9,
|
||||
Fma = 1 << 12,
|
||||
Sse41 = 1 << 19,
|
||||
Sse42 = 1 << 20,
|
||||
Popcnt = 1 << 23,
|
||||
Aes = 1 << 25,
|
||||
Avx = 1 << 28,
|
||||
F16c = 1 << 29
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FeatureFlags7Ebx
|
||||
{
|
||||
Avx2 = 1 << 5,
|
||||
Sha = 1 << 29
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FeatureFlags7Ecx
|
||||
{
|
||||
Gfni = 1 << 8,
|
||||
}
|
||||
|
||||
public static FeatureFlags1Edx FeatureInfo1Edx { get; }
|
||||
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
|
||||
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
|
||||
public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0;
|
||||
|
||||
public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse);
|
||||
public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2);
|
||||
public static bool SupportsSse3 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse3);
|
||||
public static bool SupportsPclmulqdq => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Pclmulqdq);
|
||||
public static bool SupportsSsse3 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Ssse3);
|
||||
public static bool SupportsFma => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Fma);
|
||||
public static bool SupportsSse41 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse41);
|
||||
public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42);
|
||||
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
|
||||
public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes);
|
||||
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx);
|
||||
public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx;
|
||||
public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c);
|
||||
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
|
||||
public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni);
|
||||
|
||||
public static bool ForceLegacySse { get; set; }
|
||||
|
||||
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
static class IntrinsicTable
|
||||
{
|
||||
private const int BadOp = 0;
|
||||
|
||||
private static IntrinsicInfo[] _intrinTable;
|
||||
|
||||
static IntrinsicTable()
|
||||
{
|
||||
_intrinTable = new IntrinsicInfo[EnumUtils.GetCount(typeof(Intrinsic))];
|
||||
|
||||
Add(Intrinsic.X86Addpd, new IntrinsicInfo(X86Instruction.Addpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Addps, new IntrinsicInfo(X86Instruction.Addps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Addsd, new IntrinsicInfo(X86Instruction.Addsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Addss, new IntrinsicInfo(X86Instruction.Addss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Aesdec, new IntrinsicInfo(X86Instruction.Aesdec, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Aesdeclast, new IntrinsicInfo(X86Instruction.Aesdeclast, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Aesenc, new IntrinsicInfo(X86Instruction.Aesenc, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Aesenclast, new IntrinsicInfo(X86Instruction.Aesenclast, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Aesimc, new IntrinsicInfo(X86Instruction.Aesimc, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Andnpd, new IntrinsicInfo(X86Instruction.Andnpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Andnps, new IntrinsicInfo(X86Instruction.Andnps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Andpd, new IntrinsicInfo(X86Instruction.Andpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Andps, new IntrinsicInfo(X86Instruction.Andps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Blendvpd, new IntrinsicInfo(X86Instruction.Blendvpd, IntrinsicType.Ternary));
|
||||
Add(Intrinsic.X86Blendvps, new IntrinsicInfo(X86Instruction.Blendvps, IntrinsicType.Ternary));
|
||||
Add(Intrinsic.X86Cmppd, new IntrinsicInfo(X86Instruction.Cmppd, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Cmpps, new IntrinsicInfo(X86Instruction.Cmpps, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Cmpsd, new IntrinsicInfo(X86Instruction.Cmpsd, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Cmpss, new IntrinsicInfo(X86Instruction.Cmpss, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Comisdeq, new IntrinsicInfo(X86Instruction.Comisd, IntrinsicType.Comis_));
|
||||
Add(Intrinsic.X86Comisdge, new IntrinsicInfo(X86Instruction.Comisd, IntrinsicType.Comis_));
|
||||
Add(Intrinsic.X86Comisdlt, new IntrinsicInfo(X86Instruction.Comisd, IntrinsicType.Comis_));
|
||||
Add(Intrinsic.X86Comisseq, new IntrinsicInfo(X86Instruction.Comiss, IntrinsicType.Comis_));
|
||||
Add(Intrinsic.X86Comissge, new IntrinsicInfo(X86Instruction.Comiss, IntrinsicType.Comis_));
|
||||
Add(Intrinsic.X86Comisslt, new IntrinsicInfo(X86Instruction.Comiss, IntrinsicType.Comis_));
|
||||
Add(Intrinsic.X86Crc32, new IntrinsicInfo(X86Instruction.Crc32, IntrinsicType.Crc32));
|
||||
Add(Intrinsic.X86Crc32_16, new IntrinsicInfo(X86Instruction.Crc32_16, IntrinsicType.Crc32));
|
||||
Add(Intrinsic.X86Crc32_8, new IntrinsicInfo(X86Instruction.Crc32_8, IntrinsicType.Crc32));
|
||||
Add(Intrinsic.X86Cvtdq2pd, new IntrinsicInfo(X86Instruction.Cvtdq2pd, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Cvtdq2ps, new IntrinsicInfo(X86Instruction.Cvtdq2ps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Cvtpd2dq, new IntrinsicInfo(X86Instruction.Cvtpd2dq, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Cvtpd2ps, new IntrinsicInfo(X86Instruction.Cvtpd2ps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Cvtps2dq, new IntrinsicInfo(X86Instruction.Cvtps2dq, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Cvtps2pd, new IntrinsicInfo(X86Instruction.Cvtps2pd, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Cvtsd2si, new IntrinsicInfo(X86Instruction.Cvtsd2si, IntrinsicType.UnaryToGpr));
|
||||
Add(Intrinsic.X86Cvtsd2ss, new IntrinsicInfo(X86Instruction.Cvtsd2ss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Cvtsi2sd, new IntrinsicInfo(X86Instruction.Cvtsi2sd, IntrinsicType.BinaryGpr));
|
||||
Add(Intrinsic.X86Cvtsi2si, new IntrinsicInfo(X86Instruction.Movd, IntrinsicType.UnaryToGpr));
|
||||
Add(Intrinsic.X86Cvtsi2ss, new IntrinsicInfo(X86Instruction.Cvtsi2ss, IntrinsicType.BinaryGpr));
|
||||
Add(Intrinsic.X86Cvtss2sd, new IntrinsicInfo(X86Instruction.Cvtss2sd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Cvtss2si, new IntrinsicInfo(X86Instruction.Cvtss2si, IntrinsicType.UnaryToGpr));
|
||||
Add(Intrinsic.X86Divpd, new IntrinsicInfo(X86Instruction.Divpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Divps, new IntrinsicInfo(X86Instruction.Divps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Divsd, new IntrinsicInfo(X86Instruction.Divsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Divss, new IntrinsicInfo(X86Instruction.Divss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Gf2p8affineqb, new IntrinsicInfo(X86Instruction.Gf2p8affineqb, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Insertps, new IntrinsicInfo(X86Instruction.Insertps, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Maxpd, new IntrinsicInfo(X86Instruction.Maxpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Maxsd, new IntrinsicInfo(X86Instruction.Maxsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Maxss, new IntrinsicInfo(X86Instruction.Maxss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Minpd, new IntrinsicInfo(X86Instruction.Minpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Minps, new IntrinsicInfo(X86Instruction.Minps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Minsd, new IntrinsicInfo(X86Instruction.Minsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Minss, new IntrinsicInfo(X86Instruction.Minss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Movhlps, new IntrinsicInfo(X86Instruction.Movhlps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Movlhps, new IntrinsicInfo(X86Instruction.Movlhps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Movss, new IntrinsicInfo(X86Instruction.Movss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mulpd, new IntrinsicInfo(X86Instruction.Mulpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mulps, new IntrinsicInfo(X86Instruction.Mulps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mulss, new IntrinsicInfo(X86Instruction.Mulss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Mxcsrmb, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Mask bits.
|
||||
Add(Intrinsic.X86Mxcsrub, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Unmask bits.
|
||||
Add(Intrinsic.X86Paddb, new IntrinsicInfo(X86Instruction.Paddb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Paddq, new IntrinsicInfo(X86Instruction.Paddq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Paddw, new IntrinsicInfo(X86Instruction.Paddw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Palignr, new IntrinsicInfo(X86Instruction.Palignr, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Pand, new IntrinsicInfo(X86Instruction.Pand, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pandn, new IntrinsicInfo(X86Instruction.Pandn, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pavgb, new IntrinsicInfo(X86Instruction.Pavgb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pavgw, new IntrinsicInfo(X86Instruction.Pavgw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pblendvb, new IntrinsicInfo(X86Instruction.Pblendvb, IntrinsicType.Ternary));
|
||||
Add(Intrinsic.X86Pclmulqdq, new IntrinsicInfo(X86Instruction.Pclmulqdq, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Pcmpeqb, new IntrinsicInfo(X86Instruction.Pcmpeqb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pcmpeqd, new IntrinsicInfo(X86Instruction.Pcmpeqd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pcmpeqq, new IntrinsicInfo(X86Instruction.Pcmpeqq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pcmpeqw, new IntrinsicInfo(X86Instruction.Pcmpeqw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pcmpgtb, new IntrinsicInfo(X86Instruction.Pcmpgtb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pcmpgtd, new IntrinsicInfo(X86Instruction.Pcmpgtd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pcmpgtq, new IntrinsicInfo(X86Instruction.Pcmpgtq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pcmpgtw, new IntrinsicInfo(X86Instruction.Pcmpgtw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pmaxsb, new IntrinsicInfo(X86Instruction.Pmaxsb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pmaxsd, new IntrinsicInfo(X86Instruction.Pmaxsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pmaxsw, new IntrinsicInfo(X86Instruction.Pmaxsw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pmaxub, new IntrinsicInfo(X86Instruction.Pmaxub, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pmaxud, new IntrinsicInfo(X86Instruction.Pmaxud, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pmaxuw, new IntrinsicInfo(X86Instruction.Pmaxuw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pminsb, new IntrinsicInfo(X86Instruction.Pminsb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pminsd, new IntrinsicInfo(X86Instruction.Pminsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pminsw, new IntrinsicInfo(X86Instruction.Pminsw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pminub, new IntrinsicInfo(X86Instruction.Pminub, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pminud, new IntrinsicInfo(X86Instruction.Pminud, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pminuw, new IntrinsicInfo(X86Instruction.Pminuw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pmovsxbw, new IntrinsicInfo(X86Instruction.Pmovsxbw, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Pmovsxdq, new IntrinsicInfo(X86Instruction.Pmovsxdq, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Pmovsxwd, new IntrinsicInfo(X86Instruction.Pmovsxwd, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Pmovzxbw, new IntrinsicInfo(X86Instruction.Pmovzxbw, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Pmovzxdq, new IntrinsicInfo(X86Instruction.Pmovzxdq, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Pmovzxwd, new IntrinsicInfo(X86Instruction.Pmovzxwd, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Pmulld, new IntrinsicInfo(X86Instruction.Pmulld, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pmullw, new IntrinsicInfo(X86Instruction.Pmullw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Popcnt, new IntrinsicInfo(X86Instruction.Popcnt, IntrinsicType.PopCount));
|
||||
Add(Intrinsic.X86Por, new IntrinsicInfo(X86Instruction.Por, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pshufb, new IntrinsicInfo(X86Instruction.Pshufb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pshufd, new IntrinsicInfo(X86Instruction.Pshufd, IntrinsicType.BinaryImm));
|
||||
Add(Intrinsic.X86Pslld, new IntrinsicInfo(X86Instruction.Pslld, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pslldq, new IntrinsicInfo(X86Instruction.Pslldq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psllq, new IntrinsicInfo(X86Instruction.Psllq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psllw, new IntrinsicInfo(X86Instruction.Psllw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psrad, new IntrinsicInfo(X86Instruction.Psrad, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psraw, new IntrinsicInfo(X86Instruction.Psraw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psrld, new IntrinsicInfo(X86Instruction.Psrld, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psrlq, new IntrinsicInfo(X86Instruction.Psrlq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psrldq, new IntrinsicInfo(X86Instruction.Psrldq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psrlw, new IntrinsicInfo(X86Instruction.Psrlw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psubb, new IntrinsicInfo(X86Instruction.Psubb, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psubd, new IntrinsicInfo(X86Instruction.Psubd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psubq, new IntrinsicInfo(X86Instruction.Psubq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Psubw, new IntrinsicInfo(X86Instruction.Psubw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Punpckhbw, new IntrinsicInfo(X86Instruction.Punpckhbw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Punpckhdq, new IntrinsicInfo(X86Instruction.Punpckhdq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Punpckhqdq, new IntrinsicInfo(X86Instruction.Punpckhqdq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Punpckhwd, new IntrinsicInfo(X86Instruction.Punpckhwd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Punpcklbw, new IntrinsicInfo(X86Instruction.Punpcklbw, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Punpckldq, new IntrinsicInfo(X86Instruction.Punpckldq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Punpcklqdq, new IntrinsicInfo(X86Instruction.Punpcklqdq, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Punpcklwd, new IntrinsicInfo(X86Instruction.Punpcklwd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Pxor, new IntrinsicInfo(X86Instruction.Pxor, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Rcpps, new IntrinsicInfo(X86Instruction.Rcpps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Rcpss, new IntrinsicInfo(X86Instruction.Rcpss, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Roundpd, new IntrinsicInfo(X86Instruction.Roundpd, IntrinsicType.BinaryImm));
|
||||
Add(Intrinsic.X86Roundps, new IntrinsicInfo(X86Instruction.Roundps, IntrinsicType.BinaryImm));
|
||||
Add(Intrinsic.X86Roundsd, new IntrinsicInfo(X86Instruction.Roundsd, IntrinsicType.BinaryImm));
|
||||
Add(Intrinsic.X86Roundss, new IntrinsicInfo(X86Instruction.Roundss, IntrinsicType.BinaryImm));
|
||||
Add(Intrinsic.X86Rsqrtps, new IntrinsicInfo(X86Instruction.Rsqrtps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Rsqrtss, new IntrinsicInfo(X86Instruction.Rsqrtss, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Sha256Msg1, new IntrinsicInfo(X86Instruction.Sha256Msg1, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Sha256Msg2, new IntrinsicInfo(X86Instruction.Sha256Msg2, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Sha256Rnds2, new IntrinsicInfo(X86Instruction.Sha256Rnds2, IntrinsicType.Ternary));
|
||||
Add(Intrinsic.X86Shufpd, new IntrinsicInfo(X86Instruction.Shufpd, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Shufps, new IntrinsicInfo(X86Instruction.Shufps, IntrinsicType.TernaryImm));
|
||||
Add(Intrinsic.X86Sqrtpd, new IntrinsicInfo(X86Instruction.Sqrtpd, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Sqrtps, new IntrinsicInfo(X86Instruction.Sqrtps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Sqrtss, new IntrinsicInfo(X86Instruction.Sqrtss, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Subpd, new IntrinsicInfo(X86Instruction.Subpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Subsd, new IntrinsicInfo(X86Instruction.Subsd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Subss, new IntrinsicInfo(X86Instruction.Subss, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Unpckhpd, new IntrinsicInfo(X86Instruction.Unpckhpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Unpckhps, new IntrinsicInfo(X86Instruction.Unpckhps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Unpcklpd, new IntrinsicInfo(X86Instruction.Unpcklpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Unpcklps, new IntrinsicInfo(X86Instruction.Unpcklps, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary));
|
||||
Add(Intrinsic.X86Vcvtps2ph, new IntrinsicInfo(X86Instruction.Vcvtps2ph, IntrinsicType.BinaryImm));
|
||||
Add(Intrinsic.X86Vfmadd231ps, new IntrinsicInfo(X86Instruction.Vfmadd231ps, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfmsub231ss, new IntrinsicInfo(X86Instruction.Vfmsub231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231ps, new IntrinsicInfo(X86Instruction.Vfnmadd231ps, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma));
|
||||
Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
|
||||
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary));
|
||||
}
|
||||
|
||||
private static void Add(Intrinsic intrin, IntrinsicInfo info)
|
||||
{
|
||||
_intrinTable[(int)intrin] = info;
|
||||
}
|
||||
|
||||
public static IntrinsicInfo GetInfo(Intrinsic intrin)
|
||||
{
|
||||
return _intrinTable[(int)intrin];
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,228 +0,0 @@
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
enum X86Instruction
|
||||
{
|
||||
None,
|
||||
Add,
|
||||
Addpd,
|
||||
Addps,
|
||||
Addsd,
|
||||
Addss,
|
||||
Aesdec,
|
||||
Aesdeclast,
|
||||
Aesenc,
|
||||
Aesenclast,
|
||||
Aesimc,
|
||||
And,
|
||||
Andnpd,
|
||||
Andnps,
|
||||
Andpd,
|
||||
Andps,
|
||||
Blendvpd,
|
||||
Blendvps,
|
||||
Bsr,
|
||||
Bswap,
|
||||
Call,
|
||||
Cmovcc,
|
||||
Cmp,
|
||||
Cmppd,
|
||||
Cmpps,
|
||||
Cmpsd,
|
||||
Cmpss,
|
||||
Cmpxchg,
|
||||
Cmpxchg16b,
|
||||
Cmpxchg8,
|
||||
Comisd,
|
||||
Comiss,
|
||||
Crc32,
|
||||
Crc32_16,
|
||||
Crc32_8,
|
||||
Cvtdq2pd,
|
||||
Cvtdq2ps,
|
||||
Cvtpd2dq,
|
||||
Cvtpd2ps,
|
||||
Cvtps2dq,
|
||||
Cvtps2pd,
|
||||
Cvtsd2si,
|
||||
Cvtsd2ss,
|
||||
Cvtsi2sd,
|
||||
Cvtsi2ss,
|
||||
Cvtss2sd,
|
||||
Cvtss2si,
|
||||
Div,
|
||||
Divpd,
|
||||
Divps,
|
||||
Divsd,
|
||||
Divss,
|
||||
Gf2p8affineqb,
|
||||
Haddpd,
|
||||
Haddps,
|
||||
Idiv,
|
||||
Imul,
|
||||
Imul128,
|
||||
Insertps,
|
||||
Jmp,
|
||||
Ldmxcsr,
|
||||
Lea,
|
||||
Maxpd,
|
||||
Maxps,
|
||||
Maxsd,
|
||||
Maxss,
|
||||
Minpd,
|
||||
Minps,
|
||||
Minsd,
|
||||
Minss,
|
||||
Mov,
|
||||
Mov16,
|
||||
Mov8,
|
||||
Movd,
|
||||
Movdqu,
|
||||
Movhlps,
|
||||
Movlhps,
|
||||
Movq,
|
||||
Movsd,
|
||||
Movss,
|
||||
Movsx16,
|
||||
Movsx32,
|
||||
Movsx8,
|
||||
Movzx16,
|
||||
Movzx8,
|
||||
Mul128,
|
||||
Mulpd,
|
||||
Mulps,
|
||||
Mulsd,
|
||||
Mulss,
|
||||
Neg,
|
||||
Not,
|
||||
Or,
|
||||
Paddb,
|
||||
Paddd,
|
||||
Paddq,
|
||||
Paddw,
|
||||
Palignr,
|
||||
Pand,
|
||||
Pandn,
|
||||
Pavgb,
|
||||
Pavgw,
|
||||
Pblendvb,
|
||||
Pclmulqdq,
|
||||
Pcmpeqb,
|
||||
Pcmpeqd,
|
||||
Pcmpeqq,
|
||||
Pcmpeqw,
|
||||
Pcmpgtb,
|
||||
Pcmpgtd,
|
||||
Pcmpgtq,
|
||||
Pcmpgtw,
|
||||
Pextrb,
|
||||
Pextrd,
|
||||
Pextrq,
|
||||
Pextrw,
|
||||
Pinsrb,
|
||||
Pinsrd,
|
||||
Pinsrq,
|
||||
Pinsrw,
|
||||
Pmaxsb,
|
||||
Pmaxsd,
|
||||
Pmaxsw,
|
||||
Pmaxub,
|
||||
Pmaxud,
|
||||
Pmaxuw,
|
||||
Pminsb,
|
||||
Pminsd,
|
||||
Pminsw,
|
||||
Pminub,
|
||||
Pminud,
|
||||
Pminuw,
|
||||
Pmovsxbw,
|
||||
Pmovsxdq,
|
||||
Pmovsxwd,
|
||||
Pmovzxbw,
|
||||
Pmovzxdq,
|
||||
Pmovzxwd,
|
||||
Pmulld,
|
||||
Pmullw,
|
||||
Pop,
|
||||
Popcnt,
|
||||
Por,
|
||||
Pshufb,
|
||||
Pshufd,
|
||||
Pslld,
|
||||
Pslldq,
|
||||
Psllq,
|
||||
Psllw,
|
||||
Psrad,
|
||||
Psraw,
|
||||
Psrld,
|
||||
Psrlq,
|
||||
Psrldq,
|
||||
Psrlw,
|
||||
Psubb,
|
||||
Psubd,
|
||||
Psubq,
|
||||
Psubw,
|
||||
Punpckhbw,
|
||||
Punpckhdq,
|
||||
Punpckhqdq,
|
||||
Punpckhwd,
|
||||
Punpcklbw,
|
||||
Punpckldq,
|
||||
Punpcklqdq,
|
||||
Punpcklwd,
|
||||
Push,
|
||||
Pxor,
|
||||
Rcpps,
|
||||
Rcpss,
|
||||
Ror,
|
||||
Roundpd,
|
||||
Roundps,
|
||||
Roundsd,
|
||||
Roundss,
|
||||
Rsqrtps,
|
||||
Rsqrtss,
|
||||
Sar,
|
||||
Setcc,
|
||||
Sha256Msg1,
|
||||
Sha256Msg2,
|
||||
Sha256Rnds2,
|
||||
Shl,
|
||||
Shr,
|
||||
Shufpd,
|
||||
Shufps,
|
||||
Sqrtpd,
|
||||
Sqrtps,
|
||||
Sqrtsd,
|
||||
Sqrtss,
|
||||
Stmxcsr,
|
||||
Sub,
|
||||
Subpd,
|
||||
Subps,
|
||||
Subsd,
|
||||
Subss,
|
||||
Test,
|
||||
Unpckhpd,
|
||||
Unpckhps,
|
||||
Unpcklpd,
|
||||
Unpcklps,
|
||||
Vblendvpd,
|
||||
Vblendvps,
|
||||
Vcvtph2ps,
|
||||
Vcvtps2ph,
|
||||
Vfmadd231ps,
|
||||
Vfmadd231sd,
|
||||
Vfmadd231ss,
|
||||
Vfmsub231sd,
|
||||
Vfmsub231ss,
|
||||
Vfnmadd231ps,
|
||||
Vfnmadd231sd,
|
||||
Vfnmadd231ss,
|
||||
Vfnmsub231sd,
|
||||
Vfnmsub231ss,
|
||||
Vpblendvb,
|
||||
Xor,
|
||||
Xorpd,
|
||||
Xorps,
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
using ARMeilleure.Common;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
static class DecoderHelper
|
||||
{
|
||||
static DecoderHelper()
|
||||
{
|
||||
Imm8ToFP32Table = BuildImm8ToFP32Table();
|
||||
Imm8ToFP64Table = BuildImm8ToFP64Table();
|
||||
}
|
||||
|
||||
public static readonly uint[] Imm8ToFP32Table;
|
||||
public static readonly ulong[] Imm8ToFP64Table;
|
||||
|
||||
private static uint[] BuildImm8ToFP32Table()
|
||||
{
|
||||
uint[] tbl = new uint[256];
|
||||
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
{
|
||||
tbl[idx] = ExpandImm8ToFP32((uint)idx);
|
||||
}
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
private static ulong[] BuildImm8ToFP64Table()
|
||||
{
|
||||
ulong[] tbl = new ulong[256];
|
||||
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
{
|
||||
tbl[idx] = ExpandImm8ToFP64((ulong)idx);
|
||||
}
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
// abcdefgh -> aBbbbbbc defgh000 00000000 00000000 (B = ~b)
|
||||
private static uint ExpandImm8ToFP32(uint imm)
|
||||
{
|
||||
uint MoveBit(uint bits, int from, int to)
|
||||
{
|
||||
return ((bits >> from) & 1U) << to;
|
||||
}
|
||||
|
||||
return MoveBit(imm, 7, 31) | MoveBit(~imm, 6, 30) |
|
||||
MoveBit(imm, 6, 29) | MoveBit( imm, 6, 28) |
|
||||
MoveBit(imm, 6, 27) | MoveBit( imm, 6, 26) |
|
||||
MoveBit(imm, 6, 25) | MoveBit( imm, 5, 24) |
|
||||
MoveBit(imm, 4, 23) | MoveBit( imm, 3, 22) |
|
||||
MoveBit(imm, 2, 21) | MoveBit( imm, 1, 20) |
|
||||
MoveBit(imm, 0, 19);
|
||||
}
|
||||
|
||||
// abcdefgh -> aBbbbbbb bbcdefgh 00000000 00000000 00000000 00000000 00000000 00000000 (B = ~b)
|
||||
private static ulong ExpandImm8ToFP64(ulong imm)
|
||||
{
|
||||
ulong MoveBit(ulong bits, int from, int to)
|
||||
{
|
||||
return ((bits >> from) & 1UL) << to;
|
||||
}
|
||||
|
||||
return MoveBit(imm, 7, 63) | MoveBit(~imm, 6, 62) |
|
||||
MoveBit(imm, 6, 61) | MoveBit( imm, 6, 60) |
|
||||
MoveBit(imm, 6, 59) | MoveBit( imm, 6, 58) |
|
||||
MoveBit(imm, 6, 57) | MoveBit( imm, 6, 56) |
|
||||
MoveBit(imm, 6, 55) | MoveBit( imm, 6, 54) |
|
||||
MoveBit(imm, 5, 53) | MoveBit( imm, 4, 52) |
|
||||
MoveBit(imm, 3, 51) | MoveBit( imm, 2, 50) |
|
||||
MoveBit(imm, 1, 49) | MoveBit( imm, 0, 48);
|
||||
}
|
||||
|
||||
public struct BitMask
|
||||
{
|
||||
public long WMask;
|
||||
public long TMask;
|
||||
public int Pos;
|
||||
public int Shift;
|
||||
public bool IsUndefined;
|
||||
|
||||
public static BitMask Invalid => new BitMask { IsUndefined = true };
|
||||
}
|
||||
|
||||
public static BitMask DecodeBitMask(int opCode, bool immediate)
|
||||
{
|
||||
int immS = (opCode >> 10) & 0x3f;
|
||||
int immR = (opCode >> 16) & 0x3f;
|
||||
|
||||
int n = (opCode >> 22) & 1;
|
||||
int sf = (opCode >> 31) & 1;
|
||||
|
||||
int length = BitUtils.HighestBitSet((~immS & 0x3f) | (n << 6));
|
||||
|
||||
if (length < 1 || (sf == 0 && n != 0))
|
||||
{
|
||||
return BitMask.Invalid;
|
||||
}
|
||||
|
||||
int size = 1 << length;
|
||||
|
||||
int levels = size - 1;
|
||||
|
||||
int s = immS & levels;
|
||||
int r = immR & levels;
|
||||
|
||||
if (immediate && s == levels)
|
||||
{
|
||||
return BitMask.Invalid;
|
||||
}
|
||||
|
||||
long wMask = BitUtils.FillWithOnes(s + 1);
|
||||
long tMask = BitUtils.FillWithOnes(((s - r) & levels) + 1);
|
||||
|
||||
if (r > 0)
|
||||
{
|
||||
wMask = BitUtils.RotateRight(wMask, r, size);
|
||||
wMask &= BitUtils.FillWithOnes(size);
|
||||
}
|
||||
|
||||
return new BitMask()
|
||||
{
|
||||
WMask = BitUtils.Replicate(wMask, size),
|
||||
TMask = BitUtils.Replicate(tMask, size),
|
||||
|
||||
Pos = immS,
|
||||
Shift = immR
|
||||
};
|
||||
}
|
||||
|
||||
public static long DecodeImm24_2(int opCode)
|
||||
{
|
||||
return ((long)opCode << 40) >> 38;
|
||||
}
|
||||
|
||||
public static long DecodeImm26_2(int opCode)
|
||||
{
|
||||
return ((long)opCode << 38) >> 36;
|
||||
}
|
||||
|
||||
public static long DecodeImmS19_2(int opCode)
|
||||
{
|
||||
return (((long)opCode << 40) >> 43) & ~3;
|
||||
}
|
||||
|
||||
public static long DecodeImmS14_2(int opCode)
|
||||
{
|
||||
return (((long)opCode << 45) >> 48) & ~3;
|
||||
}
|
||||
|
||||
public static bool VectorArgumentsInvalid(bool q, params int[] args)
|
||||
{
|
||||
if (q)
|
||||
{
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if ((args[i] & 1) == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace ARMeilleure.Decoders;
|
||||
|
||||
interface IOpCode32Exception
|
||||
{
|
||||
int Id { get; }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,54 +0,0 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitHashHelper;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
static partial class InstEmit32
|
||||
{
|
||||
public static void Crc32b(ArmEmitterContext context)
|
||||
{
|
||||
EmitCrc32Call(context, ByteSizeLog2, false);
|
||||
}
|
||||
|
||||
public static void Crc32h(ArmEmitterContext context)
|
||||
{
|
||||
EmitCrc32Call(context, HWordSizeLog2, false);
|
||||
}
|
||||
|
||||
public static void Crc32w(ArmEmitterContext context)
|
||||
{
|
||||
EmitCrc32Call(context, WordSizeLog2, false);
|
||||
}
|
||||
|
||||
public static void Crc32cb(ArmEmitterContext context)
|
||||
{
|
||||
EmitCrc32Call(context, ByteSizeLog2, true);
|
||||
}
|
||||
|
||||
public static void Crc32ch(ArmEmitterContext context)
|
||||
{
|
||||
EmitCrc32Call(context, HWordSizeLog2, true);
|
||||
}
|
||||
|
||||
public static void Crc32cw(ArmEmitterContext context)
|
||||
{
|
||||
EmitCrc32Call(context, WordSizeLog2, true);
|
||||
}
|
||||
|
||||
private static void EmitCrc32Call(ArmEmitterContext context, int size, bool c)
|
||||
{
|
||||
IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp;
|
||||
|
||||
Operand n = GetIntA32(context, op.Rn);
|
||||
Operand m = GetIntA32(context, op.Rm);
|
||||
|
||||
Operand d = EmitCrc32(context, n, m, size, c);
|
||||
|
||||
EmitAluStore(context, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
// https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
|
||||
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
static class InstEmitHashHelper
|
||||
{
|
||||
public const uint Crc32RevPoly = 0xedb88320;
|
||||
public const uint Crc32cRevPoly = 0x82f63b78;
|
||||
|
||||
public static Operand EmitCrc32(ArmEmitterContext context, Operand crc, Operand value, int size, bool castagnoli)
|
||||
{
|
||||
Debug.Assert(crc.Type.IsInteger() && value.Type.IsInteger());
|
||||
Debug.Assert(size >= 0 && size < 4);
|
||||
Debug.Assert((size < 3) || (value.Type == OperandType.I64));
|
||||
|
||||
if (castagnoli && Optimizations.UseSse42)
|
||||
{
|
||||
// The CRC32 instruction does not have an immediate variant, so ensure both inputs are in registers.
|
||||
value = (value.Kind == OperandKind.Constant) ? context.Copy(value) : value;
|
||||
crc = (crc.Kind == OperandKind.Constant) ? context.Copy(crc) : crc;
|
||||
|
||||
Intrinsic op = size switch
|
||||
{
|
||||
0 => Intrinsic.X86Crc32_8,
|
||||
1 => Intrinsic.X86Crc32_16,
|
||||
_ => Intrinsic.X86Crc32,
|
||||
};
|
||||
|
||||
return (size == 3) ? context.ConvertI64ToI32(context.AddIntrinsicLong(op, crc, value)) : context.AddIntrinsicInt(op, crc, value);
|
||||
}
|
||||
else if (Optimizations.UsePclmulqdq)
|
||||
{
|
||||
return size switch
|
||||
{
|
||||
3 => EmitCrc32Optimized64(context, crc, value, castagnoli),
|
||||
_ => EmitCrc32Optimized(context, crc, value, castagnoli, size),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = (size, castagnoli) switch
|
||||
{
|
||||
(0, false) => nameof(SoftFallback.Crc32b),
|
||||
(1, false) => nameof(SoftFallback.Crc32h),
|
||||
(2, false) => nameof(SoftFallback.Crc32w),
|
||||
(3, false) => nameof(SoftFallback.Crc32x),
|
||||
(0, true) => nameof(SoftFallback.Crc32cb),
|
||||
(1, true) => nameof(SoftFallback.Crc32ch),
|
||||
(2, true) => nameof(SoftFallback.Crc32cw),
|
||||
(3, true) => nameof(SoftFallback.Crc32cx),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(size))
|
||||
};
|
||||
|
||||
return context.Call(typeof(SoftFallback).GetMethod(name), crc, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand EmitCrc32Optimized(ArmEmitterContext context, Operand crc, Operand data, bool castagnoli, int size)
|
||||
{
|
||||
long mu = castagnoli ? 0x0DEA713F1 : 0x1F7011641; // mu' = floor(x^64/P(x))'
|
||||
long polynomial = castagnoli ? 0x105EC76F0 : 0x1DB710641; // P'(x) << 1
|
||||
|
||||
crc = context.VectorInsert(context.VectorZero(), crc, 0);
|
||||
|
||||
switch (size)
|
||||
{
|
||||
case 0: data = context.VectorInsert8(context.VectorZero(), data, 0); break;
|
||||
case 1: data = context.VectorInsert16(context.VectorZero(), data, 0); break;
|
||||
case 2: data = context.VectorInsert(context.VectorZero(), data, 0); break;
|
||||
}
|
||||
|
||||
int bitsize = 8 << size;
|
||||
|
||||
Operand tmp = context.AddIntrinsic(Intrinsic.X86Pxor, crc, data);
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Psllq, tmp, Const(64 - bitsize));
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, mu), Const(0));
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, polynomial), Const(0));
|
||||
|
||||
if (bitsize < 32)
|
||||
{
|
||||
crc = context.AddIntrinsic(Intrinsic.X86Pslldq, crc, Const((64 - bitsize) / 8));
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Pxor, tmp, crc);
|
||||
}
|
||||
|
||||
return context.VectorExtract(OperandType.I32, tmp, 2);
|
||||
}
|
||||
|
||||
private static Operand EmitCrc32Optimized64(ArmEmitterContext context, Operand crc, Operand data, bool castagnoli)
|
||||
{
|
||||
long mu = castagnoli ? 0x0DEA713F1 : 0x1F7011641; // mu' = floor(x^64/P(x))'
|
||||
long polynomial = castagnoli ? 0x105EC76F0 : 0x1DB710641; // P'(x) << 1
|
||||
|
||||
crc = context.VectorInsert(context.VectorZero(), crc, 0);
|
||||
data = context.VectorInsert(context.VectorZero(), data, 0);
|
||||
|
||||
Operand tmp = context.AddIntrinsic(Intrinsic.X86Pxor, crc, data);
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pslldq, tmp, Const(4));
|
||||
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, res, X86GetScalar(context, mu), Const(0));
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, polynomial), Const(0));
|
||||
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Pxor, tmp, res);
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Psllq, tmp, Const(32));
|
||||
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, mu), Const(1));
|
||||
tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, polynomial), Const(0));
|
||||
|
||||
return context.VectorExtract(OperandType.I32, tmp, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,785 +0,0 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
using Func2I = Func<Operand, Operand, Operand>;
|
||||
|
||||
static partial class InstEmit
|
||||
{
|
||||
public static void Cmeq_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareEqual(op1, op2), scalar: true);
|
||||
}
|
||||
|
||||
public static void Cmeq_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m;
|
||||
|
||||
if (op is OpCodeSimdReg binOp)
|
||||
{
|
||||
m = GetVec(binOp.Rm);
|
||||
}
|
||||
else
|
||||
{
|
||||
m = context.VectorZero();
|
||||
}
|
||||
|
||||
Intrinsic cmpInst = X86PcmpeqInstruction[op.Size];
|
||||
|
||||
Operand res = context.AddIntrinsic(cmpInst, n, m);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareEqual(op1, op2), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cmge_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqual(op1, op2), scalar: true);
|
||||
}
|
||||
|
||||
public static void Cmge_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse42)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m;
|
||||
|
||||
if (op is OpCodeSimdReg binOp)
|
||||
{
|
||||
m = GetVec(binOp.Rm);
|
||||
}
|
||||
else
|
||||
{
|
||||
m = context.VectorZero();
|
||||
}
|
||||
|
||||
Intrinsic cmpInst = X86PcmpgtInstruction[op.Size];
|
||||
|
||||
Operand res = context.AddIntrinsic(cmpInst, m, n);
|
||||
|
||||
Operand mask = X86GetAllElements(context, -1L);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pandn, res, mask);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqual(op1, op2), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cmgt_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareGreater(op1, op2), scalar: true);
|
||||
}
|
||||
|
||||
public static void Cmgt_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse42)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m;
|
||||
|
||||
if (op is OpCodeSimdReg binOp)
|
||||
{
|
||||
m = GetVec(binOp.Rm);
|
||||
}
|
||||
else
|
||||
{
|
||||
m = context.VectorZero();
|
||||
}
|
||||
|
||||
Intrinsic cmpInst = X86PcmpgtInstruction[op.Size];
|
||||
|
||||
Operand res = context.AddIntrinsic(cmpInst, n, m);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareGreater(op1, op2), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cmhi_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareGreaterUI(op1, op2), scalar: true);
|
||||
}
|
||||
|
||||
public static void Cmhi_V(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseSse41 && op.Size < 3)
|
||||
{
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Intrinsic maxInst = X86PmaxuInstruction[op.Size];
|
||||
|
||||
Operand res = context.AddIntrinsic(maxInst, m, n);
|
||||
|
||||
Intrinsic cmpInst = X86PcmpeqInstruction[op.Size];
|
||||
|
||||
res = context.AddIntrinsic(cmpInst, res, m);
|
||||
|
||||
Operand mask = X86GetAllElements(context, -1L);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pandn, res, mask);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareGreaterUI(op1, op2), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cmhs_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqualUI(op1, op2), scalar: true);
|
||||
}
|
||||
|
||||
public static void Cmhs_V(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseSse41 && op.Size < 3)
|
||||
{
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Intrinsic maxInst = X86PmaxuInstruction[op.Size];
|
||||
|
||||
Operand res = context.AddIntrinsic(maxInst, n, m);
|
||||
|
||||
Intrinsic cmpInst = X86PcmpeqInstruction[op.Size];
|
||||
|
||||
res = context.AddIntrinsic(cmpInst, res, n);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqualUI(op1, op2), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cmle_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareLessOrEqual(op1, op2), scalar: true);
|
||||
}
|
||||
|
||||
public static void Cmle_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse42)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Intrinsic cmpInst = X86PcmpgtInstruction[op.Size];
|
||||
|
||||
Operand res = context.AddIntrinsic(cmpInst, n, context.VectorZero());
|
||||
|
||||
Operand mask = X86GetAllElements(context, -1L);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pandn, res, mask);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareLessOrEqual(op1, op2), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cmlt_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareLess(op1, op2), scalar: true);
|
||||
}
|
||||
|
||||
public static void Cmlt_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse42)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Intrinsic cmpInst = X86PcmpgtInstruction[op.Size];
|
||||
|
||||
Operand res = context.AddIntrinsic(cmpInst, context.VectorZero(), n);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOp(context, (op1, op2) => context.ICompareLess(op1, op2), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cmtst_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmtstOp(context, scalar: true);
|
||||
}
|
||||
|
||||
public static void Cmtst_V(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmtstOp(context, scalar: false);
|
||||
}
|
||||
|
||||
public static void Facge_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: true, absolute: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: true, absolute: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Facge_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: false, absolute: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: false, absolute: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Facgt_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: true, absolute: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: true, absolute: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Facgt_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: false, absolute: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: false, absolute: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fccmp_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitFccmpOrFccmpe(context, signalNaNs: false);
|
||||
}
|
||||
|
||||
public static void Fccmpe_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitFccmpOrFccmpe(context, signalNaNs: true);
|
||||
}
|
||||
|
||||
public static void Fcmeq_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.Equal, scalar: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareEQ), scalar: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmeq_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.Equal, scalar: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareEQ), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmge_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmge_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmgt_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmgt_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmle_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThanOrEqual, scalar: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLE), scalar: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmle_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThanOrEqual, scalar: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLE), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmlt_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThan, scalar: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLT), scalar: true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmlt_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThan, scalar: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLT), scalar: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fcmp_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitFcmpOrFcmpe(context, signalNaNs: false);
|
||||
}
|
||||
|
||||
public static void Fcmpe_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitFcmpOrFcmpe(context, signalNaNs: true);
|
||||
}
|
||||
|
||||
private static void EmitFccmpOrFccmpe(ArmEmitterContext context, bool signalNaNs)
|
||||
{
|
||||
OpCodeSimdFcond op = (OpCodeSimdFcond)context.CurrOp;
|
||||
|
||||
Operand lblTrue = Label();
|
||||
Operand lblEnd = Label();
|
||||
|
||||
context.BranchIfTrue(lblTrue, InstEmitFlowHelper.GetCondTrue(context, op.Cond));
|
||||
|
||||
EmitSetNzcv(context, op.Nzcv);
|
||||
|
||||
context.Branch(lblEnd);
|
||||
|
||||
context.MarkLabel(lblTrue);
|
||||
|
||||
EmitFcmpOrFcmpe(context, signalNaNs);
|
||||
|
||||
context.MarkLabel(lblEnd);
|
||||
}
|
||||
|
||||
private static void EmitSetNzcv(ArmEmitterContext context, int nzcv)
|
||||
{
|
||||
Operand Extract(int value, int bit)
|
||||
{
|
||||
if (bit != 0)
|
||||
{
|
||||
value >>= bit;
|
||||
}
|
||||
|
||||
value &= 1;
|
||||
|
||||
return Const(value);
|
||||
}
|
||||
|
||||
SetFlag(context, PState.VFlag, Extract(nzcv, 0));
|
||||
SetFlag(context, PState.CFlag, Extract(nzcv, 1));
|
||||
SetFlag(context, PState.ZFlag, Extract(nzcv, 2));
|
||||
SetFlag(context, PState.NFlag, Extract(nzcv, 3));
|
||||
}
|
||||
|
||||
private static void EmitFcmpOrFcmpe(ArmEmitterContext context, bool signalNaNs)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
bool cmpWithZero = !(op is OpCodeSimdFcond) ? op.Bit3 : false;
|
||||
|
||||
if (Optimizations.FastFP && (signalNaNs ? Optimizations.UseAvx : Optimizations.UseSse2))
|
||||
{
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = cmpWithZero ? context.VectorZero() : GetVec(op.Rm);
|
||||
|
||||
CmpCondition cmpOrdered = signalNaNs ? CmpCondition.OrderedS : CmpCondition.OrderedQ;
|
||||
|
||||
Operand lblNaN = Label();
|
||||
Operand lblEnd = Label();
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpss, n, m, Const((int)cmpOrdered));
|
||||
|
||||
Operand isOrdered = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, ordMask);
|
||||
|
||||
context.BranchIfFalse(lblNaN, isOrdered);
|
||||
|
||||
Operand nCopy = context.Copy(n);
|
||||
Operand mCopy = cmpWithZero ? context.VectorZero() : context.Copy(m);
|
||||
|
||||
Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comissge, nCopy, mCopy);
|
||||
Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisseq, nCopy, mCopy);
|
||||
Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisslt, nCopy, mCopy);
|
||||
|
||||
SetFlag(context, PState.VFlag, Const(0));
|
||||
SetFlag(context, PState.CFlag, cf);
|
||||
SetFlag(context, PState.ZFlag, zf);
|
||||
SetFlag(context, PState.NFlag, nf);
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, m, Const((int)cmpOrdered));
|
||||
|
||||
Operand isOrdered = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, ordMask);
|
||||
|
||||
context.BranchIfFalse(lblNaN, isOrdered);
|
||||
|
||||
Operand nCopy = context.Copy(n);
|
||||
Operand mCopy = cmpWithZero ? context.VectorZero() : context.Copy(m);
|
||||
|
||||
Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comisdge, nCopy, mCopy);
|
||||
Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisdeq, nCopy, mCopy);
|
||||
Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisdlt, nCopy, mCopy);
|
||||
|
||||
SetFlag(context, PState.VFlag, Const(0));
|
||||
SetFlag(context, PState.CFlag, cf);
|
||||
SetFlag(context, PState.ZFlag, zf);
|
||||
SetFlag(context, PState.NFlag, nf);
|
||||
}
|
||||
|
||||
context.Branch(lblEnd);
|
||||
|
||||
context.MarkLabel(lblNaN);
|
||||
|
||||
SetFlag(context, PState.VFlag, Const(1));
|
||||
SetFlag(context, PState.CFlag, Const(1));
|
||||
SetFlag(context, PState.ZFlag, Const(0));
|
||||
SetFlag(context, PState.NFlag, Const(0));
|
||||
|
||||
context.MarkLabel(lblEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
OperandType type = op.Size != 0 ? OperandType.FP64 : OperandType.FP32;
|
||||
|
||||
Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0);
|
||||
Operand me;
|
||||
|
||||
if (cmpWithZero)
|
||||
{
|
||||
me = op.Size == 0 ? ConstF(0f) : ConstF(0d);
|
||||
}
|
||||
else
|
||||
{
|
||||
me = context.VectorExtract(type, GetVec(op.Rm), 0);
|
||||
}
|
||||
|
||||
Operand nzcv = EmitSoftFloatCall(context, nameof(SoftFloat32.FPCompare), ne, me, Const(signalNaNs));
|
||||
|
||||
EmitSetNzcv(context, nzcv);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitSetNzcv(ArmEmitterContext context, Operand nzcv)
|
||||
{
|
||||
Operand Extract(Operand value, int bit)
|
||||
{
|
||||
if (bit != 0)
|
||||
{
|
||||
value = context.ShiftRightUI(value, Const(bit));
|
||||
}
|
||||
|
||||
value = context.BitwiseAnd(value, Const(1));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
SetFlag(context, PState.VFlag, Extract(nzcv, 0));
|
||||
SetFlag(context, PState.CFlag, Extract(nzcv, 1));
|
||||
SetFlag(context, PState.ZFlag, Extract(nzcv, 2));
|
||||
SetFlag(context, PState.NFlag, Extract(nzcv, 3));
|
||||
}
|
||||
|
||||
private static void EmitCmpOp(ArmEmitterContext context, Func2I emitCmp, bool scalar)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand res = context.VectorZero();
|
||||
|
||||
int elems = !scalar ? op.GetBytesCount() >> op.Size : 1;
|
||||
|
||||
ulong szMask = ulong.MaxValue >> (64 - (8 << op.Size));
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size);
|
||||
Operand me;
|
||||
|
||||
if (op is OpCodeSimdReg binOp)
|
||||
{
|
||||
me = EmitVectorExtractSx(context, binOp.Rm, index, op.Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
me = Const(0L);
|
||||
}
|
||||
|
||||
Operand isTrue = emitCmp(ne, me);
|
||||
|
||||
Operand mask = context.ConditionalSelect(isTrue, Const(szMask), Const(0L));
|
||||
|
||||
res = EmitVectorInsert(context, res, mask, index, op.Size);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
|
||||
private static void EmitCmtstOp(ArmEmitterContext context, bool scalar)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand res = context.VectorZero();
|
||||
|
||||
int elems = !scalar ? op.GetBytesCount() >> op.Size : 1;
|
||||
|
||||
ulong szMask = ulong.MaxValue >> (64 - (8 << op.Size));
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size);
|
||||
Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size);
|
||||
|
||||
Operand test = context.BitwiseAnd(ne, me);
|
||||
|
||||
Operand isTrue = context.ICompareNotEqual(test, Const(0L));
|
||||
|
||||
Operand mask = context.ConditionalSelect(isTrue, Const(szMask), Const(0L));
|
||||
|
||||
res = EmitVectorInsert(context, res, mask, index, op.Size);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
|
||||
private static void EmitCmpOpF(ArmEmitterContext context, string name, bool scalar, bool absolute = false)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand res = context.VectorZero();
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32;
|
||||
|
||||
int elems = !scalar ? op.GetBytesCount() >> sizeF + 2 : 1;
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand ne = context.VectorExtract(type, GetVec(op.Rn), index);
|
||||
Operand me;
|
||||
|
||||
if (op is OpCodeSimdReg binOp)
|
||||
{
|
||||
me = context.VectorExtract(type, GetVec(binOp.Rm), index);
|
||||
}
|
||||
else
|
||||
{
|
||||
me = sizeF == 0 ? ConstF(0f) : ConstF(0d);
|
||||
}
|
||||
|
||||
if (absolute)
|
||||
{
|
||||
ne = EmitUnaryMathCall(context, nameof(Math.Abs), ne);
|
||||
me = EmitUnaryMathCall(context, nameof(Math.Abs), me);
|
||||
}
|
||||
|
||||
Operand e = EmitSoftFloatCall(context, name, ne, me);
|
||||
|
||||
res = context.VectorInsert(res, e, index);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
|
||||
private static void EmitSse2OrAvxCmpOpF(ArmEmitterContext context, CmpCondition cond, bool scalar, bool absolute = false)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = op is OpCodeSimdReg binOp ? GetVec(binOp.Rm) : context.VectorZero();
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
if (absolute)
|
||||
{
|
||||
Operand mask = scalar ? X86GetScalar(context, int.MaxValue) : X86GetAllElements(context, int.MaxValue);
|
||||
|
||||
n = context.AddIntrinsic(Intrinsic.X86Andps, n, mask);
|
||||
m = context.AddIntrinsic(Intrinsic.X86Andps, m, mask);
|
||||
}
|
||||
|
||||
Intrinsic inst = scalar ? Intrinsic.X86Cmpss : Intrinsic.X86Cmpps;
|
||||
|
||||
Operand res = context.AddIntrinsic(inst, n, m, Const((int)cond));
|
||||
|
||||
if (scalar)
|
||||
{
|
||||
res = context.VectorZeroUpper96(res);
|
||||
}
|
||||
else if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
if (absolute)
|
||||
{
|
||||
Operand mask = scalar ? X86GetScalar(context, long.MaxValue) : X86GetAllElements(context, long.MaxValue);
|
||||
|
||||
n = context.AddIntrinsic(Intrinsic.X86Andpd, n, mask);
|
||||
m = context.AddIntrinsic(Intrinsic.X86Andpd, m, mask);
|
||||
}
|
||||
|
||||
Intrinsic inst = scalar ? Intrinsic.X86Cmpsd : Intrinsic.X86Cmppd;
|
||||
|
||||
Operand res = context.AddIntrinsic(inst, n, m, Const((int)cond));
|
||||
|
||||
if (scalar)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,391 +0,0 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper32;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
using Func2I = Func<Operand, Operand, Operand>;
|
||||
|
||||
static partial class InstEmit32
|
||||
{
|
||||
public static void Vceq_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF32(context, CmpCondition.Equal, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareEQFpscr), false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vceq_I(ArmEmitterContext context)
|
||||
{
|
||||
EmitCmpOpI32(context, context.ICompareEqual, context.ICompareEqual, false, false);
|
||||
}
|
||||
|
||||
public static void Vceq_Z(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
if (op.F)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF32(context, CmpCondition.Equal, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareEQFpscr), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpI32(context, context.ICompareEqual, context.ICompareEqual, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vcge_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThanOrEqual, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGEFpscr), false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vcge_I(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||
|
||||
EmitCmpOpI32(context, context.ICompareGreaterOrEqual, context.ICompareGreaterOrEqualUI, false, !op.U);
|
||||
}
|
||||
|
||||
public static void Vcge_Z(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
if (op.F)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThanOrEqual, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGEFpscr), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpI32(context, context.ICompareGreaterOrEqual, context.ICompareGreaterOrEqualUI, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vcgt_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThan, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGTFpscr), false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vcgt_I(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||
|
||||
EmitCmpOpI32(context, context.ICompareGreater, context.ICompareGreaterUI, false, !op.U);
|
||||
}
|
||||
|
||||
public static void Vcgt_Z(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
if (op.F)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseAvx)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThan, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGTFpscr), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpI32(context, context.ICompareGreater, context.ICompareGreaterUI, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vcle_Z(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
if (op.F)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF32(context, CmpCondition.LessThanOrEqual, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareLEFpscr), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpI32(context, context.ICompareLessOrEqual, context.ICompareLessOrEqualUI, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vclt_Z(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
if (op.F)
|
||||
{
|
||||
if (Optimizations.FastFP && Optimizations.UseSse2)
|
||||
{
|
||||
EmitSse2OrAvxCmpOpF32(context, CmpCondition.LessThan, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareLTFpscr), true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCmpOpI32(context, context.ICompareLess, context.ICompareLessUI, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitCmpOpF32(ArmEmitterContext context, string name, bool zero)
|
||||
{
|
||||
if (zero)
|
||||
{
|
||||
EmitVectorUnaryOpF32(context, (m) =>
|
||||
{
|
||||
Operand zeroOp = m.Type == OperandType.FP64 ? ConstF(0.0d) : ConstF(0.0f);
|
||||
|
||||
return EmitSoftFloatCallDefaultFpscr(context, name, m, zeroOp);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpF32(context, (n, m) =>
|
||||
{
|
||||
return EmitSoftFloatCallDefaultFpscr(context, name, n, m);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand ZerosOrOnes(ArmEmitterContext context, Operand fromBool, OperandType baseType)
|
||||
{
|
||||
var ones = (baseType == OperandType.I64) ? Const(-1L) : Const(-1);
|
||||
|
||||
return context.ConditionalSelect(fromBool, ones, Const(baseType, 0L));
|
||||
}
|
||||
|
||||
private static void EmitCmpOpI32(
|
||||
ArmEmitterContext context,
|
||||
Func2I signedOp,
|
||||
Func2I unsignedOp,
|
||||
bool zero,
|
||||
bool signed)
|
||||
{
|
||||
if (zero)
|
||||
{
|
||||
if (signed)
|
||||
{
|
||||
EmitVectorUnaryOpSx32(context, (m) =>
|
||||
{
|
||||
OperandType type = m.Type;
|
||||
Operand zeroV = (type == OperandType.I64) ? Const(0L) : Const(0);
|
||||
|
||||
return ZerosOrOnes(context, signedOp(m, zeroV), type);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (m) =>
|
||||
{
|
||||
OperandType type = m.Type;
|
||||
Operand zeroV = (type == OperandType.I64) ? Const(0L) : Const(0);
|
||||
|
||||
return ZerosOrOnes(context, unsignedOp(m, zeroV), type);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (signed)
|
||||
{
|
||||
EmitVectorBinaryOpSx32(context, (n, m) => ZerosOrOnes(context, signedOp(n, m), n.Type));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx32(context, (n, m) => ZerosOrOnes(context, unsignedOp(n, m), n.Type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vcmp(ArmEmitterContext context)
|
||||
{
|
||||
EmitVcmpOrVcmpe(context, false);
|
||||
}
|
||||
|
||||
public static void Vcmpe(ArmEmitterContext context)
|
||||
{
|
||||
EmitVcmpOrVcmpe(context, true);
|
||||
}
|
||||
|
||||
private static void EmitVcmpOrVcmpe(ArmEmitterContext context, bool signalNaNs)
|
||||
{
|
||||
OpCode32SimdS op = (OpCode32SimdS)context.CurrOp;
|
||||
|
||||
bool cmpWithZero = (op.Opc & 2) != 0;
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
if (Optimizations.FastFP && (signalNaNs ? Optimizations.UseAvx : Optimizations.UseSse2))
|
||||
{
|
||||
CmpCondition cmpOrdered = signalNaNs ? CmpCondition.OrderedS : CmpCondition.OrderedQ;
|
||||
|
||||
bool doubleSize = sizeF != 0;
|
||||
int shift = doubleSize ? 1 : 2;
|
||||
Operand m = GetVecA32(op.Vm >> shift);
|
||||
Operand n = GetVecA32(op.Vd >> shift);
|
||||
|
||||
n = EmitSwapScalar(context, n, op.Vd, doubleSize);
|
||||
m = cmpWithZero ? context.VectorZero() : EmitSwapScalar(context, m, op.Vm, doubleSize);
|
||||
|
||||
Operand lblNaN = Label();
|
||||
Operand lblEnd = Label();
|
||||
|
||||
if (!doubleSize)
|
||||
{
|
||||
Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpss, n, m, Const((int)cmpOrdered));
|
||||
|
||||
Operand isOrdered = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, ordMask);
|
||||
|
||||
context.BranchIfFalse(lblNaN, isOrdered);
|
||||
|
||||
Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comissge, n, m);
|
||||
Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisseq, n, m);
|
||||
Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisslt, n, m);
|
||||
|
||||
SetFpFlag(context, FPState.VFlag, Const(0));
|
||||
SetFpFlag(context, FPState.CFlag, cf);
|
||||
SetFpFlag(context, FPState.ZFlag, zf);
|
||||
SetFpFlag(context, FPState.NFlag, nf);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, m, Const((int)cmpOrdered));
|
||||
|
||||
Operand isOrdered = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, ordMask);
|
||||
|
||||
context.BranchIfFalse(lblNaN, isOrdered);
|
||||
|
||||
Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comisdge, n, m);
|
||||
Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisdeq, n, m);
|
||||
Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisdlt, n, m);
|
||||
|
||||
SetFpFlag(context, FPState.VFlag, Const(0));
|
||||
SetFpFlag(context, FPState.CFlag, cf);
|
||||
SetFpFlag(context, FPState.ZFlag, zf);
|
||||
SetFpFlag(context, FPState.NFlag, nf);
|
||||
}
|
||||
|
||||
context.Branch(lblEnd);
|
||||
|
||||
context.MarkLabel(lblNaN);
|
||||
|
||||
SetFpFlag(context, FPState.VFlag, Const(1));
|
||||
SetFpFlag(context, FPState.CFlag, Const(1));
|
||||
SetFpFlag(context, FPState.ZFlag, Const(0));
|
||||
SetFpFlag(context, FPState.NFlag, Const(0));
|
||||
|
||||
context.MarkLabel(lblEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32;
|
||||
|
||||
Operand ne = ExtractScalar(context, type, op.Vd);
|
||||
Operand me;
|
||||
|
||||
if (cmpWithZero)
|
||||
{
|
||||
me = sizeF == 0 ? ConstF(0f) : ConstF(0d);
|
||||
}
|
||||
else
|
||||
{
|
||||
me = ExtractScalar(context, type, op.Vm);
|
||||
}
|
||||
|
||||
Operand nzcv = EmitSoftFloatCall(context, nameof(SoftFloat32.FPCompare), ne, me, Const(signalNaNs));
|
||||
|
||||
EmitSetFpscrNzcv(context, nzcv);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitSetFpscrNzcv(ArmEmitterContext context, Operand nzcv)
|
||||
{
|
||||
Operand Extract(Operand value, int bit)
|
||||
{
|
||||
if (bit != 0)
|
||||
{
|
||||
value = context.ShiftRightUI(value, Const(bit));
|
||||
}
|
||||
|
||||
value = context.BitwiseAnd(value, Const(1));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
SetFpFlag(context, FPState.VFlag, Extract(nzcv, 0));
|
||||
SetFpFlag(context, FPState.CFlag, Extract(nzcv, 1));
|
||||
SetFpFlag(context, FPState.ZFlag, Extract(nzcv, 2));
|
||||
SetFpFlag(context, FPState.NFlag, Extract(nzcv, 3));
|
||||
}
|
||||
|
||||
private static void EmitSse2OrAvxCmpOpF32(ArmEmitterContext context, CmpCondition cond, bool zero)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
int sizeF = op.Size & 1;
|
||||
Intrinsic inst = (sizeF == 0) ? Intrinsic.X86Cmpps : Intrinsic.X86Cmppd;
|
||||
|
||||
if (zero)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (m) =>
|
||||
{
|
||||
return context.AddIntrinsic(inst, m, context.VectorZero(), Const((int)cond));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpSimd32(context, (n, m) =>
|
||||
{
|
||||
return context.AddIntrinsic(inst, n, m, Const((int)cond));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,712 +0,0 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper32;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
static partial class InstEmit32
|
||||
{
|
||||
private static int FlipVdBits(int vd, bool lowBit)
|
||||
{
|
||||
if (lowBit)
|
||||
{
|
||||
// Move the low bit to the top.
|
||||
return ((vd & 0x1) << 4) | (vd >> 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move the high bit to the bottom.
|
||||
return ((vd & 0xf) << 1) | (vd >> 4);
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand EmitSaturateFloatToInt(ArmEmitterContext context, Operand op1, bool unsigned)
|
||||
{
|
||||
MethodInfo info;
|
||||
|
||||
if (op1.Type == OperandType.FP64)
|
||||
{
|
||||
info = unsigned
|
||||
? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU32))
|
||||
: typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS32));
|
||||
}
|
||||
else
|
||||
{
|
||||
info = unsigned
|
||||
? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32))
|
||||
: typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32));
|
||||
}
|
||||
|
||||
return context.Call(info, op1);
|
||||
}
|
||||
|
||||
public static void Vcvt_V(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
bool unsigned = (op.Opc & 1) != 0;
|
||||
bool toInteger = (op.Opc & 2) != 0;
|
||||
OperandType floatSize = (op.Size == 2) ? OperandType.FP32 : OperandType.FP64;
|
||||
|
||||
if (toInteger)
|
||||
{
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41ConvertVector32(context, FPRoundingMode.TowardsZero, !unsigned);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpF32(context, (op1) =>
|
||||
{
|
||||
return EmitSaturateFloatToInt(context, op1, unsigned);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (n) =>
|
||||
{
|
||||
if (unsigned)
|
||||
{
|
||||
Operand mask = X86GetAllElements(context, 0x47800000);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Psrld, n, Const(16));
|
||||
res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Mulps, res, mask);
|
||||
|
||||
Operand res2 = context.AddIntrinsic(Intrinsic.X86Pslld, n, Const(16));
|
||||
res2 = context.AddIntrinsic(Intrinsic.X86Psrld, res2, Const(16));
|
||||
res2 = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res2);
|
||||
|
||||
return context.AddIntrinsic(Intrinsic.X86Addps, res, res2);
|
||||
}
|
||||
else
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unsigned)
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) => EmitFPConvert(context, op1, floatSize, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpSx32(context, (op1) => EmitFPConvert(context, op1, floatSize, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vcvt_FD(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdS op = (OpCode32SimdS)context.CurrOp;
|
||||
|
||||
int vm = op.Vm;
|
||||
int vd;
|
||||
if (op.Size == 3)
|
||||
{
|
||||
vd = FlipVdBits(op.Vd, false);
|
||||
// Double to single.
|
||||
Operand fp = ExtractScalar(context, OperandType.FP64, vm);
|
||||
|
||||
Operand res = context.ConvertToFP(OperandType.FP32, fp);
|
||||
|
||||
InsertScalar(context, vd, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
vd = FlipVdBits(op.Vd, true);
|
||||
// Single to double.
|
||||
Operand fp = ExtractScalar(context, OperandType.FP32, vm);
|
||||
|
||||
Operand res = context.ConvertToFP(OperandType.FP64, fp);
|
||||
|
||||
InsertScalar(context, vd, res);
|
||||
}
|
||||
}
|
||||
|
||||
// VCVT (floating-point to integer, floating-point) | VCVT (integer to floating-point, floating-point).
|
||||
public static void Vcvt_FI(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp;
|
||||
|
||||
bool toInteger = (op.Opc2 & 0b100) != 0;
|
||||
|
||||
OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32;
|
||||
|
||||
if (toInteger)
|
||||
{
|
||||
bool unsigned = (op.Opc2 & 1) == 0;
|
||||
bool roundWithFpscr = op.Opc != 1;
|
||||
|
||||
if (!roundWithFpscr && Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41ConvertInt32(context, FPRoundingMode.TowardsZero, !unsigned);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand toConvert = ExtractScalar(context, floatSize, op.Vm);
|
||||
|
||||
// TODO: Fast Path.
|
||||
if (roundWithFpscr)
|
||||
{
|
||||
toConvert = EmitRoundByRMode(context, toConvert);
|
||||
}
|
||||
|
||||
// Round towards zero.
|
||||
Operand asInteger = EmitSaturateFloatToInt(context, toConvert, unsigned);
|
||||
|
||||
InsertScalar(context, op.Vd, asInteger);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool unsigned = op.Opc == 0;
|
||||
|
||||
Operand toConvert = ExtractScalar(context, OperandType.I32, op.Vm);
|
||||
|
||||
Operand asFloat = EmitFPConvert(context, toConvert, floatSize, !unsigned);
|
||||
|
||||
InsertScalar(context, op.Vd, asFloat);
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand EmitRoundMathCall(ArmEmitterContext context, MidpointRounding roundMode, Operand n)
|
||||
{
|
||||
IOpCode32Simd op = (IOpCode32Simd)context.CurrOp;
|
||||
|
||||
string name = nameof(Math.Round);
|
||||
|
||||
MethodInfo info = (op.Size & 1) == 0
|
||||
? typeof(MathF).GetMethod(name, new Type[] { typeof(float), typeof(MidpointRounding) })
|
||||
: typeof(Math). GetMethod(name, new Type[] { typeof(double), typeof(MidpointRounding) });
|
||||
|
||||
return context.Call(info, n, Const((int)roundMode));
|
||||
}
|
||||
|
||||
private static FPRoundingMode RMToRoundMode(int rm)
|
||||
{
|
||||
FPRoundingMode roundMode;
|
||||
switch (rm)
|
||||
{
|
||||
case 0b00:
|
||||
roundMode = FPRoundingMode.ToNearestAway;
|
||||
break;
|
||||
case 0b01:
|
||||
roundMode = FPRoundingMode.ToNearest;
|
||||
break;
|
||||
case 0b10:
|
||||
roundMode = FPRoundingMode.TowardsPlusInfinity;
|
||||
break;
|
||||
case 0b11:
|
||||
roundMode = FPRoundingMode.TowardsMinusInfinity;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(rm));
|
||||
}
|
||||
return roundMode;
|
||||
}
|
||||
|
||||
// VCVTA/M/N/P (floating-point).
|
||||
public static void Vcvt_RM(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp; // toInteger == true (opCode<18> == 1 => Opc2<2> == 1).
|
||||
|
||||
OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32;
|
||||
|
||||
bool unsigned = op.Opc == 0;
|
||||
int rm = op.Opc2 & 3;
|
||||
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitSse41ConvertInt32(context, RMToRoundMode(rm), !unsigned);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand toConvert = ExtractScalar(context, floatSize, op.Vm);
|
||||
|
||||
switch (rm)
|
||||
{
|
||||
case 0b00: // Away
|
||||
toConvert = EmitRoundMathCall(context, MidpointRounding.AwayFromZero, toConvert);
|
||||
break;
|
||||
case 0b01: // Nearest
|
||||
toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert);
|
||||
break;
|
||||
case 0b10: // Towards positive infinity
|
||||
toConvert = EmitUnaryMathCall(context, nameof(Math.Ceiling), toConvert);
|
||||
break;
|
||||
case 0b11: // Towards negative infinity
|
||||
toConvert = EmitUnaryMathCall(context, nameof(Math.Floor), toConvert);
|
||||
break;
|
||||
}
|
||||
|
||||
Operand asInteger = EmitSaturateFloatToInt(context, toConvert, unsigned);
|
||||
|
||||
InsertScalar(context, op.Vd, asInteger);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vcvt_TB(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdCvtTB op = (OpCode32SimdCvtTB)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseF16c)
|
||||
{
|
||||
Debug.Assert(!Optimizations.ForceLegacySse);
|
||||
|
||||
if (op.Op)
|
||||
{
|
||||
Operand res = ExtractScalar(context, op.Size == 1 ? OperandType.FP64 : OperandType.FP32, op.Vm);
|
||||
if (op.Size == 1)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), res);
|
||||
}
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, res, Const(X86GetRoundControl(FPRoundingMode.ToNearest)));
|
||||
res = context.VectorExtract16(res, 0);
|
||||
InsertScalar16(context, op.Vd, op.T, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand res = context.VectorCreateScalar(ExtractScalar16(context, op.Vm, op.T));
|
||||
res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, res);
|
||||
if (op.Size == 1)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), res);
|
||||
}
|
||||
res = context.VectorExtract(op.Size == 1 ? OperandType.I64 : OperandType.I32, res, 0);
|
||||
InsertScalar(context, op.Vd, res);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (op.Op)
|
||||
{
|
||||
// Convert to half.
|
||||
|
||||
Operand src = ExtractScalar(context, op.Size == 1 ? OperandType.FP64 : OperandType.FP32, op.Vm);
|
||||
|
||||
MethodInfo method = op.Size == 1
|
||||
? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))
|
||||
: typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert));
|
||||
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(method, src);
|
||||
context.LoadFromContext();
|
||||
|
||||
InsertScalar16(context, op.Vd, op.T, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert from half.
|
||||
|
||||
Operand src = ExtractScalar16(context, op.Vm, op.T);
|
||||
|
||||
MethodInfo method = op.Size == 1
|
||||
? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))
|
||||
: typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert));
|
||||
|
||||
context.StoreToContext();
|
||||
Operand res = context.Call(method, src);
|
||||
context.LoadFromContext();
|
||||
|
||||
InsertScalar(context, op.Vd, res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTA/M/N/P (floating-point).
|
||||
public static void Vrint_RM(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdS op = (OpCode32SimdS)context.CurrOp;
|
||||
|
||||
OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32;
|
||||
|
||||
int rm = op.Opc2 & 3;
|
||||
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitScalarUnaryOpSimd32(context, (m) =>
|
||||
{
|
||||
FPRoundingMode roundMode = RMToRoundMode(rm);
|
||||
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
Intrinsic inst = (op.Size & 1) == 0 ? Intrinsic.X86Roundss : Intrinsic.X86Roundsd;
|
||||
return context.AddIntrinsic(inst, m, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return EmitSse41RoundToNearestWithTiesToAwayOpF(context, m, scalar: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand toConvert = ExtractScalar(context, floatSize, op.Vm);
|
||||
|
||||
switch (rm)
|
||||
{
|
||||
case 0b00: // Away
|
||||
toConvert = EmitRoundMathCall(context, MidpointRounding.AwayFromZero, toConvert);
|
||||
break;
|
||||
case 0b01: // Nearest
|
||||
toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert);
|
||||
break;
|
||||
case 0b10: // Towards positive infinity
|
||||
toConvert = EmitUnaryMathCall(context, nameof(Math.Ceiling), toConvert);
|
||||
break;
|
||||
case 0b11: // Towards negative infinity
|
||||
toConvert = EmitUnaryMathCall(context, nameof(Math.Floor), toConvert);
|
||||
break;
|
||||
}
|
||||
|
||||
InsertScalar(context, op.Vd, toConvert);
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTA (vector).
|
||||
public static void Vrinta_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse41)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (m) =>
|
||||
{
|
||||
return EmitSse41RoundToNearestWithTiesToAwayOpF(context, m, scalar: false);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpF32(context, (m) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, m));
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTM (vector).
|
||||
public static void Vrintm_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (m) =>
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Roundps, m, Const(X86GetRoundControl(FPRoundingMode.TowardsMinusInfinity)));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(Math.Floor), m));
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTN (vector).
|
||||
public static void Vrintn_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (m) =>
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Roundps, m, Const(X86GetRoundControl(FPRoundingMode.ToNearest)));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpF32(context, (m) => EmitRoundMathCall(context, MidpointRounding.ToEven, m));
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTP (vector).
|
||||
public static void Vrintp_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (m) =>
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Roundps, m, Const(X86GetRoundControl(FPRoundingMode.TowardsPlusInfinity)));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(Math.Ceiling), m));
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTZ (floating-point).
|
||||
public static void Vrint_Z(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdS op = (OpCode32SimdS)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitScalarUnaryOpSimd32(context, (m) =>
|
||||
{
|
||||
Intrinsic inst = (op.Size & 1) == 0 ? Intrinsic.X86Roundss : Intrinsic.X86Roundsd;
|
||||
return context.AddIntrinsic(inst, m, Const(X86GetRoundControl(FPRoundingMode.TowardsZero)));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Truncate), op1));
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTX (floating-point).
|
||||
public static void Vrintx_S(ArmEmitterContext context)
|
||||
{
|
||||
EmitScalarUnaryOpF32(context, (op1) =>
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
}
|
||||
|
||||
private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, OperandType type, bool signed)
|
||||
{
|
||||
Debug.Assert(value.Type == OperandType.I32 || value.Type == OperandType.I64);
|
||||
|
||||
if (signed)
|
||||
{
|
||||
return context.ConvertToFP(type, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return context.ConvertToFPUI(type, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitSse41ConvertInt32(ArmEmitterContext context, FPRoundingMode roundMode, bool signed)
|
||||
{
|
||||
// A port of the similar round function in InstEmitSimdCvt.
|
||||
OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp;
|
||||
|
||||
bool doubleSize = (op.Size & 1) != 0;
|
||||
int shift = doubleSize ? 1 : 2;
|
||||
Operand n = GetVecA32(op.Vm >> shift);
|
||||
n = EmitSwapScalar(context, n, op.Vm, doubleSize);
|
||||
|
||||
if (!doubleSize)
|
||||
{
|
||||
Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n);
|
||||
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true);
|
||||
}
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
|
||||
Operand nCmp;
|
||||
Operand nIntOrLong2 = default;
|
||||
|
||||
if (!signed)
|
||||
{
|
||||
nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp);
|
||||
}
|
||||
|
||||
int fpMaxVal = 0x4F000000; // 2.14748365E9f (2147483648)
|
||||
|
||||
Operand fpMaxValMask = X86GetScalar(context, fpMaxVal);
|
||||
|
||||
Operand nIntOrLong = context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes);
|
||||
|
||||
if (!signed)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Subss, nRes, fpMaxValMask);
|
||||
|
||||
nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp);
|
||||
|
||||
nIntOrLong2 = context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan));
|
||||
|
||||
Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes);
|
||||
|
||||
Operand dRes;
|
||||
if (signed)
|
||||
{
|
||||
dRes = context.BitwiseExclusiveOr(nIntOrLong, nInt);
|
||||
}
|
||||
else
|
||||
{
|
||||
dRes = context.BitwiseExclusiveOr(nIntOrLong2, nInt);
|
||||
dRes = context.Add(dRes, nIntOrLong);
|
||||
}
|
||||
|
||||
InsertScalar(context, op.Vd, dRes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n);
|
||||
|
||||
if (roundMode != FPRoundingMode.ToNearestAway)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true);
|
||||
}
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
|
||||
Operand nCmp;
|
||||
Operand nIntOrLong2 = default;
|
||||
|
||||
if (!signed)
|
||||
{
|
||||
nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp);
|
||||
}
|
||||
|
||||
long fpMaxVal = 0x41E0000000000000L; // 2147483648.0000000d (2147483648)
|
||||
|
||||
Operand fpMaxValMask = X86GetScalar(context, fpMaxVal);
|
||||
|
||||
Operand nIntOrLong = context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes);
|
||||
|
||||
if (!signed)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Subsd, nRes, fpMaxValMask);
|
||||
|
||||
nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp);
|
||||
|
||||
nIntOrLong2 = context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan));
|
||||
|
||||
Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes);
|
||||
nLong = context.ConvertI64ToI32(nLong);
|
||||
|
||||
Operand dRes;
|
||||
if (signed)
|
||||
{
|
||||
dRes = context.BitwiseExclusiveOr(nIntOrLong, nLong);
|
||||
}
|
||||
else
|
||||
{
|
||||
dRes = context.BitwiseExclusiveOr(nIntOrLong2, nLong);
|
||||
dRes = context.Add(dRes, nIntOrLong);
|
||||
}
|
||||
|
||||
InsertScalar(context, op.Vd, dRes);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitSse41ConvertVector32(ArmEmitterContext context, FPRoundingMode roundMode, bool signed)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
EmitVectorUnaryOpSimd32(context, (n) =>
|
||||
{
|
||||
int sizeF = op.Size & 1;
|
||||
|
||||
if (sizeF == 0)
|
||||
{
|
||||
Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n);
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
Operand nCmp;
|
||||
if (!signed)
|
||||
{
|
||||
nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp);
|
||||
}
|
||||
|
||||
Operand fpMaxValMask = X86GetAllElements(context, 0x4F000000); // 2.14748365E9f (2147483648)
|
||||
|
||||
Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes);
|
||||
Operand nInt2 = default;
|
||||
|
||||
if (!signed)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Subps, nRes, fpMaxValMask);
|
||||
|
||||
nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp);
|
||||
|
||||
nInt2 = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan));
|
||||
|
||||
if (signed)
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Pxor, nInt, nRes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt2, nRes);
|
||||
return context.AddIntrinsic(Intrinsic.X86Paddd, dRes, nInt);
|
||||
}
|
||||
}
|
||||
else /* if (sizeF == 1) */
|
||||
{
|
||||
Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n);
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode)));
|
||||
|
||||
Operand zero = context.VectorZero();
|
||||
Operand nCmp;
|
||||
if (!signed)
|
||||
{
|
||||
nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp);
|
||||
}
|
||||
|
||||
Operand fpMaxValMask = X86GetAllElements(context, 0x43E0000000000000L); // 9.2233720368547760E18d (9223372036854775808)
|
||||
|
||||
Operand nLong = InstEmit.EmitSse2CvtDoubleToInt64OpF(context, nRes, false);
|
||||
Operand nLong2 = default;
|
||||
|
||||
if (!signed)
|
||||
{
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Subpd, nRes, fpMaxValMask);
|
||||
|
||||
nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual));
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp);
|
||||
|
||||
nLong2 = InstEmit.EmitSse2CvtDoubleToInt64OpF(context, nRes, false);
|
||||
}
|
||||
|
||||
nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan));
|
||||
|
||||
if (signed)
|
||||
{
|
||||
return context.AddIntrinsic(Intrinsic.X86Pxor, nLong, nRes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong2, nRes);
|
||||
return context.AddIntrinsic(Intrinsic.X86Paddq, dRes, nLong);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,543 +0,0 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
static partial class InstEmit
|
||||
{
|
||||
public static void And_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pand, n, m);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx(context, (op1, op2) => context.BitwiseAnd(op1, op2));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Bic_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pandn, m, n);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx(context, (op1, op2) =>
|
||||
{
|
||||
return context.BitwiseAnd(op1, context.BitwiseNot(op2));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Bic_Vi(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp;
|
||||
|
||||
int eSize = 8 << op.Size;
|
||||
|
||||
Operand d = GetVec(op.Rd);
|
||||
Operand imm = eSize switch {
|
||||
16 => X86GetAllElements(context, (short)~op.Immediate),
|
||||
32 => X86GetAllElements(context, (int)~op.Immediate),
|
||||
_ => throw new InvalidOperationException($"Invalid element size {eSize}.")
|
||||
};
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pand, d, imm);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorImmBinaryOp(context, (op1, op2) =>
|
||||
{
|
||||
return context.BitwiseAnd(op1, context.BitwiseNot(op2));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Bif_V(ArmEmitterContext context)
|
||||
{
|
||||
EmitBifBit(context, notRm: true);
|
||||
}
|
||||
|
||||
public static void Bit_V(ArmEmitterContext context)
|
||||
{
|
||||
EmitBifBit(context, notRm: false);
|
||||
}
|
||||
|
||||
private static void EmitBifBit(ArmEmitterContext context, bool notRm)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand d = GetVec(op.Rd);
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, d);
|
||||
|
||||
if (notRm)
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pandn, m, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pand, m, res);
|
||||
}
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pxor, d, res);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand res = context.VectorZero();
|
||||
|
||||
int elems = op.RegisterSize == RegisterSize.Simd128 ? 2 : 1;
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand d = EmitVectorExtractZx(context, op.Rd, index, 3);
|
||||
Operand n = EmitVectorExtractZx(context, op.Rn, index, 3);
|
||||
Operand m = EmitVectorExtractZx(context, op.Rm, index, 3);
|
||||
|
||||
if (notRm)
|
||||
{
|
||||
m = context.BitwiseNot(m);
|
||||
}
|
||||
|
||||
Operand e = context.BitwiseExclusiveOr(d, n);
|
||||
|
||||
e = context.BitwiseAnd(e, m);
|
||||
e = context.BitwiseExclusiveOr(e, d);
|
||||
|
||||
res = EmitVectorInsert(context, res, e, index, 3);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Bsl_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand d = GetVec(op.Rd);
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pand, res, d);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pxor, res, m);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(d, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorTernaryOpZx(context, (op1, op2, op3) =>
|
||||
{
|
||||
return context.BitwiseExclusiveOr(
|
||||
context.BitwiseAnd(op1,
|
||||
context.BitwiseExclusiveOr(op2, op3)), op3);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Eor_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, m);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx(context, (op1, op2) => context.BitwiseExclusiveOr(op1, op2));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Not_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Operand mask = X86GetAllElements(context, -1L);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pandn, n, mask);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpZx(context, (op1) => context.BitwiseNot(op1));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Orn_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand mask = X86GetAllElements(context, -1L);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pandn, m, mask);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Por, res, n);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx(context, (op1, op2) =>
|
||||
{
|
||||
return context.BitwiseOr(op1, context.BitwiseNot(op2));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Orr_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
Operand m = GetVec(op.Rm);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Por, n, m);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx(context, (op1, op2) => context.BitwiseOr(op1, op2));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Orr_Vi(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp;
|
||||
|
||||
int eSize = 8 << op.Size;
|
||||
|
||||
Operand d = GetVec(op.Rd);
|
||||
Operand imm = eSize switch {
|
||||
16 => X86GetAllElements(context, (short)op.Immediate),
|
||||
32 => X86GetAllElements(context, (int)op.Immediate),
|
||||
_ => throw new InvalidOperationException($"Invalid element size {eSize}.")
|
||||
};
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Por, d, imm);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorImmBinaryOp(context, (op1, op2) => context.BitwiseOr(op1, op2));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Rbit_V(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseGfni)
|
||||
{
|
||||
const long bitMatrix =
|
||||
(0b10000000L << 56) |
|
||||
(0b01000000L << 48) |
|
||||
(0b00100000L << 40) |
|
||||
(0b00010000L << 32) |
|
||||
(0b00001000L << 24) |
|
||||
(0b00000100L << 16) |
|
||||
(0b00000010L << 8) |
|
||||
(0b00000001L << 0);
|
||||
|
||||
Operand vBitMatrix = X86GetAllElements(context, bitMatrix);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, GetVec(op.Rn), vBitMatrix, Const(0));
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand res = context.VectorZero();
|
||||
int elems = op.RegisterSize == RegisterSize.Simd128 ? 16 : 8;
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand ne = EmitVectorExtractZx(context, op.Rn, index, 0);
|
||||
|
||||
Operand de = EmitReverseBits8Op(context, ne);
|
||||
|
||||
res = EmitVectorInsert(context, res, de, index, 0);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand EmitReverseBits8Op(ArmEmitterContext context, Operand op)
|
||||
{
|
||||
Debug.Assert(op.Type == OperandType.I64);
|
||||
|
||||
Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaul)), Const(1)),
|
||||
context.ShiftLeft (context.BitwiseAnd(op, Const(0x55ul)), Const(1)));
|
||||
|
||||
val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccul)), Const(2)),
|
||||
context.ShiftLeft (context.BitwiseAnd(val, Const(0x33ul)), Const(2)));
|
||||
|
||||
return context.BitwiseOr(context.ShiftRightUI(val, Const(4)),
|
||||
context.ShiftLeft (context.BitwiseAnd(val, Const(0x0ful)), Const(4)));
|
||||
}
|
||||
|
||||
public static void Rev16_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSsse3)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
const long maskE0 = 06L << 56 | 07L << 48 | 04L << 40 | 05L << 32 | 02L << 24 | 03L << 16 | 00L << 8 | 01L << 0;
|
||||
const long maskE1 = 14L << 56 | 15L << 48 | 12L << 40 | 13L << 32 | 10L << 24 | 11L << 16 | 08L << 8 | 09L << 0;
|
||||
|
||||
Operand mask = X86GetScalar(context, maskE0);
|
||||
|
||||
mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3);
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitRev_V(context, containerSize: 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Rev32_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSsse3)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Operand mask;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
const long maskE0 = 04L << 56 | 05L << 48 | 06L << 40 | 07L << 32 | 00L << 24 | 01L << 16 | 02L << 8 | 03L << 0;
|
||||
const long maskE1 = 12L << 56 | 13L << 48 | 14L << 40 | 15L << 32 | 08L << 24 | 09L << 16 | 10L << 8 | 11L << 0;
|
||||
|
||||
mask = X86GetScalar(context, maskE0);
|
||||
|
||||
mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3);
|
||||
}
|
||||
else /* if (op.Size == 1) */
|
||||
{
|
||||
const long maskE0 = 05L << 56 | 04L << 48 | 07L << 40 | 06L << 32 | 01L << 24 | 00L << 16 | 03L << 8 | 02L << 0;
|
||||
const long maskE1 = 13L << 56 | 12L << 48 | 15L << 40 | 14L << 32 | 09L << 24 | 08L << 16 | 11L << 8 | 10L << 0;
|
||||
|
||||
mask = X86GetScalar(context, maskE0);
|
||||
|
||||
mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3);
|
||||
}
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitRev_V(context, containerSize: 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Rev64_V(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSsse3)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand n = GetVec(op.Rn);
|
||||
|
||||
Operand mask;
|
||||
|
||||
if (op.Size == 0)
|
||||
{
|
||||
const long maskE0 = 00L << 56 | 01L << 48 | 02L << 40 | 03L << 32 | 04L << 24 | 05L << 16 | 06L << 8 | 07L << 0;
|
||||
const long maskE1 = 08L << 56 | 09L << 48 | 10L << 40 | 11L << 32 | 12L << 24 | 13L << 16 | 14L << 8 | 15L << 0;
|
||||
|
||||
mask = X86GetScalar(context, maskE0);
|
||||
|
||||
mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3);
|
||||
}
|
||||
else if (op.Size == 1)
|
||||
{
|
||||
const long maskE0 = 01L << 56 | 00L << 48 | 03L << 40 | 02L << 32 | 05L << 24 | 04L << 16 | 07L << 8 | 06L << 0;
|
||||
const long maskE1 = 09L << 56 | 08L << 48 | 11L << 40 | 10L << 32 | 13L << 24 | 12L << 16 | 15L << 8 | 14L << 0;
|
||||
|
||||
mask = X86GetScalar(context, maskE0);
|
||||
|
||||
mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3);
|
||||
}
|
||||
else /* if (op.Size == 2) */
|
||||
{
|
||||
const long maskE0 = 03L << 56 | 02L << 48 | 01L << 40 | 00L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0;
|
||||
const long maskE1 = 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 15L << 24 | 14L << 16 | 13L << 8 | 12L << 0;
|
||||
|
||||
mask = X86GetScalar(context, maskE0);
|
||||
|
||||
mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3);
|
||||
}
|
||||
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask);
|
||||
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
res = context.VectorZeroUpper64(res);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitRev_V(context, containerSize: 3);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitRev_V(ArmEmitterContext context, int containerSize)
|
||||
{
|
||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||
|
||||
Operand res = context.VectorZero();
|
||||
|
||||
int elems = op.GetBytesCount() >> op.Size;
|
||||
|
||||
int containerMask = (1 << (containerSize - op.Size)) - 1;
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
int revIndex = index ^ containerMask;
|
||||
|
||||
Operand ne = EmitVectorExtractZx(context, op.Rn, revIndex, op.Size);
|
||||
|
||||
res = EmitVectorInsert(context, res, ne, index, op.Size);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper32;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
static partial class InstEmit32
|
||||
{
|
||||
public static void Vand_I(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Pand, n, m));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseAnd(op1, op2));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vbic_I(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Pandn, m, n));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseAnd(op1, context.BitwiseNot(op2)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vbic_II(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdImm op = (OpCode32SimdImm)context.CurrOp;
|
||||
|
||||
long immediate = op.Immediate;
|
||||
|
||||
// Replicate fields to fill the 64-bits, if size is < 64-bits.
|
||||
switch (op.Size)
|
||||
{
|
||||
case 0: immediate *= 0x0101010101010101L; break;
|
||||
case 1: immediate *= 0x0001000100010001L; break;
|
||||
case 2: immediate *= 0x0000000100000001L; break;
|
||||
}
|
||||
|
||||
Operand imm = Const(immediate);
|
||||
Operand res = GetVecA32(op.Qd);
|
||||
|
||||
if (op.Q)
|
||||
{
|
||||
for (int elem = 0; elem < 2; elem++)
|
||||
{
|
||||
Operand de = EmitVectorExtractZx(context, op.Qd, elem, 3);
|
||||
|
||||
res = EmitVectorInsert(context, res, context.BitwiseAnd(de, context.BitwiseNot(imm)), elem, 3);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand de = EmitVectorExtractZx(context, op.Qd, op.Vd & 1, 3);
|
||||
|
||||
res = EmitVectorInsert(context, res, context.BitwiseAnd(de, context.BitwiseNot(imm)), op.Vd & 1, 3);
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
public static void Vbif(ArmEmitterContext context)
|
||||
{
|
||||
EmitBifBit(context, true);
|
||||
}
|
||||
|
||||
public static void Vbit(ArmEmitterContext context)
|
||||
{
|
||||
EmitBifBit(context, false);
|
||||
}
|
||||
|
||||
public static void Vbsl(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorTernaryOpSimd32(context, (d, n, m) =>
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, m);
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pand, res, d);
|
||||
return context.AddIntrinsic(Intrinsic.X86Pxor, res, m);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorTernaryOpZx32(context, (op1, op2, op3) =>
|
||||
{
|
||||
return context.BitwiseExclusiveOr(
|
||||
context.BitwiseAnd(op1,
|
||||
context.BitwiseExclusiveOr(op2, op3)), op3);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void Veor_I(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Pxor, n, m));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseExclusiveOr(op1, op2));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vorn_I(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
Operand mask = context.VectorOne();
|
||||
|
||||
EmitVectorBinaryOpSimd32(context, (n, m) =>
|
||||
{
|
||||
m = context.AddIntrinsic(Intrinsic.X86Pandn, m, mask);
|
||||
return context.AddIntrinsic(Intrinsic.X86Por, n, m);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseOr(op1, context.BitwiseNot(op2)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vorr_I(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Por, n, m));
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseOr(op1, op2));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vorr_II(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdImm op = (OpCode32SimdImm)context.CurrOp;
|
||||
|
||||
long immediate = op.Immediate;
|
||||
|
||||
// Replicate fields to fill the 64-bits, if size is < 64-bits.
|
||||
switch (op.Size)
|
||||
{
|
||||
case 0: immediate *= 0x0101010101010101L; break;
|
||||
case 1: immediate *= 0x0001000100010001L; break;
|
||||
case 2: immediate *= 0x0000000100000001L; break;
|
||||
}
|
||||
|
||||
Operand imm = Const(immediate);
|
||||
Operand res = GetVecA32(op.Qd);
|
||||
|
||||
if (op.Q)
|
||||
{
|
||||
for (int elem = 0; elem < 2; elem++)
|
||||
{
|
||||
Operand de = EmitVectorExtractZx(context, op.Qd, elem, 3);
|
||||
|
||||
res = EmitVectorInsert(context, res, context.BitwiseOr(de, imm), elem, 3);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand de = EmitVectorExtractZx(context, op.Qd, op.Vd & 1, 3);
|
||||
|
||||
res = EmitVectorInsert(context, res, context.BitwiseOr(de, imm), op.Vd & 1, 3);
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
public static void Vtst(ArmEmitterContext context)
|
||||
{
|
||||
EmitVectorBinaryOpZx32(context, (op1, op2) =>
|
||||
{
|
||||
Operand isZero = context.ICompareEqual(context.BitwiseAnd(op1, op2), Const(0));
|
||||
return context.ConditionalSelect(isZero, Const(0), Const(-1));
|
||||
});
|
||||
}
|
||||
|
||||
private static void EmitBifBit(ArmEmitterContext context, bool notRm)
|
||||
{
|
||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorTernaryOpSimd32(context, (d, n, m) =>
|
||||
{
|
||||
Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, d);
|
||||
res = context.AddIntrinsic((notRm) ? Intrinsic.X86Pandn : Intrinsic.X86Pand, m, res);
|
||||
return context.AddIntrinsic(Intrinsic.X86Pxor, d, res);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorTernaryOpZx32(context, (d, n, m) =>
|
||||
{
|
||||
if (notRm)
|
||||
{
|
||||
m = context.BitwiseNot(m);
|
||||
}
|
||||
return context.BitwiseExclusiveOr(
|
||||
context.BitwiseAnd(m,
|
||||
context.BitwiseExclusiveOr(d, n)), d);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,595 +0,0 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper32;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
static partial class InstEmit32
|
||||
{
|
||||
#region "Masks"
|
||||
// Same as InstEmitSimdMove, as the instructions do the same thing.
|
||||
private static readonly long[] _masksE0_Uzp = new long[]
|
||||
{
|
||||
13L << 56 | 09L << 48 | 05L << 40 | 01L << 32 | 12L << 24 | 08L << 16 | 04L << 8 | 00L << 0,
|
||||
11L << 56 | 10L << 48 | 03L << 40 | 02L << 32 | 09L << 24 | 08L << 16 | 01L << 8 | 00L << 0
|
||||
};
|
||||
|
||||
private static readonly long[] _masksE1_Uzp = new long[]
|
||||
{
|
||||
15L << 56 | 11L << 48 | 07L << 40 | 03L << 32 | 14L << 24 | 10L << 16 | 06L << 8 | 02L << 0,
|
||||
15L << 56 | 14L << 48 | 07L << 40 | 06L << 32 | 13L << 24 | 12L << 16 | 05L << 8 | 04L << 0
|
||||
};
|
||||
#endregion
|
||||
|
||||
public static void Vmov_I(ArmEmitterContext context)
|
||||
{
|
||||
EmitVectorImmUnaryOp32(context, (op1) => op1);
|
||||
}
|
||||
|
||||
public static void Vmvn_I(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||
{
|
||||
Operand mask = X86GetAllElements(context, -1L);
|
||||
return context.AddIntrinsic(Intrinsic.X86Pandn, op1, mask);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitVectorUnaryOpZx32(context, (op1) => context.BitwiseNot(op1));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vmvn_II(ArmEmitterContext context)
|
||||
{
|
||||
EmitVectorImmUnaryOp32(context, (op1) => context.BitwiseNot(op1));
|
||||
}
|
||||
|
||||
public static void Vmov_GS(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdMovGp op = (OpCode32SimdMovGp)context.CurrOp;
|
||||
|
||||
Operand vec = GetVecA32(op.Vn >> 2);
|
||||
if (op.Op == 1)
|
||||
{
|
||||
// To general purpose.
|
||||
Operand value = context.VectorExtract(OperandType.I32, vec, op.Vn & 0x3);
|
||||
SetIntA32(context, op.Rt, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// From general purpose.
|
||||
Operand value = GetIntA32(context, op.Rt);
|
||||
context.Copy(vec, context.VectorInsert(vec, value, op.Vn & 0x3));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vmov_G1(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdMovGpElem op = (OpCode32SimdMovGpElem)context.CurrOp;
|
||||
|
||||
int index = op.Index + ((op.Vd & 1) << (3 - op.Size));
|
||||
if (op.Op == 1)
|
||||
{
|
||||
// To general purpose.
|
||||
Operand value = EmitVectorExtract32(context, op.Vd >> 1, index, op.Size, !op.U);
|
||||
SetIntA32(context, op.Rt, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// From general purpose.
|
||||
Operand vec = GetVecA32(op.Vd >> 1);
|
||||
Operand value = GetIntA32(context, op.Rt);
|
||||
context.Copy(vec, EmitVectorInsert(context, vec, value, index, op.Size));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vmov_G2(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdMovGpDouble op = (OpCode32SimdMovGpDouble)context.CurrOp;
|
||||
|
||||
Operand vec = GetVecA32(op.Vm >> 2);
|
||||
int vm1 = op.Vm + 1;
|
||||
bool sameOwnerVec = (op.Vm >> 2) == (vm1 >> 2);
|
||||
Operand vec2 = sameOwnerVec ? vec : GetVecA32(vm1 >> 2);
|
||||
if (op.Op == 1)
|
||||
{
|
||||
// To general purpose.
|
||||
Operand lowValue = context.VectorExtract(OperandType.I32, vec, op.Vm & 3);
|
||||
SetIntA32(context, op.Rt, lowValue);
|
||||
|
||||
Operand highValue = context.VectorExtract(OperandType.I32, vec2, vm1 & 3);
|
||||
SetIntA32(context, op.Rt2, highValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// From general purpose.
|
||||
Operand lowValue = GetIntA32(context, op.Rt);
|
||||
Operand resultVec = context.VectorInsert(vec, lowValue, op.Vm & 3);
|
||||
|
||||
Operand highValue = GetIntA32(context, op.Rt2);
|
||||
|
||||
if (sameOwnerVec)
|
||||
{
|
||||
context.Copy(vec, context.VectorInsert(resultVec, highValue, vm1 & 3));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Copy(vec, resultVec);
|
||||
context.Copy(vec2, context.VectorInsert(vec2, highValue, vm1 & 3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vmov_GD(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdMovGpDouble op = (OpCode32SimdMovGpDouble)context.CurrOp;
|
||||
|
||||
Operand vec = GetVecA32(op.Vm >> 1);
|
||||
if (op.Op == 1)
|
||||
{
|
||||
// To general purpose.
|
||||
Operand value = context.VectorExtract(OperandType.I64, vec, op.Vm & 1);
|
||||
SetIntA32(context, op.Rt, context.ConvertI64ToI32(value));
|
||||
SetIntA32(context, op.Rt2, context.ConvertI64ToI32(context.ShiftRightUI(value, Const(32))));
|
||||
}
|
||||
else
|
||||
{
|
||||
// From general purpose.
|
||||
Operand lowValue = GetIntA32(context, op.Rt);
|
||||
Operand highValue = GetIntA32(context, op.Rt2);
|
||||
|
||||
Operand value = context.BitwiseOr(
|
||||
context.ZeroExtend32(OperandType.I64, lowValue),
|
||||
context.ShiftLeft(context.ZeroExtend32(OperandType.I64, highValue), Const(32)));
|
||||
|
||||
context.Copy(vec, context.VectorInsert(vec, value, op.Vm & 1));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vmovl(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdLong op = (OpCode32SimdLong)context.CurrOp;
|
||||
|
||||
Operand res = context.VectorZero();
|
||||
|
||||
int elems = op.GetBytesCount() >> op.Size;
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, !op.U);
|
||||
|
||||
if (op.Size == 2)
|
||||
{
|
||||
if (op.U)
|
||||
{
|
||||
me = context.ZeroExtend32(OperandType.I64, me);
|
||||
}
|
||||
else
|
||||
{
|
||||
me = context.SignExtend32(OperandType.I64, me);
|
||||
}
|
||||
}
|
||||
|
||||
res = EmitVectorInsert(context, res, me, index, op.Size + 1);
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
public static void Vtbl(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;
|
||||
|
||||
bool extension = op.Opc == 1;
|
||||
int length = op.Length + 1;
|
||||
|
||||
if (Optimizations.UseSsse3)
|
||||
{
|
||||
Operand d = GetVecA32(op.Qd);
|
||||
Operand m = EmitMoveDoubleWordToSide(context, GetVecA32(op.Qm), op.Vm, 0);
|
||||
|
||||
Operand res;
|
||||
Operand mask = X86GetAllElements(context, 0x0707070707070707L);
|
||||
|
||||
// Fast path for single register table.
|
||||
{
|
||||
Operand n = EmitMoveDoubleWordToSide(context, GetVecA32(op.Qn), op.Vn, 0);
|
||||
|
||||
Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, mask);
|
||||
mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, m);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mMask);
|
||||
}
|
||||
|
||||
for (int index = 1; index < length; index++)
|
||||
{
|
||||
int newVn = (op.Vn + index) & 0x1F;
|
||||
(int qn, int ind) = GetQuadwordAndSubindex(newVn, op.RegisterSize);
|
||||
Operand ni = EmitMoveDoubleWordToSide(context, GetVecA32(qn), newVn, 0);
|
||||
|
||||
Operand idxMask = X86GetAllElements(context, 0x0808080808080808L * index);
|
||||
|
||||
Operand mSubMask = context.AddIntrinsic(Intrinsic.X86Psubb, m, idxMask);
|
||||
|
||||
Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, mSubMask, mask);
|
||||
mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, mSubMask);
|
||||
|
||||
Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, ni, mMask);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Por, res, res2);
|
||||
}
|
||||
|
||||
if (extension)
|
||||
{
|
||||
Operand idxMask = X86GetAllElements(context, (0x0808080808080808L * length) - 0x0101010101010101L);
|
||||
Operand zeroMask = context.VectorZero();
|
||||
|
||||
Operand mPosMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, idxMask);
|
||||
Operand mNegMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, zeroMask, m);
|
||||
|
||||
Operand mMask = context.AddIntrinsic(Intrinsic.X86Por, mPosMask, mNegMask);
|
||||
|
||||
Operand dMask = context.AddIntrinsic(Intrinsic.X86Pand, EmitMoveDoubleWordToSide(context, d, op.Vd, 0), mMask);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Por, res, dMask);
|
||||
}
|
||||
|
||||
res = EmitMoveDoubleWordToSide(context, res, 0, op.Vd);
|
||||
|
||||
context.Copy(d, EmitDoubleWordInsert(context, d, res, op.Vd));
|
||||
}
|
||||
else
|
||||
{
|
||||
int elems = op.GetBytesCount() >> op.Size;
|
||||
|
||||
(int Qx, int Ix)[] tableTuples = new (int, int)[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
tableTuples[i] = GetQuadwordAndSubindex(op.Vn + i, op.RegisterSize);
|
||||
}
|
||||
|
||||
int byteLength = length * 8;
|
||||
|
||||
Operand res = GetVecA32(op.Qd);
|
||||
Operand m = GetVecA32(op.Qm);
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand selectedIndex = context.ZeroExtend8(OperandType.I32, context.VectorExtract8(m, index + op.Im));
|
||||
|
||||
Operand inRange = context.ICompareLess(selectedIndex, Const(byteLength));
|
||||
Operand elemRes = default; // Note: This is I64 for ease of calculation.
|
||||
|
||||
// TODO: Branching rather than conditional select.
|
||||
|
||||
// Get indexed byte.
|
||||
// To simplify (ha) the il, we get bytes from every vector and use a nested conditional select to choose the right result.
|
||||
// This does have to extract `length` times for every element but certainly not as bad as it could be.
|
||||
|
||||
// Which vector number is the index on.
|
||||
Operand vecIndex = context.ShiftRightUI(selectedIndex, Const(3));
|
||||
// What should we shift by to extract it.
|
||||
Operand subVecIndexShift = context.ShiftLeft(context.BitwiseAnd(selectedIndex, Const(7)), Const(3));
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
(int qx, int ix) = tableTuples[i];
|
||||
// Get the whole vector, we'll get a byte out of it.
|
||||
Operand lookupResult;
|
||||
if (qx == op.Qd)
|
||||
{
|
||||
// Result contains the current state of the vector.
|
||||
lookupResult = context.VectorExtract(OperandType.I64, res, ix);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookupResult = EmitVectorExtract32(context, qx, ix, 3, false); // I64
|
||||
}
|
||||
|
||||
lookupResult = context.ShiftRightUI(lookupResult, subVecIndexShift); // Get the relevant byte from this vector.
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
elemRes = lookupResult; // First result is always default.
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand isThisElem = context.ICompareEqual(vecIndex, Const(i));
|
||||
elemRes = context.ConditionalSelect(isThisElem, lookupResult, elemRes);
|
||||
}
|
||||
}
|
||||
|
||||
Operand fallback = (extension) ? context.ZeroExtend32(OperandType.I64, EmitVectorExtract32(context, op.Qd, index + op.Id, 0, false)) : Const(0L);
|
||||
|
||||
res = EmitVectorInsert(context, res, context.ConditionalSelect(inRange, elemRes, fallback), index + op.Id, 0);
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vtrn(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseSsse3)
|
||||
{
|
||||
EmitVectorShuffleOpSimd32(context, (m, d) =>
|
||||
{
|
||||
Operand mask = default;
|
||||
|
||||
if (op.Size < 3)
|
||||
{
|
||||
long maskE0 = EvenMasks[op.Size];
|
||||
long maskE1 = OddMasks[op.Size];
|
||||
|
||||
mask = X86GetScalar(context, maskE0);
|
||||
|
||||
mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3);
|
||||
}
|
||||
|
||||
if (op.Size < 3)
|
||||
{
|
||||
d = context.AddIntrinsic(Intrinsic.X86Pshufb, d, mask);
|
||||
m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask);
|
||||
}
|
||||
|
||||
Operand resD = context.AddIntrinsic(X86PunpcklInstruction[op.Size], d, m);
|
||||
Operand resM = context.AddIntrinsic(X86PunpckhInstruction[op.Size], d, m);
|
||||
|
||||
return (resM, resD);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
int elems = op.GetBytesCount() >> op.Size;
|
||||
int pairs = elems >> 1;
|
||||
|
||||
bool overlap = op.Qm == op.Qd;
|
||||
|
||||
Operand resD = GetVecA32(op.Qd);
|
||||
Operand resM = GetVecA32(op.Qm);
|
||||
|
||||
for (int index = 0; index < pairs; index++)
|
||||
{
|
||||
int pairIndex = index << 1;
|
||||
Operand d2 = EmitVectorExtract32(context, op.Qd, pairIndex + 1 + op.Id, op.Size, false);
|
||||
Operand m1 = EmitVectorExtract32(context, op.Qm, pairIndex + op.Im, op.Size, false);
|
||||
|
||||
resD = EmitVectorInsert(context, resD, m1, pairIndex + 1 + op.Id, op.Size);
|
||||
|
||||
if (overlap)
|
||||
{
|
||||
resM = resD;
|
||||
}
|
||||
|
||||
resM = EmitVectorInsert(context, resM, d2, pairIndex + op.Im, op.Size);
|
||||
|
||||
if (overlap)
|
||||
{
|
||||
resD = resM;
|
||||
}
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), resD);
|
||||
if (!overlap)
|
||||
{
|
||||
context.Copy(GetVecA32(op.Qm), resM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vzip(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseSse2)
|
||||
{
|
||||
EmitVectorShuffleOpSimd32(context, (m, d) =>
|
||||
{
|
||||
if (op.RegisterSize == RegisterSize.Simd128)
|
||||
{
|
||||
Operand resD = context.AddIntrinsic(X86PunpcklInstruction[op.Size], d, m);
|
||||
Operand resM = context.AddIntrinsic(X86PunpckhInstruction[op.Size], d, m);
|
||||
|
||||
return (resM, resD);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand res = context.AddIntrinsic(X86PunpcklInstruction[op.Size], d, m);
|
||||
|
||||
Operand resD = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, res, context.VectorZero());
|
||||
Operand resM = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, res, context.VectorZero());
|
||||
return (resM, resD);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
int elems = op.GetBytesCount() >> op.Size;
|
||||
int pairs = elems >> 1;
|
||||
|
||||
bool overlap = op.Qm == op.Qd;
|
||||
|
||||
Operand resD = GetVecA32(op.Qd);
|
||||
Operand resM = GetVecA32(op.Qm);
|
||||
|
||||
for (int index = 0; index < pairs; index++)
|
||||
{
|
||||
int pairIndex = index << 1;
|
||||
Operand dRowD = EmitVectorExtract32(context, op.Qd, index + op.Id, op.Size, false);
|
||||
Operand mRowD = EmitVectorExtract32(context, op.Qm, index + op.Im, op.Size, false);
|
||||
|
||||
Operand dRowM = EmitVectorExtract32(context, op.Qd, index + op.Id + pairs, op.Size, false);
|
||||
Operand mRowM = EmitVectorExtract32(context, op.Qm, index + op.Im + pairs, op.Size, false);
|
||||
|
||||
resD = EmitVectorInsert(context, resD, dRowD, pairIndex + op.Id, op.Size);
|
||||
resD = EmitVectorInsert(context, resD, mRowD, pairIndex + 1 + op.Id, op.Size);
|
||||
|
||||
if (overlap)
|
||||
{
|
||||
resM = resD;
|
||||
}
|
||||
|
||||
resM = EmitVectorInsert(context, resM, dRowM, pairIndex + op.Im, op.Size);
|
||||
resM = EmitVectorInsert(context, resM, mRowM, pairIndex + 1 + op.Im, op.Size);
|
||||
|
||||
if (overlap)
|
||||
{
|
||||
resD = resM;
|
||||
}
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), resD);
|
||||
if (!overlap)
|
||||
{
|
||||
context.Copy(GetVecA32(op.Qm), resM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vuzp(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp;
|
||||
|
||||
if (Optimizations.UseSsse3)
|
||||
{
|
||||
EmitVectorShuffleOpSimd32(context, (m, d) =>
|
||||
{
|
||||
if (op.RegisterSize == RegisterSize.Simd128)
|
||||
{
|
||||
Operand mask = default;
|
||||
|
||||
if (op.Size < 3)
|
||||
{
|
||||
long maskE0 = EvenMasks[op.Size];
|
||||
long maskE1 = OddMasks[op.Size];
|
||||
|
||||
mask = X86GetScalar(context, maskE0);
|
||||
mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3);
|
||||
|
||||
d = context.AddIntrinsic(Intrinsic.X86Pshufb, d, mask);
|
||||
m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask);
|
||||
}
|
||||
|
||||
Operand resD = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, d, m);
|
||||
Operand resM = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, d, m);
|
||||
|
||||
return (resM, resD);
|
||||
}
|
||||
else
|
||||
{
|
||||
Intrinsic punpcklInst = X86PunpcklInstruction[op.Size];
|
||||
|
||||
Operand res = context.AddIntrinsic(punpcklInst, d, m);
|
||||
|
||||
if (op.Size < 2)
|
||||
{
|
||||
long maskE0 = _masksE0_Uzp[op.Size];
|
||||
long maskE1 = _masksE1_Uzp[op.Size];
|
||||
|
||||
Operand mask = X86GetScalar(context, maskE0);
|
||||
|
||||
mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3);
|
||||
|
||||
res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask);
|
||||
}
|
||||
|
||||
Operand resD = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, res, context.VectorZero());
|
||||
Operand resM = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, res, context.VectorZero());
|
||||
|
||||
return (resM, resD);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
int elems = op.GetBytesCount() >> op.Size;
|
||||
int pairs = elems >> 1;
|
||||
|
||||
bool overlap = op.Qm == op.Qd;
|
||||
|
||||
Operand resD = GetVecA32(op.Qd);
|
||||
Operand resM = GetVecA32(op.Qm);
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand dIns, mIns;
|
||||
if (index >= pairs)
|
||||
{
|
||||
int pairIndex = index - pairs;
|
||||
dIns = EmitVectorExtract32(context, op.Qm, (pairIndex << 1) + op.Im, op.Size, false);
|
||||
mIns = EmitVectorExtract32(context, op.Qm, ((pairIndex << 1) | 1) + op.Im, op.Size, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
dIns = EmitVectorExtract32(context, op.Qd, (index << 1) + op.Id, op.Size, false);
|
||||
mIns = EmitVectorExtract32(context, op.Qd, ((index << 1) | 1) + op.Id, op.Size, false);
|
||||
}
|
||||
|
||||
resD = EmitVectorInsert(context, resD, dIns, index + op.Id, op.Size);
|
||||
|
||||
if (overlap)
|
||||
{
|
||||
resM = resD;
|
||||
}
|
||||
|
||||
resM = EmitVectorInsert(context, resM, mIns, index + op.Im, op.Size);
|
||||
|
||||
if (overlap)
|
||||
{
|
||||
resD = resM;
|
||||
}
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), resD);
|
||||
if (!overlap)
|
||||
{
|
||||
context.Copy(GetVecA32(op.Qm), resM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitVectorShuffleOpSimd32(ArmEmitterContext context, Func<Operand, Operand, (Operand, Operand)> shuffleFunc)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
Operand m = GetVecA32(op.Qm);
|
||||
Operand d = GetVecA32(op.Qd);
|
||||
Operand initialM = m;
|
||||
Operand initialD = d;
|
||||
|
||||
if (!op.Q) // Register swap: move relevant doubleword to side 0, for consistency.
|
||||
{
|
||||
m = EmitMoveDoubleWordToSide(context, m, op.Vm, 0);
|
||||
d = EmitMoveDoubleWordToSide(context, d, op.Vd, 0);
|
||||
}
|
||||
|
||||
(Operand resM, Operand resD) = shuffleFunc(m, d);
|
||||
|
||||
bool overlap = op.Qm == op.Qd;
|
||||
|
||||
if (!op.Q) // Register insert.
|
||||
{
|
||||
resM = EmitDoubleWordInsert(context, initialM, EmitMoveDoubleWordToSide(context, resM, 0, op.Vm), op.Vm);
|
||||
resD = EmitDoubleWordInsert(context, overlap ? resM : initialD, EmitMoveDoubleWordToSide(context, resD, 0, op.Vd), op.Vd);
|
||||
}
|
||||
|
||||
if (!overlap)
|
||||
{
|
||||
context.Copy(initialM, resM);
|
||||
}
|
||||
|
||||
context.Copy(initialD, resD);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,211 +0,0 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
static partial class InstEmit
|
||||
{
|
||||
private const int DczSizeLog2 = 4; // Log2 size in words
|
||||
public const int DczSizeInBytes = 4 << DczSizeLog2;
|
||||
|
||||
public static void Isb(ArmEmitterContext context)
|
||||
{
|
||||
// Execute as no-op.
|
||||
}
|
||||
|
||||
public static void Mrs(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (GetPackedId(op))
|
||||
{
|
||||
case 0b11_011_0000_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCtrEl0)); break;
|
||||
case 0b11_011_0000_0000_111: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)); break;
|
||||
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
|
||||
case 0b11_011_0100_0100_000: EmitGetFpcr(context); return;
|
||||
case 0b11_011_0100_0100_001: EmitGetFpsr(context); return;
|
||||
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)); break;
|
||||
case 0b11_011_1101_0000_011: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)); break;
|
||||
case 0b11_011_1110_0000_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)); break;
|
||||
case 0b11_011_1110_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
|
||||
case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break;
|
||||
|
||||
default: throw new NotImplementedException($"Unknown MRS 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
}
|
||||
|
||||
SetIntOrZR(context, op.Rt, context.Call(info));
|
||||
}
|
||||
|
||||
public static void Msr(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (GetPackedId(op))
|
||||
{
|
||||
case 0b11_011_0100_0010_000: EmitSetNzcv(context); return;
|
||||
case 0b11_011_0100_0100_000: EmitSetFpcr(context); return;
|
||||
case 0b11_011_0100_0100_001: EmitSetFpsr(context); return;
|
||||
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)); break;
|
||||
|
||||
default: throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
}
|
||||
|
||||
context.Call(info, GetIntOrZR(context, op.Rt));
|
||||
}
|
||||
|
||||
public static void Nop(ArmEmitterContext context)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
public static void Sys(ArmEmitterContext context)
|
||||
{
|
||||
// This instruction is used to do some operations on the CPU like cache invalidation,
|
||||
// address translation and the like.
|
||||
// We treat it as no-op here since we don't have any cache being emulated anyway.
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
switch (GetPackedId(op))
|
||||
{
|
||||
case 0b11_011_0111_0100_001:
|
||||
{
|
||||
// DC ZVA
|
||||
Operand t = GetIntOrZR(context, op.Rt);
|
||||
|
||||
for (long offset = 0; offset < DczSizeInBytes; offset += 8)
|
||||
{
|
||||
Operand address = context.Add(t, Const(offset));
|
||||
|
||||
InstEmitMemoryHelper.EmitStore(context, address, RegisterConsts.ZeroIndex, 3);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// No-op
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetPackedId(OpCodeSystem op)
|
||||
{
|
||||
int id;
|
||||
|
||||
id = op.Op2 << 0;
|
||||
id |= op.CRm << 3;
|
||||
id |= op.CRn << 7;
|
||||
id |= op.Op1 << 11;
|
||||
id |= op.Op0 << 14;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void EmitGetNzcv(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand nzcv = context.ShiftLeft(GetFlag(PState.VFlag), Const((int)PState.VFlag));
|
||||
nzcv = context.BitwiseOr(nzcv, context.ShiftLeft(GetFlag(PState.CFlag), Const((int)PState.CFlag)));
|
||||
nzcv = context.BitwiseOr(nzcv, context.ShiftLeft(GetFlag(PState.ZFlag), Const((int)PState.ZFlag)));
|
||||
nzcv = context.BitwiseOr(nzcv, context.ShiftLeft(GetFlag(PState.NFlag), Const((int)PState.NFlag)));
|
||||
|
||||
SetIntOrZR(context, op.Rt, nzcv);
|
||||
}
|
||||
|
||||
private static void EmitGetFpcr(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand fpcr = Const(0);
|
||||
|
||||
for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++)
|
||||
{
|
||||
if (FPCR.Mask.HasFlag((FPCR)(1u << flag)))
|
||||
{
|
||||
fpcr = context.BitwiseOr(fpcr, context.ShiftLeft(GetFpFlag((FPState)flag), Const(flag)));
|
||||
}
|
||||
}
|
||||
|
||||
SetIntOrZR(context, op.Rt, fpcr);
|
||||
}
|
||||
|
||||
private static void EmitGetFpsr(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand fpsr = Const(0);
|
||||
|
||||
for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++)
|
||||
{
|
||||
if (FPSR.Mask.HasFlag((FPSR)(1u << flag)))
|
||||
{
|
||||
fpsr = context.BitwiseOr(fpsr, context.ShiftLeft(GetFpFlag((FPState)flag), Const(flag)));
|
||||
}
|
||||
}
|
||||
|
||||
SetIntOrZR(context, op.Rt, fpsr);
|
||||
}
|
||||
|
||||
private static void EmitSetNzcv(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand nzcv = GetIntOrZR(context, op.Rt);
|
||||
nzcv = context.ConvertI64ToI32(nzcv);
|
||||
|
||||
SetFlag(context, PState.VFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.VFlag)), Const(1)));
|
||||
SetFlag(context, PState.CFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.CFlag)), Const(1)));
|
||||
SetFlag(context, PState.ZFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.ZFlag)), Const(1)));
|
||||
SetFlag(context, PState.NFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.NFlag)), Const(1)));
|
||||
}
|
||||
|
||||
private static void EmitSetFpcr(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand fpcr = GetIntOrZR(context, op.Rt);
|
||||
fpcr = context.ConvertI64ToI32(fpcr);
|
||||
|
||||
for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++)
|
||||
{
|
||||
if (FPCR.Mask.HasFlag((FPCR)(1u << flag)))
|
||||
{
|
||||
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpcr, Const(flag)), Const(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitSetFpsr(ArmEmitterContext context)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
|
||||
Operand fpsr = GetIntOrZR(context, op.Rt);
|
||||
fpsr = context.ConvertI64ToI32(fpsr);
|
||||
|
||||
for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++)
|
||||
{
|
||||
if (FPSR.Mask.HasFlag((FPSR)(1u << flag)))
|
||||
{
|
||||
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpsr, Const(flag)), Const(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
static partial class InstEmit32
|
||||
{
|
||||
public static void Mcr(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
if (op.Coproc != 15 || op.Opc1 != 0)
|
||||
{
|
||||
InstEmit.Und(context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (op.CRn)
|
||||
{
|
||||
case 13: // Process and Thread Info.
|
||||
if (op.CRm != 0)
|
||||
{
|
||||
throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
switch (op.Opc2)
|
||||
{
|
||||
case 2:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032)); break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 7:
|
||||
switch (op.CRm) // Cache and Memory barrier.
|
||||
{
|
||||
case 10:
|
||||
switch (op.Opc2)
|
||||
{
|
||||
case 5: // Data Memory Barrier Register.
|
||||
return; // No-op.
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X16} at 0x{op.Address:X16} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X16} at 0x{op.Address:X16} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||
}
|
||||
|
||||
context.Call(info, GetIntA32(context, op.Rt));
|
||||
}
|
||||
|
||||
public static void Mrc(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
if (op.Coproc != 15 || op.Opc1 != 0)
|
||||
{
|
||||
InstEmit.Und(context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (op.CRn)
|
||||
{
|
||||
case 13: // Process and Thread Info.
|
||||
if (op.CRm != 0)
|
||||
{
|
||||
throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
switch (op.Opc2)
|
||||
{
|
||||
case 2:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032)); break;
|
||||
|
||||
case 3:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32)); break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X} at 0x{op.Address:X}.");
|
||||
}
|
||||
|
||||
if (op.Rt == RegisterAlias.Aarch32Pc)
|
||||
{
|
||||
// Special behavior: copy NZCV flags into APSR.
|
||||
EmitSetNzcv(context, context.Call(info));
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetIntA32(context, op.Rt, context.Call(info));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Mrrc(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32System op = (OpCode32System)context.CurrOp;
|
||||
|
||||
if (op.Coproc != 15)
|
||||
{
|
||||
InstEmit.Und(context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int opc = op.MrrcOp;
|
||||
|
||||
MethodInfo info;
|
||||
|
||||
switch (op.CRm)
|
||||
{
|
||||
case 14: // Timer.
|
||||
switch (opc)
|
||||
{
|
||||
case 0:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRRC Opc1 0x{opc:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown MRRC 0x{op.RawOpCode:X} at 0x{op.Address:X}.");
|
||||
}
|
||||
|
||||
Operand result = context.Call(info);
|
||||
|
||||
SetIntA32(context, op.Rt, context.ConvertI64ToI32(result));
|
||||
SetIntA32(context, op.CRn, context.ConvertI64ToI32(context.ShiftRightUI(result, Const(32))));
|
||||
}
|
||||
|
||||
public static void Mrs(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Mrs op = (OpCode32Mrs)context.CurrOp;
|
||||
|
||||
if (op.R)
|
||||
{
|
||||
throw new NotImplementedException("SPSR");
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand spsr = context.ShiftLeft(GetFlag(PState.VFlag), Const((int)PState.VFlag));
|
||||
spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.CFlag), Const((int)PState.CFlag)));
|
||||
spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.ZFlag), Const((int)PState.ZFlag)));
|
||||
spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.NFlag), Const((int)PState.NFlag)));
|
||||
spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.QFlag), Const((int)PState.QFlag)));
|
||||
|
||||
// TODO: Remaining flags.
|
||||
|
||||
SetIntA32(context, op.Rd, spsr);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Msr(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32MsrReg op = (OpCode32MsrReg)context.CurrOp;
|
||||
|
||||
if (op.R)
|
||||
{
|
||||
throw new NotImplementedException("SPSR");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((op.Mask & 8) != 0)
|
||||
{
|
||||
Operand value = GetIntA32(context, op.Rn);
|
||||
|
||||
EmitSetNzcv(context, value);
|
||||
|
||||
Operand q = context.BitwiseAnd(context.ShiftRightUI(value, Const((int)PState.QFlag)), Const(1));
|
||||
|
||||
SetFlag(context, PState.QFlag, q);
|
||||
}
|
||||
|
||||
if ((op.Mask & 4) != 0)
|
||||
{
|
||||
throw new NotImplementedException("APSR_g");
|
||||
}
|
||||
|
||||
if ((op.Mask & 2) != 0)
|
||||
{
|
||||
throw new NotImplementedException("CPSR_x");
|
||||
}
|
||||
|
||||
if ((op.Mask & 1) != 0)
|
||||
{
|
||||
throw new NotImplementedException("CPSR_c");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Nop(ArmEmitterContext context) { }
|
||||
|
||||
public static void Vmrs(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp;
|
||||
|
||||
if (op.Rt == RegisterAlias.Aarch32Pc && op.Sreg == 0b0001)
|
||||
{
|
||||
// Special behavior: copy NZCV flags into APSR.
|
||||
SetFlag(context, PState.VFlag, GetFpFlag(FPState.VFlag));
|
||||
SetFlag(context, PState.CFlag, GetFpFlag(FPState.CFlag));
|
||||
SetFlag(context, PState.ZFlag, GetFpFlag(FPState.ZFlag));
|
||||
SetFlag(context, PState.NFlag, GetFpFlag(FPState.NFlag));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (op.Sreg)
|
||||
{
|
||||
case 0b0000: // FPSID
|
||||
throw new NotImplementedException("Supervisor Only");
|
||||
case 0b0001: // FPSCR
|
||||
EmitGetFpscr(context); return;
|
||||
case 0b0101: // MVFR2
|
||||
throw new NotImplementedException("MVFR2");
|
||||
case 0b0110: // MVFR1
|
||||
throw new NotImplementedException("MVFR1");
|
||||
case 0b0111: // MVFR0
|
||||
throw new NotImplementedException("MVFR0");
|
||||
case 0b1000: // FPEXC
|
||||
throw new NotImplementedException("Supervisor Only");
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown VMRS 0x{op.RawOpCode:X} at 0x{op.Address:X}.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vmsr(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp;
|
||||
|
||||
switch (op.Sreg)
|
||||
{
|
||||
case 0b0000: // FPSID
|
||||
throw new NotImplementedException("Supervisor Only");
|
||||
case 0b0001: // FPSCR
|
||||
EmitSetFpscr(context); return;
|
||||
case 0b0101: // MVFR2
|
||||
throw new NotImplementedException("MVFR2");
|
||||
case 0b0110: // MVFR1
|
||||
throw new NotImplementedException("MVFR1");
|
||||
case 0b0111: // MVFR0
|
||||
throw new NotImplementedException("MVFR0");
|
||||
case 0b1000: // FPEXC
|
||||
throw new NotImplementedException("Supervisor Only");
|
||||
default:
|
||||
throw new NotImplementedException($"Unknown VMSR 0x{op.RawOpCode:X} at 0x{op.Address:X}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitSetNzcv(ArmEmitterContext context, Operand t)
|
||||
{
|
||||
Operand v = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.VFlag)), Const(1));
|
||||
Operand c = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.CFlag)), Const(1));
|
||||
Operand z = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.ZFlag)), Const(1));
|
||||
Operand n = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.NFlag)), Const(1));
|
||||
|
||||
SetFlag(context, PState.VFlag, v);
|
||||
SetFlag(context, PState.CFlag, c);
|
||||
SetFlag(context, PState.ZFlag, z);
|
||||
SetFlag(context, PState.NFlag, n);
|
||||
}
|
||||
|
||||
private static void EmitGetFpscr(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp;
|
||||
|
||||
Operand fpscr = Const(0);
|
||||
|
||||
for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++)
|
||||
{
|
||||
if (FPSCR.Mask.HasFlag((FPSCR)(1u << flag)))
|
||||
{
|
||||
fpscr = context.BitwiseOr(fpscr, context.ShiftLeft(GetFpFlag((FPState)flag), Const(flag)));
|
||||
}
|
||||
}
|
||||
|
||||
SetIntA32(context, op.Rt, fpscr);
|
||||
}
|
||||
|
||||
private static void EmitSetFpscr(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp;
|
||||
|
||||
Operand fpscr = GetIntA32(context, op.Rt);
|
||||
|
||||
for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++)
|
||||
{
|
||||
if (FPSCR.Mask.HasFlag((FPSCR)(1u << flag)))
|
||||
{
|
||||
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpscr, Const(flag)), Const(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
static class NativeInterface
|
||||
{
|
||||
private class ThreadContext
|
||||
{
|
||||
public ExecutionContext Context { get; }
|
||||
public IMemoryManager Memory { get; }
|
||||
public Translator Translator { get; }
|
||||
|
||||
public ThreadContext(ExecutionContext context, IMemoryManager memory, Translator translator)
|
||||
{
|
||||
Context = context;
|
||||
Memory = memory;
|
||||
Translator = translator;
|
||||
}
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static ThreadContext Context;
|
||||
|
||||
public static void RegisterThread(ExecutionContext context, IMemoryManager memory, Translator translator)
|
||||
{
|
||||
Context = new ThreadContext(context, memory, translator);
|
||||
}
|
||||
|
||||
public static void UnregisterThread()
|
||||
{
|
||||
Context = null;
|
||||
}
|
||||
|
||||
public static void Break(ulong address, int imm)
|
||||
{
|
||||
Statistics.PauseTimer();
|
||||
|
||||
GetContext().OnBreak(address, imm);
|
||||
|
||||
Statistics.ResumeTimer();
|
||||
}
|
||||
|
||||
public static void SupervisorCall(ulong address, int imm)
|
||||
{
|
||||
Statistics.PauseTimer();
|
||||
|
||||
GetContext().OnSupervisorCall(address, imm);
|
||||
|
||||
Statistics.ResumeTimer();
|
||||
}
|
||||
|
||||
public static void Undefined(ulong address, int opCode)
|
||||
{
|
||||
Statistics.PauseTimer();
|
||||
|
||||
GetContext().OnUndefined(address, opCode);
|
||||
|
||||
Statistics.ResumeTimer();
|
||||
}
|
||||
|
||||
#region "System registers"
|
||||
public static ulong GetCtrEl0()
|
||||
{
|
||||
return (ulong)GetContext().CtrEl0;
|
||||
}
|
||||
|
||||
public static ulong GetDczidEl0()
|
||||
{
|
||||
return (ulong)GetContext().DczidEl0;
|
||||
}
|
||||
|
||||
public static ulong GetTpidrEl0()
|
||||
{
|
||||
return (ulong)GetContext().TpidrEl0;
|
||||
}
|
||||
|
||||
public static uint GetTpidrEl032()
|
||||
{
|
||||
return (uint)GetContext().TpidrEl0;
|
||||
}
|
||||
|
||||
public static ulong GetTpidrroEl0()
|
||||
{
|
||||
return (ulong)GetContext().TpidrroEl0;
|
||||
}
|
||||
|
||||
public static uint GetTpidr32()
|
||||
{
|
||||
return (uint)GetContext().TpidrroEl0;
|
||||
}
|
||||
|
||||
public static ulong GetCntfrqEl0()
|
||||
{
|
||||
return GetContext().CntfrqEl0;
|
||||
}
|
||||
|
||||
public static ulong GetCntpctEl0()
|
||||
{
|
||||
return GetContext().CntpctEl0;
|
||||
}
|
||||
|
||||
public static ulong GetCntvctEl0()
|
||||
{
|
||||
return GetContext().CntvctEl0;
|
||||
}
|
||||
|
||||
public static void SetTpidrEl0(ulong value)
|
||||
{
|
||||
GetContext().TpidrEl0 = (long)value;
|
||||
}
|
||||
|
||||
public static void SetTpidrEl032(uint value)
|
||||
{
|
||||
GetContext().TpidrEl0 = (long)value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Read"
|
||||
public static byte ReadByte(ulong address)
|
||||
{
|
||||
return GetMemoryManager().ReadTracked<byte>(address);
|
||||
}
|
||||
|
||||
public static ushort ReadUInt16(ulong address)
|
||||
{
|
||||
return GetMemoryManager().ReadTracked<ushort>(address);
|
||||
}
|
||||
|
||||
public static uint ReadUInt32(ulong address)
|
||||
{
|
||||
return GetMemoryManager().ReadTracked<uint>(address);
|
||||
}
|
||||
|
||||
public static ulong ReadUInt64(ulong address)
|
||||
{
|
||||
return GetMemoryManager().ReadTracked<ulong>(address);
|
||||
}
|
||||
|
||||
public static V128 ReadVector128(ulong address)
|
||||
{
|
||||
return GetMemoryManager().ReadTracked<V128>(address);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region "Write"
|
||||
public static void WriteByte(ulong address, byte value)
|
||||
{
|
||||
GetMemoryManager().Write(address, value);
|
||||
}
|
||||
|
||||
public static void WriteUInt16(ulong address, ushort value)
|
||||
{
|
||||
GetMemoryManager().Write(address, value);
|
||||
}
|
||||
|
||||
public static void WriteUInt32(ulong address, uint value)
|
||||
{
|
||||
GetMemoryManager().Write(address, value);
|
||||
}
|
||||
|
||||
public static void WriteUInt64(ulong address, ulong value)
|
||||
{
|
||||
GetMemoryManager().Write(address, value);
|
||||
}
|
||||
|
||||
public static void WriteVector128(ulong address, V128 value)
|
||||
{
|
||||
GetMemoryManager().Write(address, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static void EnqueueForRejit(ulong address)
|
||||
{
|
||||
Context.Translator.EnqueueForRejit(address, GetContext().ExecutionMode);
|
||||
}
|
||||
|
||||
public static void SignalMemoryTracking(ulong address, ulong size, bool write)
|
||||
{
|
||||
GetMemoryManager().SignalMemoryTracking(address, size, write);
|
||||
}
|
||||
|
||||
public static void ThrowInvalidMemoryAccess(ulong address)
|
||||
{
|
||||
throw new InvalidAccessException(address);
|
||||
}
|
||||
|
||||
public static ulong GetFunctionAddress(ulong address)
|
||||
{
|
||||
TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode);
|
||||
|
||||
return (ulong)function.FuncPtr.ToInt64();
|
||||
}
|
||||
|
||||
public static void InvalidateCacheLine(ulong address)
|
||||
{
|
||||
Context.Translator.InvalidateJitCacheRegion(address, InstEmit.DczSizeInBytes);
|
||||
}
|
||||
|
||||
public static bool CheckSynchronization()
|
||||
{
|
||||
Statistics.PauseTimer();
|
||||
|
||||
ExecutionContext context = GetContext();
|
||||
|
||||
context.CheckInterrupt();
|
||||
|
||||
Statistics.ResumeTimer();
|
||||
|
||||
return context.Running;
|
||||
}
|
||||
|
||||
public static ExecutionContext GetContext()
|
||||
{
|
||||
return Context.Context;
|
||||
}
|
||||
|
||||
public static IMemoryManager GetMemoryManager()
|
||||
{
|
||||
return Context.Memory;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
enum Intrinsic : ushort
|
||||
{
|
||||
X86Addpd,
|
||||
X86Addps,
|
||||
X86Addsd,
|
||||
X86Addss,
|
||||
X86Aesdec,
|
||||
X86Aesdeclast,
|
||||
X86Aesenc,
|
||||
X86Aesenclast,
|
||||
X86Aesimc,
|
||||
X86Andnpd,
|
||||
X86Andnps,
|
||||
X86Andpd,
|
||||
X86Andps,
|
||||
X86Blendvpd,
|
||||
X86Blendvps,
|
||||
X86Cmppd,
|
||||
X86Cmpps,
|
||||
X86Cmpsd,
|
||||
X86Cmpss,
|
||||
X86Comisdeq,
|
||||
X86Comisdge,
|
||||
X86Comisdlt,
|
||||
X86Comisseq,
|
||||
X86Comissge,
|
||||
X86Comisslt,
|
||||
X86Crc32,
|
||||
X86Crc32_16,
|
||||
X86Crc32_8,
|
||||
X86Cvtdq2pd,
|
||||
X86Cvtdq2ps,
|
||||
X86Cvtpd2dq,
|
||||
X86Cvtpd2ps,
|
||||
X86Cvtps2dq,
|
||||
X86Cvtps2pd,
|
||||
X86Cvtsd2si,
|
||||
X86Cvtsd2ss,
|
||||
X86Cvtsi2sd,
|
||||
X86Cvtsi2si,
|
||||
X86Cvtsi2ss,
|
||||
X86Cvtss2sd,
|
||||
X86Cvtss2si,
|
||||
X86Divpd,
|
||||
X86Divps,
|
||||
X86Divsd,
|
||||
X86Divss,
|
||||
X86Gf2p8affineqb,
|
||||
X86Haddpd,
|
||||
X86Haddps,
|
||||
X86Insertps,
|
||||
X86Maxpd,
|
||||
X86Maxps,
|
||||
X86Maxsd,
|
||||
X86Maxss,
|
||||
X86Minpd,
|
||||
X86Minps,
|
||||
X86Minsd,
|
||||
X86Minss,
|
||||
X86Movhlps,
|
||||
X86Movlhps,
|
||||
X86Movss,
|
||||
X86Mulpd,
|
||||
X86Mulps,
|
||||
X86Mulsd,
|
||||
X86Mulss,
|
||||
X86Mxcsrmb,
|
||||
X86Mxcsrub,
|
||||
X86Paddb,
|
||||
X86Paddd,
|
||||
X86Paddq,
|
||||
X86Paddw,
|
||||
X86Palignr,
|
||||
X86Pand,
|
||||
X86Pandn,
|
||||
X86Pavgb,
|
||||
X86Pavgw,
|
||||
X86Pblendvb,
|
||||
X86Pclmulqdq,
|
||||
X86Pcmpeqb,
|
||||
X86Pcmpeqd,
|
||||
X86Pcmpeqq,
|
||||
X86Pcmpeqw,
|
||||
X86Pcmpgtb,
|
||||
X86Pcmpgtd,
|
||||
X86Pcmpgtq,
|
||||
X86Pcmpgtw,
|
||||
X86Pmaxsb,
|
||||
X86Pmaxsd,
|
||||
X86Pmaxsw,
|
||||
X86Pmaxub,
|
||||
X86Pmaxud,
|
||||
X86Pmaxuw,
|
||||
X86Pminsb,
|
||||
X86Pminsd,
|
||||
X86Pminsw,
|
||||
X86Pminub,
|
||||
X86Pminud,
|
||||
X86Pminuw,
|
||||
X86Pmovsxbw,
|
||||
X86Pmovsxdq,
|
||||
X86Pmovsxwd,
|
||||
X86Pmovzxbw,
|
||||
X86Pmovzxdq,
|
||||
X86Pmovzxwd,
|
||||
X86Pmulld,
|
||||
X86Pmullw,
|
||||
X86Popcnt,
|
||||
X86Por,
|
||||
X86Pshufb,
|
||||
X86Pshufd,
|
||||
X86Pslld,
|
||||
X86Pslldq,
|
||||
X86Psllq,
|
||||
X86Psllw,
|
||||
X86Psrad,
|
||||
X86Psraw,
|
||||
X86Psrld,
|
||||
X86Psrlq,
|
||||
X86Psrldq,
|
||||
X86Psrlw,
|
||||
X86Psubb,
|
||||
X86Psubd,
|
||||
X86Psubq,
|
||||
X86Psubw,
|
||||
X86Punpckhbw,
|
||||
X86Punpckhdq,
|
||||
X86Punpckhqdq,
|
||||
X86Punpckhwd,
|
||||
X86Punpcklbw,
|
||||
X86Punpckldq,
|
||||
X86Punpcklqdq,
|
||||
X86Punpcklwd,
|
||||
X86Pxor,
|
||||
X86Rcpps,
|
||||
X86Rcpss,
|
||||
X86Roundpd,
|
||||
X86Roundps,
|
||||
X86Roundsd,
|
||||
X86Roundss,
|
||||
X86Rsqrtps,
|
||||
X86Rsqrtss,
|
||||
X86Sha256Msg1,
|
||||
X86Sha256Msg2,
|
||||
X86Sha256Rnds2,
|
||||
X86Shufpd,
|
||||
X86Shufps,
|
||||
X86Sqrtpd,
|
||||
X86Sqrtps,
|
||||
X86Sqrtsd,
|
||||
X86Sqrtss,
|
||||
X86Subpd,
|
||||
X86Subps,
|
||||
X86Subsd,
|
||||
X86Subss,
|
||||
X86Unpckhpd,
|
||||
X86Unpckhps,
|
||||
X86Unpcklpd,
|
||||
X86Unpcklps,
|
||||
X86Vcvtph2ps,
|
||||
X86Vcvtps2ph,
|
||||
X86Vfmadd231ps,
|
||||
X86Vfmadd231sd,
|
||||
X86Vfmadd231ss,
|
||||
X86Vfmsub231sd,
|
||||
X86Vfmsub231ss,
|
||||
X86Vfnmadd231ps,
|
||||
X86Vfnmadd231sd,
|
||||
X86Vfnmadd231ss,
|
||||
X86Vfnmsub231sd,
|
||||
X86Vfnmsub231ss,
|
||||
X86Xorpd,
|
||||
X86Xorps
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
enum Multiplier
|
||||
{
|
||||
x1 = 0,
|
||||
x2 = 1,
|
||||
x4 = 2,
|
||||
x8 = 3
|
||||
}
|
||||
}
|
||||
@@ -1,580 +0,0 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
unsafe struct Operand : IEquatable<Operand>
|
||||
{
|
||||
internal struct Data
|
||||
{
|
||||
public byte Kind;
|
||||
public byte Type;
|
||||
public byte SymbolType;
|
||||
public byte Padding; // Unused space.
|
||||
public ushort AssignmentsCount;
|
||||
public ushort AssignmentsCapacity;
|
||||
public uint UsesCount;
|
||||
public uint UsesCapacity;
|
||||
public Operation* Assignments;
|
||||
public Operation* Uses;
|
||||
public ulong Value;
|
||||
public ulong SymbolValue;
|
||||
}
|
||||
|
||||
private Data* _data;
|
||||
|
||||
public OperandKind Kind
|
||||
{
|
||||
get => (OperandKind)_data->Kind;
|
||||
private set => _data->Kind = (byte)value;
|
||||
}
|
||||
|
||||
public OperandType Type
|
||||
{
|
||||
get => (OperandType)_data->Type;
|
||||
private set => _data->Type = (byte)value;
|
||||
}
|
||||
|
||||
public ulong Value
|
||||
{
|
||||
get => _data->Value;
|
||||
private set => _data->Value = value;
|
||||
}
|
||||
|
||||
public Symbol Symbol
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(Kind != OperandKind.Memory);
|
||||
|
||||
return new Symbol((SymbolType)_data->SymbolType, _data->SymbolValue);
|
||||
}
|
||||
private set
|
||||
{
|
||||
Debug.Assert(Kind != OperandKind.Memory);
|
||||
|
||||
if (value.Type == SymbolType.None)
|
||||
{
|
||||
_data->SymbolType = (byte)SymbolType.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
_data->SymbolType = (byte)value.Type;
|
||||
_data->SymbolValue = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<Operation> Assignments
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(Kind != OperandKind.Memory);
|
||||
|
||||
return new ReadOnlySpan<Operation>(_data->Assignments, _data->AssignmentsCount);
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<Operation> Uses
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(Kind != OperandKind.Memory);
|
||||
|
||||
return new ReadOnlySpan<Operation>(_data->Uses, (int)_data->UsesCount);
|
||||
}
|
||||
}
|
||||
|
||||
public int UsesCount => (int)_data->UsesCount;
|
||||
public int AssignmentsCount => _data->AssignmentsCount;
|
||||
|
||||
public bool Relocatable => Symbol.Type != SymbolType.None;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Register GetRegister()
|
||||
{
|
||||
Debug.Assert(Kind == OperandKind.Register);
|
||||
|
||||
return new Register((int)Value & 0xffffff, (RegisterType)(Value >> 24));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public MemoryOperand GetMemory()
|
||||
{
|
||||
Debug.Assert(Kind == OperandKind.Memory);
|
||||
|
||||
return new MemoryOperand(this);
|
||||
}
|
||||
|
||||
public int GetLocalNumber()
|
||||
{
|
||||
Debug.Assert(Kind == OperandKind.LocalVariable);
|
||||
|
||||
return (int)Value;
|
||||
}
|
||||
|
||||
public byte AsByte()
|
||||
{
|
||||
return (byte)Value;
|
||||
}
|
||||
|
||||
public short AsInt16()
|
||||
{
|
||||
return (short)Value;
|
||||
}
|
||||
|
||||
public int AsInt32()
|
||||
{
|
||||
return (int)Value;
|
||||
}
|
||||
|
||||
public long AsInt64()
|
||||
{
|
||||
return (long)Value;
|
||||
}
|
||||
|
||||
public float AsFloat()
|
||||
{
|
||||
return BitConverter.Int32BitsToSingle((int)Value);
|
||||
}
|
||||
|
||||
public double AsDouble()
|
||||
{
|
||||
return BitConverter.Int64BitsToDouble((long)Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ref ulong GetValueUnsafe()
|
||||
{
|
||||
return ref _data->Value;
|
||||
}
|
||||
|
||||
internal void NumberLocal(int number)
|
||||
{
|
||||
if (Kind != OperandKind.LocalVariable)
|
||||
{
|
||||
throw new InvalidOperationException("The operand is not a local variable.");
|
||||
}
|
||||
|
||||
Value = (ulong)number;
|
||||
}
|
||||
|
||||
public void AddAssignment(Operation operation)
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
Add(operation, ref _data->Assignments, ref _data->AssignmentsCount, ref _data->AssignmentsCapacity);
|
||||
}
|
||||
else if (Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = GetMemory();
|
||||
Operand addr = memOp.BaseAddress;
|
||||
Operand index = memOp.Index;
|
||||
|
||||
if (addr != default)
|
||||
{
|
||||
Add(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount, ref addr._data->AssignmentsCapacity);
|
||||
}
|
||||
|
||||
if (index != default)
|
||||
{
|
||||
Add(operation, ref index._data->Assignments, ref index._data->AssignmentsCount, ref index._data->AssignmentsCapacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAssignment(Operation operation)
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
Remove(operation, ref _data->Assignments, ref _data->AssignmentsCount);
|
||||
}
|
||||
else if (Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = GetMemory();
|
||||
Operand addr = memOp.BaseAddress;
|
||||
Operand index = memOp.Index;
|
||||
|
||||
if (addr != default)
|
||||
{
|
||||
Remove(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount);
|
||||
}
|
||||
|
||||
if (index != default)
|
||||
{
|
||||
Remove(operation, ref index._data->Assignments, ref index._data->AssignmentsCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddUse(Operation operation)
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
Add(operation, ref _data->Uses, ref _data->UsesCount, ref _data->UsesCapacity);
|
||||
}
|
||||
else if (Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = GetMemory();
|
||||
Operand addr = memOp.BaseAddress;
|
||||
Operand index = memOp.Index;
|
||||
|
||||
if (addr != default)
|
||||
{
|
||||
Add(operation, ref addr._data->Uses, ref addr._data->UsesCount, ref addr._data->UsesCapacity);
|
||||
}
|
||||
|
||||
if (index != default)
|
||||
{
|
||||
Add(operation, ref index._data->Uses, ref index._data->UsesCount, ref index._data->UsesCapacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveUse(Operation operation)
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
{
|
||||
Remove(operation, ref _data->Uses, ref _data->UsesCount);
|
||||
}
|
||||
else if (Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memOp = GetMemory();
|
||||
Operand addr = memOp.BaseAddress;
|
||||
Operand index = memOp.Index;
|
||||
|
||||
if (addr != default)
|
||||
{
|
||||
Remove(operation, ref addr._data->Uses, ref addr._data->UsesCount);
|
||||
}
|
||||
|
||||
if (index != default)
|
||||
{
|
||||
Remove(operation, ref index._data->Uses, ref index._data->UsesCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void New<T>(ref T* data, ref ushort count, ref ushort capacity, ushort initialCapacity) where T : unmanaged
|
||||
{
|
||||
count = 0;
|
||||
capacity = initialCapacity;
|
||||
data = Allocators.References.Allocate<T>(initialCapacity);
|
||||
}
|
||||
|
||||
private static void New<T>(ref T* data, ref uint count, ref uint capacity, uint initialCapacity) where T : unmanaged
|
||||
{
|
||||
count = 0;
|
||||
capacity = initialCapacity;
|
||||
data = Allocators.References.Allocate<T>(initialCapacity);
|
||||
}
|
||||
|
||||
private static void Add<T>(T item, ref T* data, ref ushort count, ref ushort capacity) where T : unmanaged
|
||||
{
|
||||
if (count < capacity)
|
||||
{
|
||||
data[(uint)count++] = item;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Could not add item in the fast path, fallback onto the slow path.
|
||||
ExpandAdd(item, ref data, ref count, ref capacity);
|
||||
|
||||
static void ExpandAdd(T item, ref T* data, ref ushort count, ref ushort capacity)
|
||||
{
|
||||
ushort newCount = checked((ushort)(count + 1));
|
||||
ushort newCapacity = (ushort)Math.Min(capacity * 2, ushort.MaxValue);
|
||||
|
||||
var oldSpan = new Span<T>(data, count);
|
||||
|
||||
capacity = newCapacity;
|
||||
data = Allocators.References.Allocate<T>(capacity);
|
||||
|
||||
oldSpan.CopyTo(new Span<T>(data, count));
|
||||
|
||||
data[count] = item;
|
||||
count = newCount;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Add<T>(T item, ref T* data, ref uint count, ref uint capacity) where T : unmanaged
|
||||
{
|
||||
if (count < capacity)
|
||||
{
|
||||
data[count++] = item;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Could not add item in the fast path, fallback onto the slow path.
|
||||
ExpandAdd(item, ref data, ref count, ref capacity);
|
||||
|
||||
static void ExpandAdd(T item, ref T* data, ref uint count, ref uint capacity)
|
||||
{
|
||||
uint newCount = checked(count + 1);
|
||||
uint newCapacity = (uint)Math.Min(capacity * 2, int.MaxValue);
|
||||
|
||||
if (newCapacity <= capacity)
|
||||
{
|
||||
throw new OverflowException();
|
||||
}
|
||||
|
||||
var oldSpan = new Span<T>(data, (int)count);
|
||||
|
||||
capacity = newCapacity;
|
||||
data = Allocators.References.Allocate<T>(capacity);
|
||||
|
||||
oldSpan.CopyTo(new Span<T>(data, (int)count));
|
||||
|
||||
data[count] = item;
|
||||
count = newCount;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Remove<T>(in T item, ref T* data, ref ushort count) where T : unmanaged
|
||||
{
|
||||
var span = new Span<T>(data, count);
|
||||
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(span[i], item))
|
||||
{
|
||||
if (i + 1 < count)
|
||||
{
|
||||
span.Slice(i + 1).CopyTo(span.Slice(i));
|
||||
}
|
||||
|
||||
count--;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Remove<T>(in T item, ref T* data, ref uint count) where T : unmanaged
|
||||
{
|
||||
var span = new Span<T>(data, (int)count);
|
||||
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(span[i], item))
|
||||
{
|
||||
if (i + 1 < count)
|
||||
{
|
||||
span.Slice(i + 1).CopyTo(span.Slice(i));
|
||||
}
|
||||
|
||||
count--;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ((ulong)_data).GetHashCode();
|
||||
}
|
||||
|
||||
public bool Equals(Operand operand)
|
||||
{
|
||||
return operand._data == _data;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Operand operand && Equals(operand);
|
||||
}
|
||||
|
||||
public static bool operator ==(Operand a, Operand b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Operand a, Operand b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static class Factory
|
||||
{
|
||||
private const int InternTableSize = 256;
|
||||
private const int InternTableProbeLength = 8;
|
||||
|
||||
[ThreadStatic]
|
||||
private static Data* _internTable;
|
||||
|
||||
private static Data* InternTable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_internTable == null)
|
||||
{
|
||||
_internTable = (Data*)NativeAllocator.Instance.Allocate((uint)sizeof(Data) * InternTableSize);
|
||||
|
||||
// Make sure the table is zeroed.
|
||||
new Span<Data>(_internTable, InternTableSize).Clear();
|
||||
}
|
||||
|
||||
return _internTable;
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand Make(OperandKind kind, OperandType type, ulong value, Symbol symbol = default)
|
||||
{
|
||||
Debug.Assert(kind != OperandKind.None);
|
||||
|
||||
Data* data = null;
|
||||
|
||||
// If constant or register, then try to look up in the intern table before allocating.
|
||||
if (kind == OperandKind.Constant || kind == OperandKind.Register)
|
||||
{
|
||||
uint hash = (uint)HashCode.Combine(kind, type, value);
|
||||
|
||||
// Look in the next InternTableProbeLength slots for a match.
|
||||
for (uint i = 0; i < InternTableProbeLength; i++)
|
||||
{
|
||||
Operand interned = new();
|
||||
interned._data = &InternTable[(hash + i) % InternTableSize];
|
||||
|
||||
// If slot matches the allocation request then return that slot.
|
||||
if (interned.Kind == kind && interned.Type == type && interned.Value == value && interned.Symbol == symbol)
|
||||
{
|
||||
return interned;
|
||||
}
|
||||
// Otherwise if the slot is not occupied, we store in that slot.
|
||||
else if (interned.Kind == OperandKind.None)
|
||||
{
|
||||
data = interned._data;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we could not get a slot from the intern table, we allocate somewhere else and store there.
|
||||
if (data == null)
|
||||
{
|
||||
data = Allocators.Operands.Allocate<Data>();
|
||||
}
|
||||
|
||||
*data = default;
|
||||
|
||||
Operand result = new();
|
||||
result._data = data;
|
||||
result.Value = value;
|
||||
result.Kind = kind;
|
||||
result.Type = type;
|
||||
|
||||
if (kind != OperandKind.Memory)
|
||||
{
|
||||
result.Symbol = symbol;
|
||||
}
|
||||
|
||||
// If local variable, then the use and def list is initialized with default sizes.
|
||||
if (kind == OperandKind.LocalVariable)
|
||||
{
|
||||
New(ref result._data->Assignments, ref result._data->AssignmentsCount, ref result._data->AssignmentsCapacity, 1);
|
||||
New(ref result._data->Uses, ref result._data->UsesCount, ref result._data->UsesCapacity, 4);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operand Const(OperandType type, long value)
|
||||
{
|
||||
Debug.Assert(type is OperandType.I32 or OperandType.I64);
|
||||
|
||||
return type == OperandType.I32 ? Const((int)value) : Const(value);
|
||||
}
|
||||
|
||||
public static Operand Const(bool value)
|
||||
{
|
||||
return Const(value ? 1 : 0);
|
||||
}
|
||||
|
||||
public static Operand Const(int value)
|
||||
{
|
||||
return Const((uint)value);
|
||||
}
|
||||
|
||||
public static Operand Const(uint value)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.I32, value);
|
||||
}
|
||||
|
||||
public static Operand Const(long value)
|
||||
{
|
||||
return Const(value, symbol: default);
|
||||
}
|
||||
|
||||
public static Operand Const<T>(ref T reference, Symbol symbol = default)
|
||||
{
|
||||
return Const((long)Unsafe.AsPointer(ref reference), symbol);
|
||||
}
|
||||
|
||||
public static Operand Const(long value, Symbol symbol)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.I64, (ulong)value, symbol);
|
||||
}
|
||||
|
||||
public static Operand Const(ulong value)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.I64, value);
|
||||
}
|
||||
|
||||
public static Operand ConstF(float value)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.FP32, (ulong)BitConverter.SingleToInt32Bits(value));
|
||||
}
|
||||
|
||||
public static Operand ConstF(double value)
|
||||
{
|
||||
return Make(OperandKind.Constant, OperandType.FP64, (ulong)BitConverter.DoubleToInt64Bits(value));
|
||||
}
|
||||
|
||||
public static Operand Label()
|
||||
{
|
||||
return Make(OperandKind.Label, OperandType.None, 0);
|
||||
}
|
||||
|
||||
public static Operand Local(OperandType type)
|
||||
{
|
||||
return Make(OperandKind.LocalVariable, type, 0);
|
||||
}
|
||||
|
||||
public static Operand Register(int index, RegisterType regType, OperandType type)
|
||||
{
|
||||
return Make(OperandKind.Register, type, (ulong)((int)regType << 24 | index));
|
||||
}
|
||||
|
||||
public static Operand Undef()
|
||||
{
|
||||
return Make(OperandKind.Undefined, OperandType.None, 0);
|
||||
}
|
||||
|
||||
public static Operand MemoryOp(
|
||||
OperandType type,
|
||||
Operand baseAddress,
|
||||
Operand index = default,
|
||||
Multiplier scale = Multiplier.x1,
|
||||
int displacement = 0)
|
||||
{
|
||||
Operand result = Make(OperandKind.Memory, type, 0);
|
||||
|
||||
MemoryOperand memory = result.GetMemory();
|
||||
memory.BaseAddress = baseAddress;
|
||||
memory.Index = index;
|
||||
memory.Scale = scale;
|
||||
memory.Displacement = displacement;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
enum OperandType
|
||||
{
|
||||
None,
|
||||
I32,
|
||||
I64,
|
||||
FP32,
|
||||
FP64,
|
||||
V128
|
||||
}
|
||||
|
||||
static class OperandTypeExtensions
|
||||
{
|
||||
public static bool IsInteger(this OperandType type)
|
||||
{
|
||||
return type == OperandType.I32 ||
|
||||
type == OperandType.I64;
|
||||
}
|
||||
|
||||
public static RegisterType ToRegisterType(this OperandType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case OperandType.FP32: return RegisterType.Vector;
|
||||
case OperandType.FP64: return RegisterType.Vector;
|
||||
case OperandType.I32: return RegisterType.Integer;
|
||||
case OperandType.I64: return RegisterType.Integer;
|
||||
case OperandType.V128: return RegisterType.Vector;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Invalid operand type \"{type}\".");
|
||||
}
|
||||
|
||||
public static int GetSizeInBytes(this OperandType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case OperandType.FP32: return 4;
|
||||
case OperandType.FP64: return 8;
|
||||
case OperandType.I32: return 4;
|
||||
case OperandType.I64: return 8;
|
||||
case OperandType.V128: return 16;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Invalid operand type \"{type}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace ARMeilleure.Memory
|
||||
{
|
||||
public interface IJitMemoryAllocator
|
||||
{
|
||||
IJitMemoryBlock Allocate(ulong size);
|
||||
IJitMemoryBlock Reserve(ulong size);
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.Memory
|
||||
{
|
||||
public interface IMemoryManager
|
||||
{
|
||||
int AddressSpaceBits { get; }
|
||||
|
||||
IntPtr PageTablePointer { get; }
|
||||
|
||||
MemoryManagerType Type { get; }
|
||||
|
||||
event Action<ulong, ulong> UnmapEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from CPU mapped memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data being read</typeparam>
|
||||
/// <param name="va">Virtual address of the data in memory</param>
|
||||
/// <returns>The data</returns>
|
||||
T Read<T>(ulong va) where T : unmanaged;
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from CPU mapped memory, with read tracking
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data being read</typeparam>
|
||||
/// <param name="va">Virtual address of the data in memory</param>
|
||||
/// <returns>The data</returns>
|
||||
T ReadTracked<T>(ulong va) where T : unmanaged;
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to CPU mapped memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data being written</typeparam>
|
||||
/// <param name="va">Virtual address to write the data into</param>
|
||||
/// <param name="value">Data to be written</param>
|
||||
void Write<T>(ulong va, T value) where T : unmanaged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only span of data from CPU mapped memory.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the data</param>
|
||||
/// <param name="size">Size of the data</param>
|
||||
/// <param name="tracked">True if read tracking is triggered on the span</param>
|
||||
/// <returns>A read-only span of the data</returns>
|
||||
ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference for the given type at the specified virtual memory address.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The data must be located at a contiguous memory region.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">Type of the data to get the reference</typeparam>
|
||||
/// <param name="va">Virtual address of the data</param>
|
||||
/// <returns>A reference to the data in memory</returns>
|
||||
ref T GetRef<T>(ulong va) where T : unmanaged;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the page at a given CPU virtual address is mapped.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address to check</param>
|
||||
/// <returns>True if the address is mapped, false otherwise</returns>
|
||||
bool IsMapped(ulong va);
|
||||
|
||||
/// <summary>
|
||||
/// Alerts the memory tracking that a given region has been read from or written to.
|
||||
/// This should be called before read/write is performed.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the region</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <param name="write">True if the region was written, false if read</param>
|
||||
/// <param name="precise">True if the access is precise, false otherwise</param>
|
||||
void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.Memory
|
||||
{
|
||||
class ReservedRegion
|
||||
{
|
||||
private const int DefaultGranularity = 65536; // Mapping granularity in Windows.
|
||||
|
||||
public IJitMemoryBlock Block { get; }
|
||||
|
||||
public IntPtr Pointer => Block.Pointer;
|
||||
|
||||
private readonly ulong _maxSize;
|
||||
private readonly ulong _sizeGranularity;
|
||||
private ulong _currentSize;
|
||||
|
||||
public ReservedRegion(IJitMemoryAllocator allocator, ulong maxSize, ulong granularity = 0)
|
||||
{
|
||||
if (granularity == 0)
|
||||
{
|
||||
granularity = DefaultGranularity;
|
||||
}
|
||||
|
||||
Block = allocator.Reserve(maxSize);
|
||||
_maxSize = maxSize;
|
||||
_sizeGranularity = granularity;
|
||||
_currentSize = 0;
|
||||
}
|
||||
|
||||
public void ExpandIfNeeded(ulong desiredSize)
|
||||
{
|
||||
if (desiredSize > _maxSize)
|
||||
{
|
||||
throw new OutOfMemoryException();
|
||||
}
|
||||
|
||||
if (desiredSize > _currentSize)
|
||||
{
|
||||
// Lock, and then check again. We only want to commit once.
|
||||
lock (this)
|
||||
{
|
||||
if (desiredSize >= _currentSize)
|
||||
{
|
||||
ulong overflowBytes = desiredSize - _currentSize;
|
||||
ulong moreToCommit = (((_sizeGranularity - 1) + overflowBytes) / _sizeGranularity) * _sizeGranularity; // Round up.
|
||||
Block.Commit(_currentSize, moreToCommit);
|
||||
_currentSize += moreToCommit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Block.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using ARMeilleure.CodeGen.X86;
|
||||
|
||||
namespace ARMeilleure
|
||||
{
|
||||
public static class Optimizations
|
||||
{
|
||||
public static bool FastFP { get; set; } = true;
|
||||
|
||||
public static bool AllowLcqInFunctionTable { get; set; } = true;
|
||||
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
||||
|
||||
public static bool UseSseIfAvailable { get; set; } = true;
|
||||
public static bool UseSse2IfAvailable { get; set; } = true;
|
||||
public static bool UseSse3IfAvailable { get; set; } = true;
|
||||
public static bool UseSsse3IfAvailable { get; set; } = true;
|
||||
public static bool UseSse41IfAvailable { get; set; } = true;
|
||||
public static bool UseSse42IfAvailable { get; set; } = true;
|
||||
public static bool UsePopCntIfAvailable { get; set; } = true;
|
||||
public static bool UseAvxIfAvailable { get; set; } = true;
|
||||
public static bool UseF16cIfAvailable { get; set; } = true;
|
||||
public static bool UseFmaIfAvailable { get; set; } = true;
|
||||
public static bool UseAesniIfAvailable { get; set; } = true;
|
||||
public static bool UsePclmulqdqIfAvailable { get; set; } = true;
|
||||
public static bool UseShaIfAvailable { get; set; } = true;
|
||||
public static bool UseGfniIfAvailable { get; set; } = true;
|
||||
|
||||
public static bool ForceLegacySse
|
||||
{
|
||||
get => HardwareCapabilities.ForceLegacySse;
|
||||
set => HardwareCapabilities.ForceLegacySse = value;
|
||||
}
|
||||
|
||||
internal static bool UseSse => UseSseIfAvailable && HardwareCapabilities.SupportsSse;
|
||||
internal static bool UseSse2 => UseSse2IfAvailable && HardwareCapabilities.SupportsSse2;
|
||||
internal static bool UseSse3 => UseSse3IfAvailable && HardwareCapabilities.SupportsSse3;
|
||||
internal static bool UseSsse3 => UseSsse3IfAvailable && HardwareCapabilities.SupportsSsse3;
|
||||
internal static bool UseSse41 => UseSse41IfAvailable && HardwareCapabilities.SupportsSse41;
|
||||
internal static bool UseSse42 => UseSse42IfAvailable && HardwareCapabilities.SupportsSse42;
|
||||
internal static bool UsePopCnt => UsePopCntIfAvailable && HardwareCapabilities.SupportsPopcnt;
|
||||
internal static bool UseAvx => UseAvxIfAvailable && HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
||||
internal static bool UseF16c => UseF16cIfAvailable && HardwareCapabilities.SupportsF16c;
|
||||
internal static bool UseFma => UseFmaIfAvailable && HardwareCapabilities.SupportsFma;
|
||||
internal static bool UseAesni => UseAesniIfAvailable && HardwareCapabilities.SupportsAesni;
|
||||
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && HardwareCapabilities.SupportsPclmulqdq;
|
||||
internal static bool UseSha => UseShaIfAvailable && HardwareCapabilities.SupportsSha;
|
||||
internal static bool UseGfni => UseGfniIfAvailable && HardwareCapabilities.SupportsGfni;
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Signal
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct SignalHandlerRange
|
||||
{
|
||||
public int IsActive;
|
||||
public nuint RangeAddress;
|
||||
public nuint RangeEndAddress;
|
||||
public IntPtr ActionPointer;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct SignalHandlerConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The byte offset of the faulting address in the SigInfo or ExceptionRecord struct.
|
||||
/// </summary>
|
||||
public int StructAddressOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset of the write flag in the SigInfo or ExceptionRecord struct.
|
||||
/// </summary>
|
||||
public int StructWriteOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The sigaction handler that was registered before this one. (unix only)
|
||||
/// </summary>
|
||||
public nuint UnixOldSigaction;
|
||||
|
||||
/// <summary>
|
||||
/// The type of the previous sigaction. True for the 3 argument variant. (unix only)
|
||||
/// </summary>
|
||||
public int UnixOldSigaction3Arg;
|
||||
|
||||
public SignalHandlerRange Range0;
|
||||
public SignalHandlerRange Range1;
|
||||
public SignalHandlerRange Range2;
|
||||
public SignalHandlerRange Range3;
|
||||
public SignalHandlerRange Range4;
|
||||
public SignalHandlerRange Range5;
|
||||
public SignalHandlerRange Range6;
|
||||
public SignalHandlerRange Range7;
|
||||
}
|
||||
|
||||
public static class NativeSignalHandler
|
||||
{
|
||||
private delegate void UnixExceptionHandler(int sig, IntPtr info, IntPtr ucontext);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
||||
private delegate int VectoredExceptionHandler(IntPtr exceptionInfo);
|
||||
|
||||
private const int MaxTrackedRanges = 8;
|
||||
|
||||
private const int StructAddressOffset = 0;
|
||||
private const int StructWriteOffset = 4;
|
||||
private const int UnixOldSigaction = 8;
|
||||
private const int UnixOldSigaction3Arg = 16;
|
||||
private const int RangeOffset = 20;
|
||||
|
||||
private const int EXCEPTION_CONTINUE_SEARCH = 0;
|
||||
private const int EXCEPTION_CONTINUE_EXECUTION = -1;
|
||||
|
||||
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
||||
|
||||
private const ulong PageSize = 0x1000;
|
||||
private const ulong PageMask = PageSize - 1;
|
||||
|
||||
private static IntPtr _handlerConfig;
|
||||
private static IntPtr _signalHandlerPtr;
|
||||
private static IntPtr _signalHandlerHandle;
|
||||
|
||||
private static readonly object _lock = new object();
|
||||
private static bool _initialized;
|
||||
|
||||
static NativeSignalHandler()
|
||||
{
|
||||
_handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
|
||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
||||
|
||||
config = new SignalHandlerConfig();
|
||||
}
|
||||
|
||||
public static void InitializeSignalHandler()
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
bool unix = OperatingSystem.IsLinux() || OperatingSystem.IsMacOS();
|
||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
||||
|
||||
if (unix)
|
||||
{
|
||||
// Unix siginfo struct locations.
|
||||
// NOTE: These are incredibly likely to be different between kernel version and architectures.
|
||||
|
||||
config.StructAddressOffset = OperatingSystem.IsMacOS() ? 24 : 16; // si_addr
|
||||
config.StructWriteOffset = 8; // si_code
|
||||
|
||||
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
|
||||
|
||||
SigAction old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
|
||||
config.UnixOldSigaction = (nuint)(ulong)old.sa_handler;
|
||||
config.UnixOldSigaction3Arg = old.sa_flags & 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
config.StructAddressOffset = 40; // ExceptionInformation1
|
||||
config.StructWriteOffset = 32; // ExceptionInformation0
|
||||
|
||||
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateWindowsSignalHandler(_handlerConfig));
|
||||
|
||||
_signalHandlerHandle = WindowsSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe ref SignalHandlerConfig GetConfigRef()
|
||||
{
|
||||
return ref Unsafe.AsRef<SignalHandlerConfig>((void*)_handlerConfig);
|
||||
}
|
||||
|
||||
public static unsafe bool AddTrackedRegion(nuint address, nuint endAddress, IntPtr action)
|
||||
{
|
||||
var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0;
|
||||
|
||||
for (int i = 0; i < MaxTrackedRanges; i++)
|
||||
{
|
||||
if (ranges[i].IsActive == 0)
|
||||
{
|
||||
ranges[i].RangeAddress = address;
|
||||
ranges[i].RangeEndAddress = endAddress;
|
||||
ranges[i].ActionPointer = action;
|
||||
ranges[i].IsActive = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static unsafe bool RemoveTrackedRegion(nuint address)
|
||||
{
|
||||
var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0;
|
||||
|
||||
for (int i = 0; i < MaxTrackedRanges; i++)
|
||||
{
|
||||
if (ranges[i].IsActive == 1 && ranges[i].RangeAddress == address)
|
||||
{
|
||||
ranges[i].IsActive = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite)
|
||||
{
|
||||
Operand inRegionLocal = context.AllocateLocal(OperandType.I32);
|
||||
context.Copy(inRegionLocal, Const(0));
|
||||
|
||||
Operand endLabel = Label();
|
||||
|
||||
for (int i = 0; i < MaxTrackedRanges; i++)
|
||||
{
|
||||
ulong rangeBaseOffset = (ulong)(RangeOffset + i * Unsafe.SizeOf<SignalHandlerRange>());
|
||||
|
||||
Operand nextLabel = Label();
|
||||
|
||||
Operand isActive = context.Load(OperandType.I32, Const((ulong)signalStructPtr + rangeBaseOffset));
|
||||
|
||||
context.BranchIfFalse(nextLabel, isActive);
|
||||
|
||||
Operand rangeAddress = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 4));
|
||||
Operand rangeEndAddress = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 12));
|
||||
|
||||
// Is the fault address within this tracked region?
|
||||
Operand inRange = context.BitwiseAnd(
|
||||
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
|
||||
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI)
|
||||
);
|
||||
|
||||
// Only call tracking if in range.
|
||||
context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold);
|
||||
|
||||
Operand offset = context.BitwiseAnd(context.Subtract(faultAddress, rangeAddress), Const(~PageMask));
|
||||
|
||||
// Call the tracking action, with the pointer's relative offset to the base address.
|
||||
Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20));
|
||||
|
||||
context.Copy(inRegionLocal, Const(0));
|
||||
|
||||
Operand skipActionLabel = Label();
|
||||
|
||||
// Tracking action should be non-null to call it, otherwise assume false return.
|
||||
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
|
||||
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(PageSize), isWrite, Const(0));
|
||||
context.Copy(inRegionLocal, result);
|
||||
|
||||
context.MarkLabel(skipActionLabel);
|
||||
|
||||
// If the tracking action returns false or does not exist, it might be an invalid access due to a partial overlap on Windows.
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
context.BranchIfTrue(endLabel, inRegionLocal);
|
||||
|
||||
context.Copy(inRegionLocal, WindowsPartialUnmapHandler.EmitRetryFromAccessViolation(context));
|
||||
}
|
||||
|
||||
context.Branch(endLabel);
|
||||
|
||||
context.MarkLabel(nextLabel);
|
||||
}
|
||||
|
||||
context.MarkLabel(endLabel);
|
||||
|
||||
return context.Copy(inRegionLocal);
|
||||
}
|
||||
|
||||
private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr)
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
// (int sig, SigInfo* sigInfo, void* ucontext)
|
||||
Operand sigInfoPtr = context.LoadArgument(OperandType.I64, 1);
|
||||
|
||||
Operand structAddressOffset = context.Load(OperandType.I64, Const((ulong)signalStructPtr + StructAddressOffset));
|
||||
Operand structWriteOffset = context.Load(OperandType.I64, Const((ulong)signalStructPtr + StructWriteOffset));
|
||||
|
||||
Operand faultAddress = context.Load(OperandType.I64, context.Add(sigInfoPtr, context.ZeroExtend32(OperandType.I64, structAddressOffset)));
|
||||
Operand writeFlag = context.Load(OperandType.I64, context.Add(sigInfoPtr, context.ZeroExtend32(OperandType.I64, structWriteOffset)));
|
||||
|
||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
||||
|
||||
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite);
|
||||
|
||||
Operand endLabel = Label();
|
||||
|
||||
context.BranchIfTrue(endLabel, isInRegion);
|
||||
|
||||
Operand unixOldSigaction = context.Load(OperandType.I64, Const((ulong)signalStructPtr + UnixOldSigaction));
|
||||
Operand unixOldSigaction3Arg = context.Load(OperandType.I64, Const((ulong)signalStructPtr + UnixOldSigaction3Arg));
|
||||
Operand threeArgLabel = Label();
|
||||
|
||||
context.BranchIfTrue(threeArgLabel, unixOldSigaction3Arg);
|
||||
|
||||
context.Call(unixOldSigaction, OperandType.None, context.LoadArgument(OperandType.I32, 0));
|
||||
context.Branch(endLabel);
|
||||
|
||||
context.MarkLabel(threeArgLabel);
|
||||
|
||||
context.Call(unixOldSigaction,
|
||||
OperandType.None,
|
||||
context.LoadArgument(OperandType.I32, 0),
|
||||
sigInfoPtr,
|
||||
context.LoadArgument(OperandType.I64, 2)
|
||||
);
|
||||
|
||||
context.MarkLabel(endLabel);
|
||||
|
||||
context.Return();
|
||||
|
||||
ControlFlowGraph cfg = context.GetControlFlowGraph();
|
||||
|
||||
OperandType[] argTypes = new OperandType[] { OperandType.I32, OperandType.I64, OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq).Map<UnixExceptionHandler>();
|
||||
}
|
||||
|
||||
private static VectoredExceptionHandler GenerateWindowsSignalHandler(IntPtr signalStructPtr)
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
// (ExceptionPointers* exceptionInfo)
|
||||
Operand exceptionInfoPtr = context.LoadArgument(OperandType.I64, 0);
|
||||
Operand exceptionRecordPtr = context.Load(OperandType.I64, exceptionInfoPtr);
|
||||
|
||||
// First thing's first - this catches a number of exceptions, but we only want access violations.
|
||||
Operand validExceptionLabel = Label();
|
||||
|
||||
Operand exceptionCode = context.Load(OperandType.I32, exceptionRecordPtr);
|
||||
|
||||
context.BranchIf(validExceptionLabel, exceptionCode, Const(EXCEPTION_ACCESS_VIOLATION), Comparison.Equal);
|
||||
|
||||
context.Return(Const(EXCEPTION_CONTINUE_SEARCH)); // Don't handle this one.
|
||||
|
||||
context.MarkLabel(validExceptionLabel);
|
||||
|
||||
// Next, read the address of the invalid access, and whether it is a write or not.
|
||||
|
||||
Operand structAddressOffset = context.Load(OperandType.I32, Const((ulong)signalStructPtr + StructAddressOffset));
|
||||
Operand structWriteOffset = context.Load(OperandType.I32, Const((ulong)signalStructPtr + StructWriteOffset));
|
||||
|
||||
Operand faultAddress = context.Load(OperandType.I64, context.Add(exceptionRecordPtr, context.ZeroExtend32(OperandType.I64, structAddressOffset)));
|
||||
Operand writeFlag = context.Load(OperandType.I64, context.Add(exceptionRecordPtr, context.ZeroExtend32(OperandType.I64, structWriteOffset)));
|
||||
|
||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
||||
|
||||
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite);
|
||||
|
||||
Operand endLabel = Label();
|
||||
|
||||
// If the region check result is false, then run the next vectored exception handler.
|
||||
|
||||
context.BranchIfTrue(endLabel, isInRegion);
|
||||
|
||||
context.Return(Const(EXCEPTION_CONTINUE_SEARCH));
|
||||
|
||||
context.MarkLabel(endLabel);
|
||||
|
||||
// Otherwise, return to execution.
|
||||
|
||||
context.Return(Const(EXCEPTION_CONTINUE_EXECUTION));
|
||||
|
||||
// Compile and return the function.
|
||||
|
||||
ControlFlowGraph cfg = context.GetControlFlowGraph();
|
||||
|
||||
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq).Map<VectoredExceptionHandler>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Signal
|
||||
{
|
||||
public struct NativeWriteLoopState
|
||||
{
|
||||
public int Running;
|
||||
public int Error;
|
||||
}
|
||||
|
||||
public static class TestMethods
|
||||
{
|
||||
public delegate bool DebugPartialUnmap();
|
||||
public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState);
|
||||
public delegate void DebugNativeWriteLoop(IntPtr nativeWriteLoopPtr, IntPtr writePtr);
|
||||
|
||||
public static DebugPartialUnmap GenerateDebugPartialUnmap()
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
var result = WindowsPartialUnmapHandler.EmitRetryFromAccessViolation(context);
|
||||
|
||||
context.Return(result);
|
||||
|
||||
// Compile and return the function.
|
||||
|
||||
ControlFlowGraph cfg = context.GetControlFlowGraph();
|
||||
|
||||
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq).Map<DebugPartialUnmap>();
|
||||
}
|
||||
|
||||
public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(IntPtr structPtr)
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
var result = WindowsPartialUnmapHandler.EmitThreadLocalMapIntGetOrReserve(context, structPtr, context.LoadArgument(OperandType.I32, 0), context.LoadArgument(OperandType.I32, 1));
|
||||
|
||||
context.Return(result);
|
||||
|
||||
// Compile and return the function.
|
||||
|
||||
ControlFlowGraph cfg = context.GetControlFlowGraph();
|
||||
|
||||
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq).Map<DebugThreadLocalMapGetOrReserve>();
|
||||
}
|
||||
|
||||
public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop()
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
// Loop a write to the target address until "running" is false.
|
||||
|
||||
Operand structPtr = context.Copy(context.LoadArgument(OperandType.I64, 0));
|
||||
Operand writePtr = context.Copy(context.LoadArgument(OperandType.I64, 1));
|
||||
|
||||
Operand loopLabel = Label();
|
||||
context.MarkLabel(loopLabel);
|
||||
|
||||
context.Store(writePtr, Const(12345));
|
||||
|
||||
Operand running = context.Load(OperandType.I32, structPtr);
|
||||
|
||||
context.BranchIfTrue(loopLabel, running);
|
||||
|
||||
context.Return();
|
||||
|
||||
// Compile and return the function.
|
||||
|
||||
ControlFlowGraph cfg = context.GetControlFlowGraph();
|
||||
|
||||
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq).Map<DebugNativeWriteLoop>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ARMeilleure.Signal
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
unsafe struct SigSet
|
||||
{
|
||||
fixed long sa_mask[16];
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct SigAction
|
||||
{
|
||||
public IntPtr sa_handler;
|
||||
public SigSet sa_mask;
|
||||
public int sa_flags;
|
||||
public IntPtr sa_restorer;
|
||||
}
|
||||
|
||||
static partial class UnixSignalHandlerRegistration
|
||||
{
|
||||
private const int SIGSEGV = 11;
|
||||
private const int SIGBUS = 10;
|
||||
private const int SA_SIGINFO = 0x00000004;
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int sigaction(int signum, ref SigAction sigAction, out SigAction oldAction);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int sigemptyset(ref SigSet set);
|
||||
|
||||
public static SigAction RegisterExceptionHandler(IntPtr action)
|
||||
{
|
||||
SigAction sig = new SigAction
|
||||
{
|
||||
sa_handler = action,
|
||||
sa_flags = SA_SIGINFO
|
||||
};
|
||||
|
||||
sigemptyset(ref sig.sa_mask);
|
||||
|
||||
int result = sigaction(SIGSEGV, ref sig, out SigAction old);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}");
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
result = sigaction(SIGBUS, ref sig, out SigAction oldb);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not register SIGBUS sigaction. Error: {result}");
|
||||
}
|
||||
}
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
public static bool RestoreExceptionHandler(SigAction oldAction)
|
||||
{
|
||||
return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
using ARMeilleure.Memory;
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
public class ExecutionContext
|
||||
{
|
||||
private const int MinCountForCheck = 4000;
|
||||
|
||||
private NativeContext _nativeContext;
|
||||
|
||||
internal IntPtr NativeContextPtr => _nativeContext.BasePtr;
|
||||
|
||||
private bool _interrupted;
|
||||
|
||||
private readonly ICounter _counter;
|
||||
|
||||
public ulong Pc => _nativeContext.GetPc();
|
||||
|
||||
public uint CtrEl0 => 0x8444c004;
|
||||
public uint DczidEl0 => 0x00000004;
|
||||
|
||||
public ulong CntfrqEl0 => _counter.Frequency;
|
||||
public ulong CntpctEl0 => _counter.Counter;
|
||||
|
||||
// CNTVCT_EL0 = CNTPCT_EL0 - CNTVOFF_EL2
|
||||
// Since EL2 isn't implemented, CNTVOFF_EL2 = 0
|
||||
public ulong CntvctEl0 => CntpctEl0;
|
||||
|
||||
public long TpidrEl0 { get; set; }
|
||||
public long TpidrroEl0 { get; set; }
|
||||
|
||||
public uint Pstate
|
||||
{
|
||||
get => _nativeContext.GetPstate();
|
||||
set => _nativeContext.SetPstate(value);
|
||||
}
|
||||
|
||||
public FPSR Fpsr
|
||||
{
|
||||
get => (FPSR)_nativeContext.GetFPState((uint)FPSR.Mask);
|
||||
set => _nativeContext.SetFPState((uint)value, (uint)FPSR.Mask);
|
||||
}
|
||||
|
||||
public FPCR Fpcr
|
||||
{
|
||||
get => (FPCR)_nativeContext.GetFPState((uint)FPCR.Mask);
|
||||
set => _nativeContext.SetFPState((uint)value, (uint)FPCR.Mask);
|
||||
}
|
||||
public FPCR StandardFpcrValue => (Fpcr & (FPCR.Ahp)) | FPCR.Dn | FPCR.Fz;
|
||||
|
||||
public FPSCR Fpscr
|
||||
{
|
||||
get => (FPSCR)_nativeContext.GetFPState((uint)FPSCR.Mask);
|
||||
set => _nativeContext.SetFPState((uint)value, (uint)FPSCR.Mask);
|
||||
}
|
||||
|
||||
public bool IsAarch32 { get; set; }
|
||||
|
||||
internal ExecutionMode ExecutionMode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsAarch32)
|
||||
{
|
||||
return GetPstateFlag(PState.TFlag)
|
||||
? ExecutionMode.Aarch32Thumb
|
||||
: ExecutionMode.Aarch32Arm;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ExecutionMode.Aarch64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Running
|
||||
{
|
||||
get => _nativeContext.GetRunning();
|
||||
private set => _nativeContext.SetRunning(value);
|
||||
}
|
||||
|
||||
private readonly ExceptionCallbackNoArgs _interruptCallback;
|
||||
private readonly ExceptionCallback _breakCallback;
|
||||
private readonly ExceptionCallback _supervisorCallback;
|
||||
private readonly ExceptionCallback _undefinedCallback;
|
||||
|
||||
public ExecutionContext(
|
||||
IJitMemoryAllocator allocator,
|
||||
ICounter counter,
|
||||
ExceptionCallbackNoArgs interruptCallback = null,
|
||||
ExceptionCallback breakCallback = null,
|
||||
ExceptionCallback supervisorCallback = null,
|
||||
ExceptionCallback undefinedCallback = null)
|
||||
{
|
||||
_nativeContext = new NativeContext(allocator);
|
||||
_counter = counter;
|
||||
_interruptCallback = interruptCallback;
|
||||
_breakCallback = breakCallback;
|
||||
_supervisorCallback = supervisorCallback;
|
||||
_undefinedCallback = undefinedCallback;
|
||||
|
||||
Running = true;
|
||||
|
||||
_nativeContext.SetCounter(MinCountForCheck);
|
||||
}
|
||||
|
||||
public ulong GetX(int index) => _nativeContext.GetX(index);
|
||||
public void SetX(int index, ulong value) => _nativeContext.SetX(index, value);
|
||||
|
||||
public V128 GetV(int index) => _nativeContext.GetV(index);
|
||||
public void SetV(int index, V128 value) => _nativeContext.SetV(index, value);
|
||||
|
||||
public bool GetPstateFlag(PState flag) => _nativeContext.GetPstateFlag(flag);
|
||||
public void SetPstateFlag(PState flag, bool value) => _nativeContext.SetPstateFlag(flag, value);
|
||||
|
||||
public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag);
|
||||
public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value);
|
||||
|
||||
internal void CheckInterrupt()
|
||||
{
|
||||
if (_interrupted)
|
||||
{
|
||||
_interrupted = false;
|
||||
|
||||
_interruptCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
_nativeContext.SetCounter(MinCountForCheck);
|
||||
}
|
||||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
_interrupted = true;
|
||||
}
|
||||
|
||||
internal void OnBreak(ulong address, int imm)
|
||||
{
|
||||
_breakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
internal void OnSupervisorCall(ulong address, int imm)
|
||||
{
|
||||
_supervisorCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
internal void OnUndefined(ulong address, int opCode)
|
||||
{
|
||||
_undefinedCallback?.Invoke(this, address, opCode);
|
||||
}
|
||||
|
||||
public void StopRunning()
|
||||
{
|
||||
Running = false;
|
||||
|
||||
_nativeContext.SetCounter(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_nativeContext.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Memory;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
class NativeContext : IDisposable
|
||||
{
|
||||
private unsafe struct NativeCtxStorage
|
||||
{
|
||||
public fixed ulong X[RegisterConsts.IntRegsCount];
|
||||
public fixed ulong V[RegisterConsts.VecRegsCount * 2];
|
||||
public fixed uint Flags[RegisterConsts.FlagsCount];
|
||||
public fixed uint FpFlags[RegisterConsts.FpFlagsCount];
|
||||
public int Counter;
|
||||
public ulong DispatchAddress;
|
||||
public ulong ExclusiveAddress;
|
||||
public ulong ExclusiveValueLow;
|
||||
public ulong ExclusiveValueHigh;
|
||||
public int Running;
|
||||
}
|
||||
|
||||
private static NativeCtxStorage _dummyStorage = new NativeCtxStorage();
|
||||
|
||||
private readonly IJitMemoryBlock _block;
|
||||
|
||||
public IntPtr BasePtr => _block.Pointer;
|
||||
|
||||
public NativeContext(IJitMemoryAllocator allocator)
|
||||
{
|
||||
_block = allocator.Allocate((ulong)Unsafe.SizeOf<NativeCtxStorage>());
|
||||
|
||||
GetStorage().ExclusiveAddress = ulong.MaxValue;
|
||||
}
|
||||
|
||||
public ulong GetPc()
|
||||
{
|
||||
// TODO: More precise tracking of PC value.
|
||||
return GetStorage().DispatchAddress;
|
||||
}
|
||||
|
||||
public unsafe ulong GetX(int index)
|
||||
{
|
||||
if ((uint)index >= RegisterConsts.IntRegsCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
return GetStorage().X[index];
|
||||
}
|
||||
|
||||
public unsafe void SetX(int index, ulong value)
|
||||
{
|
||||
if ((uint)index >= RegisterConsts.IntRegsCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
GetStorage().X[index] = value;
|
||||
}
|
||||
|
||||
public unsafe V128 GetV(int index)
|
||||
{
|
||||
if ((uint)index >= RegisterConsts.VecRegsCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
return new V128(GetStorage().V[index * 2 + 0], GetStorage().V[index * 2 + 1]);
|
||||
}
|
||||
|
||||
public unsafe void SetV(int index, V128 value)
|
||||
{
|
||||
if ((uint)index >= RegisterConsts.VecRegsCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
GetStorage().V[index * 2 + 0] = value.Extract<ulong>(0);
|
||||
GetStorage().V[index * 2 + 1] = value.Extract<ulong>(1);
|
||||
}
|
||||
|
||||
public unsafe bool GetPstateFlag(PState flag)
|
||||
{
|
||||
if ((uint)flag >= RegisterConsts.FlagsCount)
|
||||
{
|
||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||
}
|
||||
|
||||
return GetStorage().Flags[(int)flag] != 0;
|
||||
}
|
||||
|
||||
public unsafe void SetPstateFlag(PState flag, bool value)
|
||||
{
|
||||
if ((uint)flag >= RegisterConsts.FlagsCount)
|
||||
{
|
||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||
}
|
||||
|
||||
return GetStorage().FpFlags[(int)flag] != 0;
|
||||
}
|
||||
|
||||
public unsafe void SetFPStateFlag(FPState flag, bool value)
|
||||
{
|
||||
if ((uint)flag >= RegisterConsts.FpFlagsCount)
|
||||
{
|
||||
throw new ArgumentException($"Invalid flag \"{flag}\" specified.");
|
||||
}
|
||||
|
||||
GetStorage().FpFlags[(int)flag] = value ? 1u : 0u;
|
||||
}
|
||||
|
||||
public unsafe uint GetFPState(uint mask = uint.MaxValue)
|
||||
{
|
||||
uint value = 0;
|
||||
for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++)
|
||||
{
|
||||
uint bit = 1u << flag;
|
||||
|
||||
if ((mask & bit) == bit)
|
||||
{
|
||||
value |= GetStorage().FpFlags[flag] != 0 ? bit : 0u;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public unsafe void SetFPState(uint value, uint mask = uint.MaxValue)
|
||||
{
|
||||
for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++)
|
||||
{
|
||||
uint bit = 1u << flag;
|
||||
|
||||
if ((mask & bit) == bit)
|
||||
{
|
||||
GetStorage().FpFlags[flag] = (value & bit) == bit ? 1u : 0u;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetCounter() => GetStorage().Counter;
|
||||
public void SetCounter(int value) => GetStorage().Counter = value;
|
||||
|
||||
public bool GetRunning() => GetStorage().Running != 0;
|
||||
public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0;
|
||||
|
||||
public unsafe static int GetRegisterOffset(Register reg)
|
||||
{
|
||||
if (reg.Type == RegisterType.Integer)
|
||||
{
|
||||
if ((uint)reg.Index >= RegisterConsts.IntRegsCount)
|
||||
{
|
||||
throw new ArgumentException("Invalid register.");
|
||||
}
|
||||
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.X[reg.Index]);
|
||||
}
|
||||
else if (reg.Type == RegisterType.Vector)
|
||||
{
|
||||
if ((uint)reg.Index >= RegisterConsts.VecRegsCount)
|
||||
{
|
||||
throw new ArgumentException("Invalid register.");
|
||||
}
|
||||
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.V[reg.Index * 2]);
|
||||
}
|
||||
else if (reg.Type == RegisterType.Flag)
|
||||
{
|
||||
if ((uint)reg.Index >= RegisterConsts.FlagsCount)
|
||||
{
|
||||
throw new ArgumentException("Invalid register.");
|
||||
}
|
||||
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Flags[reg.Index]);
|
||||
}
|
||||
else /* if (reg.Type == RegisterType.FpFlag) */
|
||||
{
|
||||
if ((uint)reg.Index >= RegisterConsts.FpFlagsCount)
|
||||
{
|
||||
throw new ArgumentException("Invalid register.");
|
||||
}
|
||||
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.FpFlags[reg.Index]);
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetCounterOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
||||
}
|
||||
|
||||
public static int GetDispatchAddressOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.DispatchAddress);
|
||||
}
|
||||
|
||||
public static int GetExclusiveAddressOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.ExclusiveAddress);
|
||||
}
|
||||
|
||||
public static int GetExclusiveValueOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.ExclusiveValueLow);
|
||||
}
|
||||
|
||||
public static int GetRunningOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running);
|
||||
}
|
||||
|
||||
private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target)
|
||||
{
|
||||
return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target);
|
||||
}
|
||||
|
||||
private unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef<NativeCtxStorage>((void*)_block.Pointer);
|
||||
|
||||
public void Dispose() => _block.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
using ARMeilleure.CodeGen.Linking;
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.Diagnostics;
|
||||
using ARMeilleure.Instructions;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.State;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
class ArmEmitterContext : EmitterContext
|
||||
{
|
||||
private readonly Dictionary<ulong, Operand> _labels;
|
||||
|
||||
private OpCode _optOpLastCompare;
|
||||
private OpCode _optOpLastFlagSet;
|
||||
|
||||
private Operand _optCmpTempN;
|
||||
private Operand _optCmpTempM;
|
||||
|
||||
private Block _currBlock;
|
||||
|
||||
public Block CurrBlock
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currBlock;
|
||||
}
|
||||
set
|
||||
{
|
||||
_currBlock = value;
|
||||
|
||||
ResetBlockState();
|
||||
}
|
||||
}
|
||||
|
||||
public OpCode CurrOp { get; set; }
|
||||
|
||||
public IMemoryManager Memory { get; }
|
||||
|
||||
public EntryTable<uint> CountTable { get; }
|
||||
public AddressTable<ulong> FunctionTable { get; }
|
||||
public TranslatorStubs Stubs { get; }
|
||||
|
||||
public ulong EntryAddress { get; }
|
||||
public bool HighCq { get; }
|
||||
public bool HasPtc { 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,
|
||||
AddressTable<ulong> funcTable,
|
||||
TranslatorStubs stubs,
|
||||
ulong entryAddress,
|
||||
bool highCq,
|
||||
bool hasPtc,
|
||||
Aarch32Mode mode)
|
||||
{
|
||||
Memory = memory;
|
||||
CountTable = countTable;
|
||||
FunctionTable = funcTable;
|
||||
Stubs = stubs;
|
||||
EntryAddress = entryAddress;
|
||||
HighCq = highCq;
|
||||
HasPtc = hasPtc;
|
||||
Mode = mode;
|
||||
|
||||
_labels = new Dictionary<ulong, Operand>();
|
||||
}
|
||||
|
||||
public override Operand Call(MethodInfo info, params Operand[] callArgs)
|
||||
{
|
||||
if (!HasPtc)
|
||||
{
|
||||
return base.Call(info, callArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = Delegates.GetDelegateIndex(info);
|
||||
IntPtr funcPtr = Delegates.GetDelegateFuncPtrByIndex(index);
|
||||
|
||||
OperandType returnType = GetOperandType(info.ReturnType);
|
||||
|
||||
Symbol symbol = new Symbol(SymbolType.DelegateTable, (ulong)index);
|
||||
|
||||
Symbols.Add((ulong)funcPtr.ToInt64(), info.Name);
|
||||
|
||||
return Call(Const(funcPtr.ToInt64(), symbol), returnType, callArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public Operand GetLabel(ulong address)
|
||||
{
|
||||
if (!_labels.TryGetValue(address, out Operand label))
|
||||
{
|
||||
label = Label();
|
||||
|
||||
_labels.Add(address, label);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
public void MarkComparison(Operand n, Operand m)
|
||||
{
|
||||
_optOpLastCompare = CurrOp;
|
||||
|
||||
_optCmpTempN = Copy(n);
|
||||
_optCmpTempM = Copy(m);
|
||||
}
|
||||
|
||||
public void MarkFlagSet(PState stateFlag)
|
||||
{
|
||||
// Set this only if any of the NZCV flag bits were modified.
|
||||
// This is used to ensure that when emiting a direct IL branch
|
||||
// instruction for compare + branch sequences, we're not expecting
|
||||
// to use comparison values from an old instruction, when in fact
|
||||
// the flags were already overwritten by another instruction further along.
|
||||
if (stateFlag >= PState.VFlag)
|
||||
{
|
||||
_optOpLastFlagSet = CurrOp;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetBlockState()
|
||||
{
|
||||
_optOpLastCompare = null;
|
||||
_optOpLastFlagSet = null;
|
||||
}
|
||||
|
||||
public Operand TryGetComparisonResult(Condition condition)
|
||||
{
|
||||
if (_optOpLastCompare == null || _optOpLastCompare != _optOpLastFlagSet)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
Operand n = _optCmpTempN;
|
||||
Operand m = _optCmpTempM;
|
||||
|
||||
InstName cmpName = _optOpLastCompare.Instruction.Name;
|
||||
|
||||
if (cmpName == InstName.Subs)
|
||||
{
|
||||
switch (condition)
|
||||
{
|
||||
case Condition.Eq: return ICompareEqual (n, m);
|
||||
case Condition.Ne: return ICompareNotEqual (n, m);
|
||||
case Condition.GeUn: return ICompareGreaterOrEqualUI(n, m);
|
||||
case Condition.LtUn: return ICompareLessUI (n, m);
|
||||
case Condition.GtUn: return ICompareGreaterUI (n, m);
|
||||
case Condition.LeUn: return ICompareLessOrEqualUI (n, m);
|
||||
case Condition.Ge: return ICompareGreaterOrEqual (n, m);
|
||||
case Condition.Lt: return ICompareLess (n, m);
|
||||
case Condition.Gt: return ICompareGreater (n, m);
|
||||
case Condition.Le: return ICompareLessOrEqual (n, m);
|
||||
}
|
||||
}
|
||||
else if (cmpName == InstName.Adds && _optOpLastCompare is IOpCodeAluImm op)
|
||||
{
|
||||
// There are several limitations that needs to be taken into account for CMN comparisons:
|
||||
// - The unsigned comparisons are not valid, as they depend on the
|
||||
// carry flag value, and they will have different values for addition and
|
||||
// subtraction. For addition, it's carry, and for subtraction, it's borrow.
|
||||
// So, we need to make sure we're not doing a unsigned compare for the CMN case.
|
||||
// - We can only do the optimization for the immediate variants,
|
||||
// because when the second operand value is exactly INT_MIN, we can't
|
||||
// negate the value as theres no positive counterpart.
|
||||
// Such invalid values can't be encoded on the immediate encodings.
|
||||
if (op.RegisterSize == RegisterSize.Int32)
|
||||
{
|
||||
m = Const((int)-op.Immediate);
|
||||
}
|
||||
else
|
||||
{
|
||||
m = Const(-op.Immediate);
|
||||
}
|
||||
|
||||
switch (condition)
|
||||
{
|
||||
case Condition.Eq: return ICompareEqual (n, m);
|
||||
case Condition.Ne: return ICompareNotEqual (n, m);
|
||||
case Condition.Ge: return ICompareGreaterOrEqual(n, m);
|
||||
case Condition.Lt: return ICompareLess (n, m);
|
||||
case Condition.Gt: return ICompareGreater (n, m);
|
||||
case Condition.Le: return ICompareLessOrEqual (n, m);
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public void SetIfThenBlockState(Condition[] state)
|
||||
{
|
||||
_ifThenBlockState = state;
|
||||
_ifThenBlockStateIndex = 0;
|
||||
}
|
||||
|
||||
public void AdvanceIfThenBlockState()
|
||||
{
|
||||
if (IsInIfThenBlock)
|
||||
{
|
||||
_ifThenBlockStateIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
using ARMeilleure.CodeGen;
|
||||
using ARMeilleure.CodeGen.Unwinding;
|
||||
using ARMeilleure.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
static class JitCache
|
||||
{
|
||||
private const int PageSize = 4 * 1024;
|
||||
private const int PageMask = PageSize - 1;
|
||||
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
private const int CacheSize = 2047 * 1024 * 1024;
|
||||
|
||||
private static ReservedRegion _jitRegion;
|
||||
|
||||
private static CacheMemoryAllocator _cacheAllocator;
|
||||
|
||||
private static readonly List<CacheEntry> _cacheEntries = new List<CacheEntry>();
|
||||
|
||||
private static readonly object _lock = new object();
|
||||
private static bool _initialized;
|
||||
|
||||
public static IntPtr Base => _jitRegion.Pointer;
|
||||
|
||||
public static void Initialize(IJitMemoryAllocator allocator)
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
||||
|
||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(PageSize));
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr Map(CompiledFunction func)
|
||||
{
|
||||
byte[] code = func.Code;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = Allocate(code.Length);
|
||||
|
||||
IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
|
||||
|
||||
ReprotectAsWritable(funcOffset, code.Length);
|
||||
|
||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||
|
||||
ReprotectAsExecutable(funcOffset, code.Length);
|
||||
|
||||
Add(funcOffset, code.Length, func.UnwindInfo);
|
||||
|
||||
return funcPtr;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unmap(IntPtr pointer)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
||||
|
||||
bool result = TryFind(funcOffset, out CacheEntry entry);
|
||||
Debug.Assert(result);
|
||||
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
|
||||
Remove(funcOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReprotectAsWritable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~PageMask;
|
||||
int regionEnd = (endOffs + PageMask) & ~PageMask;
|
||||
|
||||
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static void ReprotectAsExecutable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~PageMask;
|
||||
int regionEnd = (endOffs + PageMask) & ~PageMask;
|
||||
|
||||
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static int Allocate(int codeSize)
|
||||
{
|
||||
codeSize = AlignCodeSize(codeSize);
|
||||
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset < 0)
|
||||
{
|
||||
throw new OutOfMemoryException("JIT Cache exhausted.");
|
||||
}
|
||||
|
||||
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
|
||||
return allocOffset;
|
||||
}
|
||||
|
||||
private static int AlignCodeSize(int codeSize)
|
||||
{
|
||||
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
|
||||
}
|
||||
|
||||
private static void Add(int offset, int size, UnwindInfo unwindInfo)
|
||||
{
|
||||
CacheEntry entry = new CacheEntry(offset, size, unwindInfo);
|
||||
|
||||
int index = _cacheEntries.BinarySearch(entry);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
_cacheEntries.Insert(index, entry);
|
||||
}
|
||||
|
||||
private static void Remove(int offset)
|
||||
{
|
||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index - 1;
|
||||
}
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
_cacheEntries.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryFind(int offset, out CacheEntry entry)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index - 1;
|
||||
}
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
entry = _cacheEntries[index];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
entry = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using ARMeilleure.CodeGen;
|
||||
using ARMeilleure.CodeGen.Optimizations;
|
||||
using ARMeilleure.CodeGen.X86;
|
||||
using ARMeilleure.Diagnostics;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
static class Compiler
|
||||
{
|
||||
public static CompiledFunction Compile(
|
||||
ControlFlowGraph cfg,
|
||||
OperandType[] argTypes,
|
||||
OperandType retType,
|
||||
CompilerOptions options)
|
||||
{
|
||||
CompilerContext cctx = new(cfg, argTypes, retType, options);
|
||||
|
||||
if (options.HasFlag(CompilerOptions.Optimize))
|
||||
{
|
||||
Logger.StartPass(PassName.TailMerge);
|
||||
|
||||
TailMerge.RunPass(cctx);
|
||||
|
||||
Logger.EndPass(PassName.TailMerge, cfg);
|
||||
}
|
||||
|
||||
if (options.HasFlag(CompilerOptions.SsaForm))
|
||||
{
|
||||
Logger.StartPass(PassName.Dominance);
|
||||
|
||||
Dominance.FindDominators(cfg);
|
||||
Dominance.FindDominanceFrontiers(cfg);
|
||||
|
||||
Logger.EndPass(PassName.Dominance);
|
||||
|
||||
Logger.StartPass(PassName.SsaConstruction);
|
||||
|
||||
Ssa.Construct(cfg);
|
||||
|
||||
Logger.EndPass(PassName.SsaConstruction, cfg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.StartPass(PassName.RegisterToLocal);
|
||||
|
||||
RegisterToLocal.Rename(cfg);
|
||||
|
||||
Logger.EndPass(PassName.RegisterToLocal, cfg);
|
||||
}
|
||||
|
||||
return CodeGenerator.Generate(cctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
static class DelegateHelper
|
||||
{
|
||||
private const string DelegateTypesAssemblyName = "JitDelegateTypes";
|
||||
|
||||
private static readonly ModuleBuilder _modBuilder;
|
||||
|
||||
private static readonly Dictionary<string, Type> _delegateTypesCache;
|
||||
|
||||
static DelegateHelper()
|
||||
{
|
||||
AssemblyBuilder asmBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(DelegateTypesAssemblyName), AssemblyBuilderAccess.Run);
|
||||
|
||||
_modBuilder = asmBuilder.DefineDynamicModule(DelegateTypesAssemblyName);
|
||||
|
||||
_delegateTypesCache = new Dictionary<string, Type>();
|
||||
}
|
||||
|
||||
public static Delegate GetDelegate(MethodInfo info)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
|
||||
Type[] parameters = info.GetParameters().Select(pI => pI.ParameterType).ToArray();
|
||||
Type returnType = info.ReturnType;
|
||||
|
||||
Type delegateType = GetDelegateType(parameters, returnType);
|
||||
|
||||
return Delegate.CreateDelegate(delegateType, info);
|
||||
}
|
||||
|
||||
private static Type GetDelegateType(Type[] parameters, Type returnType)
|
||||
{
|
||||
string key = GetFunctionSignatureKey(parameters, returnType);
|
||||
|
||||
if (!_delegateTypesCache.TryGetValue(key, out Type delegateType))
|
||||
{
|
||||
delegateType = MakeDelegateType(parameters, returnType, key);
|
||||
|
||||
_delegateTypesCache.TryAdd(key, delegateType);
|
||||
}
|
||||
|
||||
return delegateType;
|
||||
}
|
||||
|
||||
private static string GetFunctionSignatureKey(Type[] parameters, Type returnType)
|
||||
{
|
||||
string sig = GetTypeName(returnType);
|
||||
|
||||
foreach (Type type in parameters)
|
||||
{
|
||||
sig += '_' + GetTypeName(type);
|
||||
}
|
||||
|
||||
return sig;
|
||||
}
|
||||
|
||||
private static string GetTypeName(Type type)
|
||||
{
|
||||
return type.FullName.Replace(".", string.Empty);
|
||||
}
|
||||
|
||||
private const MethodAttributes CtorAttributes =
|
||||
MethodAttributes.RTSpecialName |
|
||||
MethodAttributes.HideBySig |
|
||||
MethodAttributes.Public;
|
||||
|
||||
private const TypeAttributes DelegateTypeAttributes =
|
||||
TypeAttributes.Class |
|
||||
TypeAttributes.Public |
|
||||
TypeAttributes.Sealed |
|
||||
TypeAttributes.AnsiClass |
|
||||
TypeAttributes.AutoClass;
|
||||
|
||||
private const MethodImplAttributes ImplAttributes =
|
||||
MethodImplAttributes.Runtime |
|
||||
MethodImplAttributes.Managed;
|
||||
|
||||
private const MethodAttributes InvokeAttributes =
|
||||
MethodAttributes.Public |
|
||||
MethodAttributes.HideBySig |
|
||||
MethodAttributes.NewSlot |
|
||||
MethodAttributes.Virtual;
|
||||
|
||||
private static readonly Type[] _delegateCtorSignature = { typeof(object), typeof(IntPtr) };
|
||||
|
||||
private static Type MakeDelegateType(Type[] parameters, Type returnType, string name)
|
||||
{
|
||||
TypeBuilder builder = _modBuilder.DefineType(name, DelegateTypeAttributes, typeof(MulticastDelegate));
|
||||
|
||||
builder.DefineConstructor(CtorAttributes, CallingConventions.Standard, _delegateCtorSignature).SetImplementationFlags(ImplAttributes);
|
||||
|
||||
builder.DefineMethod("Invoke", InvokeAttributes, returnType, parameters).SetImplementationFlags(ImplAttributes);
|
||||
|
||||
return builder.CreateTypeInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
using ARMeilleure.Instructions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
static class Delegates
|
||||
{
|
||||
public static bool TryGetDelegateFuncPtrByIndex(int index, out IntPtr funcPtr)
|
||||
{
|
||||
if (index >= 0 && index < _delegates.Count)
|
||||
{
|
||||
funcPtr = _delegates.Values[index].FuncPtr; // O(1).
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
funcPtr = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetDelegateFuncPtrByIndex(int index)
|
||||
{
|
||||
if (index < 0 || index >= _delegates.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"({nameof(index)} = {index})");
|
||||
}
|
||||
|
||||
return _delegates.Values[index].FuncPtr; // O(1).
|
||||
}
|
||||
|
||||
public static IntPtr GetDelegateFuncPtr(MethodInfo info)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
|
||||
string key = GetKey(info);
|
||||
|
||||
if (!_delegates.TryGetValue(key, out DelegateInfo dlgInfo)) // O(log(n)).
|
||||
{
|
||||
throw new KeyNotFoundException($"({nameof(key)} = {key})");
|
||||
}
|
||||
|
||||
return dlgInfo.FuncPtr;
|
||||
}
|
||||
|
||||
public static int GetDelegateIndex(MethodInfo info)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
|
||||
string key = GetKey(info);
|
||||
|
||||
int index = _delegates.IndexOfKey(key); // O(log(n)).
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
throw new KeyNotFoundException($"({nameof(key)} = {key})");
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private static void SetDelegateInfo(MethodInfo info)
|
||||
{
|
||||
string key = GetKey(info);
|
||||
|
||||
Delegate dlg = DelegateHelper.GetDelegate(info);
|
||||
|
||||
_delegates.Add(key, new DelegateInfo(dlg)); // ArgumentException (key).
|
||||
}
|
||||
|
||||
private static string GetKey(MethodInfo info)
|
||||
{
|
||||
return $"{info.DeclaringType.Name}.{info.Name}";
|
||||
}
|
||||
|
||||
private static readonly SortedList<string, DelegateInfo> _delegates;
|
||||
|
||||
static Delegates()
|
||||
{
|
||||
_delegates = new SortedList<string, DelegateInfo>();
|
||||
|
||||
SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Abs), new Type[] { typeof(double) }));
|
||||
SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Ceiling), new Type[] { typeof(double) }));
|
||||
SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Floor), new Type[] { typeof(double) }));
|
||||
SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Round), new Type[] { typeof(double), typeof(MidpointRounding) }));
|
||||
SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Truncate), new Type[] { typeof(double) }));
|
||||
|
||||
SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Abs), new Type[] { typeof(float) }));
|
||||
SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Ceiling), new Type[] { typeof(float) }));
|
||||
SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Floor), new Type[] { typeof(float) }));
|
||||
SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Round), new Type[] { typeof(float), typeof(MidpointRounding) }));
|
||||
SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Truncate), new Type[] { typeof(float) }));
|
||||
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.Break)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.CheckSynchronization)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.EnqueueForRejit)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCtrEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.Undefined)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteVector128)));
|
||||
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingSigns)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingZeros)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32b)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32cb)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32ch)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32cw)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32cx)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32h)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32w)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32x)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Decrypt)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Encrypt)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.FixedRotate)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashChoose)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashLower)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashMajority)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashParity)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashUpper)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.InverseMixColumns)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.MixColumns)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.PolynomialMult64_128)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS64)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU64)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS32)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU32)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart1)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart2)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart1)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart2)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShrImm64)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl1)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl2)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl3)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl4)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx1)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx2)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx3)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx4)));
|
||||
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShrImm64)));
|
||||
|
||||
SetDelegateInfo(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)));
|
||||
SetDelegateInfo(typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert)));
|
||||
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPAdd)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPAddFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompare)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareEQ)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareEQFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGE)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGEFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGT)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGTFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLE)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLEFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLT)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLTFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPDiv)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMax)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMaxFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMaxNum)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMaxNumFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMin)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMinFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMinNum)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMinNumFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMul)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulAdd)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulAddFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulSub)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulSubFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulX)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPNegMulAdd)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPNegMulSub)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipEstimate)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipEstimateFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipStep))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipStepFused)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecpX)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtEstimate)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtEstimateFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtStep))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtStepFused)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPSqrt)));
|
||||
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPSub)));
|
||||
|
||||
SetDelegateInfo(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)));
|
||||
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPAdd)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPAddFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompare)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareEQ)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareEQFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGE)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGEFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGT)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGTFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLE)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLEFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLT)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLTFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPDiv)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMax)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMaxFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMaxNum)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMaxNumFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMin)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMinFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMinNum)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMinNumFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMul)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulAdd)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulAddFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulSub)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulSubFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulX)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPNegMulAdd)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPNegMulSub)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipEstimate)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipEstimateFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipStep))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipStepFused)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecpX)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtEstimate)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtEstimateFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtStep))); // A32 only.
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtStepFused)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPSqrt)));
|
||||
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPSub)));
|
||||
|
||||
SetDelegateInfo(typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,420 +0,0 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
using static ARMeilleure.Translation.PTC.PtcFormatter;
|
||||
|
||||
namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
class PtcProfiler
|
||||
{
|
||||
private const string OuterHeaderMagicString = "Pohd\0\0\0\0";
|
||||
|
||||
private const uint InternalVersion = 1866; //! Not to be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const int SaveInterval = 30; // Seconds.
|
||||
|
||||
private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
|
||||
|
||||
private readonly Ptc _ptc;
|
||||
|
||||
private readonly System.Timers.Timer _timer;
|
||||
|
||||
private readonly ulong _outerHeaderMagic;
|
||||
|
||||
private readonly ManualResetEvent _waitEvent;
|
||||
|
||||
private readonly object _lock;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private Hash128 _lastHash;
|
||||
|
||||
public Dictionary<ulong, FuncProfile> ProfiledFuncs { get; private set; }
|
||||
|
||||
public bool Enabled { get; private set; }
|
||||
|
||||
public ulong StaticCodeStart { get; set; }
|
||||
public ulong StaticCodeSize { get; set; }
|
||||
|
||||
public PtcProfiler(Ptc ptc)
|
||||
{
|
||||
_ptc = ptc;
|
||||
|
||||
_timer = new System.Timers.Timer((double)SaveInterval * 1000d);
|
||||
_timer.Elapsed += PreSave;
|
||||
|
||||
_outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
|
||||
|
||||
_waitEvent = new ManualResetEvent(true);
|
||||
|
||||
_lock = new object();
|
||||
|
||||
_disposed = false;
|
||||
|
||||
ProfiledFuncs = new Dictionary<ulong, FuncProfile>();
|
||||
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
public void AddEntry(ulong address, ExecutionMode mode, bool highCq)
|
||||
{
|
||||
if (IsAddressInStaticCodeRange(address))
|
||||
{
|
||||
Debug.Assert(!highCq);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateEntry(ulong address, ExecutionMode mode, bool highCq)
|
||||
{
|
||||
if (IsAddressInStaticCodeRange(address))
|
||||
{
|
||||
Debug.Assert(highCq);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
Debug.Assert(ProfiledFuncs.ContainsKey(address));
|
||||
|
||||
ProfiledFuncs[address] = new FuncProfile(mode, highCq: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAddressInStaticCodeRange(ulong address)
|
||||
{
|
||||
return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
|
||||
}
|
||||
|
||||
public ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache<TranslatedFunction> funcs)
|
||||
{
|
||||
var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();
|
||||
|
||||
foreach (var profiledFunc in ProfiledFuncs)
|
||||
{
|
||||
if (!funcs.ContainsKey(profiledFunc.Key))
|
||||
{
|
||||
profiledFuncsToTranslate.Enqueue((profiledFunc.Key, profiledFunc.Value));
|
||||
}
|
||||
}
|
||||
|
||||
return profiledFuncsToTranslate;
|
||||
}
|
||||
|
||||
public void ClearEntries()
|
||||
{
|
||||
ProfiledFuncs.Clear();
|
||||
ProfiledFuncs.TrimExcess();
|
||||
}
|
||||
|
||||
public void PreLoad()
|
||||
{
|
||||
_lastHash = default;
|
||||
|
||||
string fileNameActual = string.Concat(_ptc.CachePathActual, ".info");
|
||||
string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info");
|
||||
|
||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||
FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
|
||||
|
||||
if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
|
||||
{
|
||||
if (!Load(fileNameActual, false))
|
||||
{
|
||||
if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
|
||||
{
|
||||
Load(fileNameBackup, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
|
||||
{
|
||||
Load(fileNameBackup, true);
|
||||
}
|
||||
}
|
||||
|
||||
private bool Load(string fileName, bool isBackup)
|
||||
{
|
||||
using (FileStream compressedStream = new(fileName, FileMode.Open))
|
||||
using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true))
|
||||
{
|
||||
OuterHeader outerHeader = DeserializeStructure<OuterHeader>(compressedStream);
|
||||
|
||||
if (!outerHeader.IsHeaderValid())
|
||||
{
|
||||
InvalidateCompressedStream(compressedStream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outerHeader.Magic != _outerHeaderMagic)
|
||||
{
|
||||
InvalidateCompressedStream(compressedStream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outerHeader.InfoFileVersion != InternalVersion)
|
||||
{
|
||||
InvalidateCompressedStream(compressedStream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outerHeader.Endianness != Ptc.GetEndianness())
|
||||
{
|
||||
InvalidateCompressedStream(compressedStream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||
|
||||
try
|
||||
{
|
||||
deflateStream.CopyTo(stream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
InvalidateCompressedStream(compressedStream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug.Assert(stream.Position == stream.Length);
|
||||
|
||||
stream.Seek(0L, SeekOrigin.Begin);
|
||||
|
||||
Hash128 expectedHash = DeserializeStructure<Hash128>(stream);
|
||||
|
||||
Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
|
||||
|
||||
if (actualHash != expectedHash)
|
||||
{
|
||||
InvalidateCompressedStream(compressedStream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ProfiledFuncs = Deserialize(stream);
|
||||
|
||||
Debug.Assert(stream.Position == stream.Length);
|
||||
|
||||
_lastHash = actualHash;
|
||||
}
|
||||
}
|
||||
|
||||
long fileSize = new FileInfo(fileName).Length;
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} (size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Dictionary<ulong, FuncProfile> Deserialize(Stream stream)
|
||||
{
|
||||
return DeserializeDictionary<ulong, FuncProfile>(stream, (stream) => DeserializeStructure<FuncProfile>(stream));
|
||||
}
|
||||
|
||||
private ReadOnlySpan<byte> GetReadOnlySpan(MemoryStream memoryStream)
|
||||
{
|
||||
return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position);
|
||||
}
|
||||
|
||||
private void InvalidateCompressedStream(FileStream compressedStream)
|
||||
{
|
||||
compressedStream.SetLength(0L);
|
||||
}
|
||||
|
||||
private void PreSave(object source, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
_waitEvent.Reset();
|
||||
|
||||
string fileNameActual = string.Concat(_ptc.CachePathActual, ".info");
|
||||
string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info");
|
||||
|
||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||
|
||||
if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
|
||||
{
|
||||
File.Copy(fileNameActual, fileNameBackup, true);
|
||||
}
|
||||
|
||||
Save(fileNameActual);
|
||||
|
||||
_waitEvent.Set();
|
||||
}
|
||||
|
||||
private void Save(string fileName)
|
||||
{
|
||||
int profiledFuncsCount;
|
||||
|
||||
OuterHeader outerHeader = new OuterHeader();
|
||||
|
||||
outerHeader.Magic = _outerHeaderMagic;
|
||||
|
||||
outerHeader.InfoFileVersion = InternalVersion;
|
||||
outerHeader.Endianness = Ptc.GetEndianness();
|
||||
|
||||
outerHeader.SetHeaderHash();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||
|
||||
stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
Serialize(stream, ProfiledFuncs);
|
||||
|
||||
profiledFuncsCount = ProfiledFuncs.Count;
|
||||
}
|
||||
|
||||
Debug.Assert(stream.Position == stream.Length);
|
||||
|
||||
stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
|
||||
Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
|
||||
|
||||
stream.Seek(0L, SeekOrigin.Begin);
|
||||
SerializeStructure(stream, hash);
|
||||
|
||||
if (hash == _lastHash)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (FileStream compressedStream = new(fileName, FileMode.OpenOrCreate))
|
||||
using (DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true))
|
||||
{
|
||||
try
|
||||
{
|
||||
SerializeStructure(compressedStream, outerHeader);
|
||||
|
||||
stream.WriteTo(deflateStream);
|
||||
|
||||
_lastHash = hash;
|
||||
}
|
||||
catch
|
||||
{
|
||||
compressedStream.Position = 0L;
|
||||
|
||||
_lastHash = default;
|
||||
}
|
||||
|
||||
if (compressedStream.Position < compressedStream.Length)
|
||||
{
|
||||
compressedStream.SetLength(compressedStream.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long fileSize = new FileInfo(fileName).Length;
|
||||
|
||||
if (fileSize != 0L)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
|
||||
}
|
||||
}
|
||||
|
||||
private void Serialize(Stream stream, Dictionary<ulong, FuncProfile> profiledFuncs)
|
||||
{
|
||||
SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure));
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 29*/)]
|
||||
private struct OuterHeader
|
||||
{
|
||||
public ulong Magic;
|
||||
|
||||
public uint InfoFileVersion;
|
||||
|
||||
public bool Endianness;
|
||||
|
||||
public Hash128 HeaderHash;
|
||||
|
||||
public void SetHeaderHash()
|
||||
{
|
||||
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||
|
||||
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>()));
|
||||
}
|
||||
|
||||
public bool IsHeaderValid()
|
||||
{
|
||||
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||
|
||||
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())) == HeaderHash;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)]
|
||||
public struct FuncProfile
|
||||
{
|
||||
public ExecutionMode Mode;
|
||||
public bool HighCq;
|
||||
|
||||
public FuncProfile(ExecutionMode mode, bool highCq)
|
||||
{
|
||||
Mode = mode;
|
||||
HighCq = highCq;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_ptc.State == PtcState.Enabled ||
|
||||
_ptc.State == PtcState.Continuing)
|
||||
{
|
||||
Enabled = true;
|
||||
|
||||
_timer.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Enabled = false;
|
||||
|
||||
if (!_disposed)
|
||||
{
|
||||
_timer.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
_waitEvent.WaitOne();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
_timer.Elapsed -= PreSave;
|
||||
_timer.Dispose();
|
||||
|
||||
Wait();
|
||||
_waitEvent.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using ARMeilleure.Common;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
class TranslatedFunction
|
||||
{
|
||||
private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected.
|
||||
|
||||
public Counter<uint> CallCounter { get; }
|
||||
public ulong GuestSize { get; }
|
||||
public bool HighCq { get; }
|
||||
public IntPtr FuncPtr { get; }
|
||||
|
||||
public TranslatedFunction(GuestFunction func, Counter<uint> callCounter, ulong guestSize, bool highCq)
|
||||
{
|
||||
_func = func;
|
||||
CallCounter = callCounter;
|
||||
GuestSize = guestSize;
|
||||
HighCq = highCq;
|
||||
FuncPtr = Marshal.GetFunctionPointerForDelegate(func);
|
||||
}
|
||||
|
||||
public ulong Execute(State.ExecutionContext context)
|
||||
{
|
||||
return _func(context.NativeContextPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,565 +0,0 @@
|
||||
using ARMeilleure.CodeGen;
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.Diagnostics;
|
||||
using ARMeilleure.Instructions;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Signal;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation.Cache;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
public class Translator
|
||||
{
|
||||
private static readonly AddressTable<ulong>.Level[] Levels64Bit =
|
||||
new AddressTable<ulong>.Level[]
|
||||
{
|
||||
new(31, 17),
|
||||
new(23, 8),
|
||||
new(15, 8),
|
||||
new( 7, 8),
|
||||
new( 2, 5)
|
||||
};
|
||||
|
||||
private static readonly AddressTable<ulong>.Level[] Levels32Bit =
|
||||
new AddressTable<ulong>.Level[]
|
||||
{
|
||||
new(31, 17),
|
||||
new(23, 8),
|
||||
new(15, 8),
|
||||
new( 7, 8),
|
||||
new( 1, 6)
|
||||
};
|
||||
|
||||
private readonly IJitMemoryAllocator _allocator;
|
||||
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
||||
|
||||
private readonly Ptc _ptc;
|
||||
|
||||
internal TranslatorCache<TranslatedFunction> Functions { get; }
|
||||
internal AddressTable<ulong> FunctionTable { get; }
|
||||
internal EntryTable<uint> CountTable { get; }
|
||||
internal TranslatorStubs Stubs { get; }
|
||||
internal TranslatorQueue Queue { get; }
|
||||
internal IMemoryManager Memory { get; }
|
||||
|
||||
private volatile int _threadCount;
|
||||
|
||||
// FIXME: Remove this once the init logic of the emulator will be redone.
|
||||
public static readonly ManualResetEvent IsReadyForTranslation = new(false);
|
||||
|
||||
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
|
||||
{
|
||||
_allocator = allocator;
|
||||
Memory = memory;
|
||||
|
||||
_oldFuncs = new ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>>();
|
||||
|
||||
_ptc = new Ptc();
|
||||
|
||||
Queue = new TranslatorQueue();
|
||||
|
||||
JitCache.Initialize(allocator);
|
||||
|
||||
CountTable = new EntryTable<uint>();
|
||||
Functions = new TranslatorCache<TranslatedFunction>();
|
||||
FunctionTable = new AddressTable<ulong>(for64Bits ? Levels64Bit : Levels32Bit);
|
||||
Stubs = new TranslatorStubs(this);
|
||||
|
||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||
|
||||
if (memory.Type.IsHostMapped())
|
||||
{
|
||||
NativeSignalHandler.InitializeSignalHandler();
|
||||
}
|
||||
}
|
||||
|
||||
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
||||
{
|
||||
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type);
|
||||
return _ptc;
|
||||
}
|
||||
|
||||
public void PrepareCodeRange(ulong address, ulong size)
|
||||
{
|
||||
if (_ptc.Profiler.StaticCodeSize == 0)
|
||||
{
|
||||
_ptc.Profiler.StaticCodeStart = address;
|
||||
_ptc.Profiler.StaticCodeSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(State.ExecutionContext context, ulong address)
|
||||
{
|
||||
if (Interlocked.Increment(ref _threadCount) == 1)
|
||||
{
|
||||
IsReadyForTranslation.WaitOne();
|
||||
|
||||
if (_ptc.State == PtcState.Enabled)
|
||||
{
|
||||
Debug.Assert(Functions.Count == 0);
|
||||
_ptc.LoadTranslations(this);
|
||||
_ptc.MakeAndSaveTranslations(this);
|
||||
}
|
||||
|
||||
_ptc.Profiler.Start();
|
||||
|
||||
_ptc.Disable();
|
||||
|
||||
// Simple heuristic, should be user configurable in future. (1 for 4 core/ht or less, 2 for 6 core + ht
|
||||
// etc). All threads are normal priority except from the last, which just fills as much of the last core
|
||||
// as the os lets it with a low priority. If we only have one rejit thread, it should be normal priority
|
||||
// as highCq code is performance critical.
|
||||
//
|
||||
// TODO: Use physical cores rather than logical. This only really makes sense for processors with
|
||||
// hyperthreading. Requires OS specific code.
|
||||
int unboundedThreadCount = Math.Max(1, (Environment.ProcessorCount - 6) / 3);
|
||||
int threadCount = Math.Min(4, unboundedThreadCount);
|
||||
|
||||
for (int i = 0; i < threadCount; i++)
|
||||
{
|
||||
bool last = i != 0 && i == unboundedThreadCount - 1;
|
||||
|
||||
Thread backgroundTranslatorThread = new Thread(BackgroundTranslate)
|
||||
{
|
||||
Name = "CPU.BackgroundTranslatorThread." + i,
|
||||
Priority = last ? ThreadPriority.Lowest : ThreadPriority.Normal
|
||||
};
|
||||
|
||||
backgroundTranslatorThread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
Statistics.InitializeTimer();
|
||||
|
||||
NativeInterface.RegisterThread(context, Memory, this);
|
||||
|
||||
if (Optimizations.UseUnmanagedDispatchLoop)
|
||||
{
|
||||
Stubs.DispatchLoop(context.NativeContextPtr, address);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
address = ExecuteSingle(context, address);
|
||||
}
|
||||
while (context.Running && address != 0);
|
||||
}
|
||||
|
||||
NativeInterface.UnregisterThread();
|
||||
|
||||
if (Interlocked.Decrement(ref _threadCount) == 0)
|
||||
{
|
||||
ClearJitCache();
|
||||
|
||||
Queue.Dispose();
|
||||
Stubs.Dispose();
|
||||
FunctionTable.Dispose();
|
||||
CountTable.Dispose();
|
||||
|
||||
_ptc.Close();
|
||||
_ptc.Profiler.Stop();
|
||||
|
||||
_ptc.Dispose();
|
||||
_ptc.Profiler.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private ulong ExecuteSingle(State.ExecutionContext context, ulong address)
|
||||
{
|
||||
TranslatedFunction func = GetOrTranslate(address, context.ExecutionMode);
|
||||
|
||||
Statistics.StartTimer();
|
||||
|
||||
ulong nextAddr = func.Execute(context);
|
||||
|
||||
Statistics.StopTimer(address);
|
||||
|
||||
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.GuestSize, func);
|
||||
|
||||
if (oldFunc != func)
|
||||
{
|
||||
JitCache.Unmap(func.FuncPtr);
|
||||
func = oldFunc;
|
||||
}
|
||||
|
||||
if (_ptc.Profiler.Enabled)
|
||||
{
|
||||
_ptc.Profiler.AddEntry(address, mode, highCq: false);
|
||||
}
|
||||
|
||||
RegisterFunction(address, func);
|
||||
}
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
internal void RegisterFunction(ulong guestAddress, TranslatedFunction func)
|
||||
{
|
||||
if (FunctionTable.IsValid(guestAddress) && (Optimizations.AllowLcqInFunctionTable || func.HighCq))
|
||||
{
|
||||
Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPtr);
|
||||
}
|
||||
}
|
||||
|
||||
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false)
|
||||
{
|
||||
var context = new ArmEmitterContext(
|
||||
Memory,
|
||||
CountTable,
|
||||
FunctionTable,
|
||||
Stubs,
|
||||
address,
|
||||
highCq,
|
||||
_ptc.State != PtcState.Disabled,
|
||||
mode: Aarch32Mode.User);
|
||||
|
||||
Logger.StartPass(PassName.Decoding);
|
||||
|
||||
Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleStep ? DecoderMode.SingleInstruction : DecoderMode.MultipleBlocks);
|
||||
|
||||
Logger.EndPass(PassName.Decoding);
|
||||
|
||||
Logger.StartPass(PassName.Translation);
|
||||
|
||||
EmitSynchronization(context);
|
||||
|
||||
if (blocks[0].Address != address)
|
||||
{
|
||||
context.Branch(context.GetLabel(address));
|
||||
}
|
||||
|
||||
ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter<uint> counter);
|
||||
|
||||
ulong funcSize = funcRange.End - funcRange.Start;
|
||||
|
||||
Logger.EndPass(PassName.Translation, cfg);
|
||||
|
||||
Logger.StartPass(PassName.RegisterUsage);
|
||||
|
||||
RegisterUsage.RunPass(cfg, mode);
|
||||
|
||||
Logger.EndPass(PassName.RegisterUsage);
|
||||
|
||||
var retType = OperandType.I64;
|
||||
var argTypes = new OperandType[] { OperandType.I64 };
|
||||
|
||||
var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
|
||||
|
||||
if (context.HasPtc && !singleStep)
|
||||
{
|
||||
options |= CompilerOptions.Relocatable;
|
||||
}
|
||||
|
||||
CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options);
|
||||
|
||||
if (context.HasPtc && !singleStep)
|
||||
{
|
||||
Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize);
|
||||
|
||||
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
|
||||
}
|
||||
|
||||
GuestFunction func = compiledFunc.Map<GuestFunction>();
|
||||
|
||||
Allocators.ResetAll();
|
||||
|
||||
return new TranslatedFunction(func, counter, funcSize, highCq);
|
||||
}
|
||||
|
||||
private void BackgroundTranslate()
|
||||
{
|
||||
while (_threadCount != 0 && Queue.TryDequeue(out RejitRequest request))
|
||||
{
|
||||
TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true);
|
||||
|
||||
Functions.AddOrUpdate(request.Address, func.GuestSize, func, (key, oldFunc) =>
|
||||
{
|
||||
EnqueueForDeletion(key, oldFunc);
|
||||
return func;
|
||||
});
|
||||
|
||||
if (_ptc.Profiler.Enabled)
|
||||
{
|
||||
_ptc.Profiler.UpdateEntry(request.Address, request.Mode, highCq: true);
|
||||
}
|
||||
|
||||
RegisterFunction(request.Address, func);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct Range
|
||||
{
|
||||
public ulong Start { get; }
|
||||
public ulong End { get; }
|
||||
|
||||
public Range(ulong start, ulong end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
}
|
||||
|
||||
private static ControlFlowGraph EmitAndGetCFG(
|
||||
ArmEmitterContext context,
|
||||
Block[] blocks,
|
||||
out Range range,
|
||||
out Counter<uint> counter)
|
||||
{
|
||||
counter = null;
|
||||
|
||||
ulong rangeStart = ulong.MaxValue;
|
||||
ulong rangeEnd = 0;
|
||||
|
||||
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
|
||||
{
|
||||
Block block = blocks[blkIndex];
|
||||
|
||||
if (!block.Exit)
|
||||
{
|
||||
if (rangeStart > block.Address)
|
||||
{
|
||||
rangeStart = block.Address;
|
||||
}
|
||||
|
||||
if (rangeEnd < block.EndAddress)
|
||||
{
|
||||
rangeEnd = block.EndAddress;
|
||||
}
|
||||
}
|
||||
|
||||
if (block.Address == context.EntryAddress && !context.HighCq)
|
||||
{
|
||||
EmitRejitCheck(context, out counter);
|
||||
}
|
||||
|
||||
context.CurrBlock = block;
|
||||
|
||||
context.MarkLabel(context.GetLabel(block.Address));
|
||||
|
||||
if (block.Exit)
|
||||
{
|
||||
// Left option here as it may be useful if we need to return to managed rather than tail call in
|
||||
// future. (eg. for debug)
|
||||
bool useReturns = false;
|
||||
|
||||
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int opcIndex = 0; opcIndex < block.OpCodes.Count; opcIndex++)
|
||||
{
|
||||
OpCode opCode = block.OpCodes[opcIndex];
|
||||
|
||||
context.CurrOp = opCode;
|
||||
|
||||
bool isLastOp = opcIndex == block.OpCodes.Count - 1;
|
||||
|
||||
if (isLastOp && block.Branch != null && !block.Branch.Exit && block.Branch.Address <= block.Address)
|
||||
{
|
||||
EmitSynchronization(context);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, op.Cond.Invert());
|
||||
}
|
||||
|
||||
if (opCode.Instruction.Emitter != null)
|
||||
{
|
||||
opCode.Instruction.Emitter(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid instruction \"{opCode.Instruction.Name}\".");
|
||||
}
|
||||
|
||||
if (lblPredicateSkip != default)
|
||||
{
|
||||
context.MarkLabel(lblPredicateSkip);
|
||||
}
|
||||
|
||||
if (context.IsInIfThenBlock && opCode.Instruction.Name != InstName.It)
|
||||
{
|
||||
context.AdvanceIfThenBlockState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
range = new Range(rangeStart, rangeEnd);
|
||||
|
||||
return context.GetControlFlowGraph();
|
||||
}
|
||||
|
||||
internal static void EmitRejitCheck(ArmEmitterContext context, out Counter<uint> counter)
|
||||
{
|
||||
const int MinsCallForRejit = 100;
|
||||
|
||||
counter = new Counter<uint>(context.CountTable);
|
||||
|
||||
Operand lblEnd = Label();
|
||||
|
||||
Operand address = !context.HasPtc ?
|
||||
Const(ref counter.Value) :
|
||||
Const(ref counter.Value, Ptc.CountTableSymbol);
|
||||
|
||||
Operand curCount = context.Load(OperandType.I32, address);
|
||||
Operand count = context.Add(curCount, Const(1));
|
||||
context.Store(address, count);
|
||||
context.BranchIf(lblEnd, curCount, Const(MinsCallForRejit), Comparison.NotEqual, BasicBlockFrequency.Cold);
|
||||
|
||||
context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.EnqueueForRejit)), Const(context.EntryAddress));
|
||||
|
||||
context.MarkLabel(lblEnd);
|
||||
}
|
||||
|
||||
internal static void EmitSynchronization(EmitterContext context)
|
||||
{
|
||||
long countOffs = NativeContext.GetCounterOffset();
|
||||
|
||||
Operand lblNonZero = Label();
|
||||
Operand lblExit = Label();
|
||||
|
||||
Operand countAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(countOffs));
|
||||
Operand count = context.Load(OperandType.I32, countAddr);
|
||||
context.BranchIfTrue(lblNonZero, count, BasicBlockFrequency.Cold);
|
||||
|
||||
Operand running = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.CheckSynchronization)));
|
||||
context.BranchIfTrue(lblExit, running, BasicBlockFrequency.Cold);
|
||||
|
||||
context.Return(Const(0L));
|
||||
|
||||
context.MarkLabel(lblNonZero);
|
||||
count = context.Subtract(count, Const(1));
|
||||
context.Store(countAddr, count);
|
||||
|
||||
context.MarkLabel(lblExit);
|
||||
}
|
||||
|
||||
public void InvalidateJitCacheRegion(ulong address, ulong size)
|
||||
{
|
||||
ulong[] overlapAddresses = Array.Empty<ulong>();
|
||||
|
||||
int overlapsCount = Functions.GetOverlaps(address, size, ref overlapAddresses);
|
||||
|
||||
if (overlapsCount != 0)
|
||||
{
|
||||
// If rejit is running, stop it as it may be trying to rejit a function on the invalidated region.
|
||||
ClearRejitQueue(allowRequeue: true);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Queue.Enqueue(guestAddress, mode);
|
||||
}
|
||||
|
||||
private void EnqueueForDeletion(ulong guestAddress, TranslatedFunction func)
|
||||
{
|
||||
_oldFuncs.Enqueue(new(guestAddress, func));
|
||||
}
|
||||
|
||||
private void ClearJitCache()
|
||||
{
|
||||
// Ensure no attempt will be made to compile new functions due to rejit.
|
||||
ClearRejitQueue(allowRequeue: false);
|
||||
|
||||
List<TranslatedFunction> functions = Functions.AsList();
|
||||
|
||||
foreach (var func in functions)
|
||||
{
|
||||
JitCache.Unmap(func.FuncPtr);
|
||||
|
||||
func.CallCounter?.Dispose();
|
||||
}
|
||||
|
||||
Functions.Clear();
|
||||
|
||||
while (_oldFuncs.TryDequeue(out var kv))
|
||||
{
|
||||
JitCache.Unmap(kv.Value.FuncPtr);
|
||||
|
||||
kv.Value.CallCounter?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearRejitQueue(bool allowRequeue)
|
||||
{
|
||||
if (!allowRequeue)
|
||||
{
|
||||
Queue.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (Queue.Sync)
|
||||
{
|
||||
while (Queue.Count > 0 && Queue.TryDequeue(out RejitRequest request))
|
||||
{
|
||||
if (Functions.TryGetValue(request.Address, out var func) && func.CallCounter != null)
|
||||
{
|
||||
Volatile.Write(ref func.CallCounter.Value, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
using ARMeilleure.Instructions;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation.Cache;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Translation
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a stub manager.
|
||||
/// </summary>
|
||||
class TranslatorStubs : IDisposable
|
||||
{
|
||||
private static readonly Lazy<IntPtr> _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private readonly Translator _translator;
|
||||
private readonly Lazy<IntPtr> _dispatchStub;
|
||||
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dispatch stub.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
|
||||
public IntPtr DispatchStub
|
||||
{
|
||||
get
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return _dispatchStub.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the slow dispatch stub.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
|
||||
public IntPtr SlowDispatchStub
|
||||
{
|
||||
get
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return _slowDispatchStub.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dispatch loop function.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
|
||||
public DispatcherFunction DispatchLoop
|
||||
{
|
||||
get
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
return _dispatchLoop.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
|
||||
/// <see cref="Translator"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="translator"><see cref="Translator"/> instance to use</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
||||
public TranslatorStubs(Translator translator)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(translator);
|
||||
|
||||
_translator = translator;
|
||||
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
|
||||
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources used by the <see cref="TranslatorStubs"/> instance.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases all unmanaged and optionally managed resources used by the <see cref="TranslatorStubs"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (_dispatchStub.IsValueCreated)
|
||||
{
|
||||
JitCache.Unmap(_dispatchStub.Value);
|
||||
}
|
||||
|
||||
if (_dispatchLoop.IsValueCreated)
|
||||
{
|
||||
JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees resources used by the <see cref="TranslatorStubs"/> instance.
|
||||
/// </summary>
|
||||
~TranslatorStubs()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a <see cref="DispatchStub"/>.
|
||||
/// </summary>
|
||||
/// <returns>Generated <see cref="DispatchStub"/></returns>
|
||||
private IntPtr GenerateDispatchStub()
|
||||
{
|
||||
var context = new EmitterContext();
|
||||
|
||||
Operand lblFallback = Label();
|
||||
Operand lblEnd = Label();
|
||||
|
||||
// Load the target guest address from the native context.
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
Operand guestAddress = context.Load(OperandType.I64,
|
||||
context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())));
|
||||
|
||||
// Check if guest address is within range of the AddressTable.
|
||||
Operand masked = context.BitwiseAnd(guestAddress, Const(~_translator.FunctionTable.Mask));
|
||||
context.BranchIfTrue(lblFallback, masked);
|
||||
|
||||
Operand index = default;
|
||||
Operand page = Const((long)_translator.FunctionTable.Base);
|
||||
|
||||
for (int i = 0; i < _translator.FunctionTable.Levels.Length; i++)
|
||||
{
|
||||
ref var level = ref _translator.FunctionTable.Levels[i];
|
||||
|
||||
// level.Mask is not used directly because it is more often bigger than 32-bits, so it will not
|
||||
// be encoded as an immediate on x86's bitwise and operation.
|
||||
Operand mask = Const(level.Mask >> level.Index);
|
||||
|
||||
index = context.BitwiseAnd(context.ShiftRightUI(guestAddress, Const(level.Index)), mask);
|
||||
|
||||
if (i < _translator.FunctionTable.Levels.Length - 1)
|
||||
{
|
||||
page = context.Load(OperandType.I64, context.Add(page, context.ShiftLeft(index, Const(3))));
|
||||
context.BranchIfFalse(lblFallback, page);
|
||||
}
|
||||
}
|
||||
|
||||
Operand hostAddress;
|
||||
Operand hostAddressAddr = context.Add(page, context.ShiftLeft(index, Const(3)));
|
||||
hostAddress = context.Load(OperandType.I64, hostAddressAddr);
|
||||
context.Tailcall(hostAddress, nativeContext);
|
||||
|
||||
context.MarkLabel(lblFallback);
|
||||
hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress);
|
||||
context.Tailcall(hostAddress, nativeContext);
|
||||
|
||||
var cfg = context.GetControlFlowGraph();
|
||||
var retType = OperandType.I64;
|
||||
var argTypes = new[] { OperandType.I64 };
|
||||
|
||||
var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq).Map<GuestFunction>();
|
||||
|
||||
return Marshal.GetFunctionPointerForDelegate(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a <see cref="SlowDispatchStub"/>.
|
||||
/// </summary>
|
||||
/// <returns>Generated <see cref="SlowDispatchStub"/></returns>
|
||||
private static IntPtr GenerateSlowDispatchStub()
|
||||
{
|
||||
var context = new EmitterContext();
|
||||
|
||||
// Load the target guest address from the native context.
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
Operand guestAddress = context.Load(OperandType.I64,
|
||||
context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())));
|
||||
|
||||
MethodInfo getFuncAddress = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress));
|
||||
Operand hostAddress = context.Call(getFuncAddress, guestAddress);
|
||||
context.Tailcall(hostAddress, nativeContext);
|
||||
|
||||
var cfg = context.GetControlFlowGraph();
|
||||
var retType = OperandType.I64;
|
||||
var argTypes = new[] { OperandType.I64 };
|
||||
|
||||
var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq).Map<GuestFunction>();
|
||||
|
||||
return Marshal.GetFunctionPointerForDelegate(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a <see cref="DispatchLoop"/> function.
|
||||
/// </summary>
|
||||
/// <returns><see cref="DispatchLoop"/> function</returns>
|
||||
private DispatcherFunction GenerateDispatchLoop()
|
||||
{
|
||||
var context = new EmitterContext();
|
||||
|
||||
Operand beginLbl = Label();
|
||||
Operand endLbl = Label();
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
Operand guestAddress = context.Copy(
|
||||
context.AllocateLocal(OperandType.I64),
|
||||
context.LoadArgument(OperandType.I64, 1));
|
||||
|
||||
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
|
||||
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
|
||||
|
||||
context.MarkLabel(beginLbl);
|
||||
context.Store(dispatchAddress, guestAddress);
|
||||
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
|
||||
context.BranchIfFalse(endLbl, guestAddress);
|
||||
context.BranchIfFalse(endLbl, context.Load(OperandType.I32, runningAddress));
|
||||
context.Branch(beginLbl);
|
||||
|
||||
context.MarkLabel(endLbl);
|
||||
context.Return();
|
||||
|
||||
var cfg = context.GetControlFlowGraph();
|
||||
var retType = OperandType.None;
|
||||
var argTypes = new[] { OperandType.I64, OperandType.I64 };
|
||||
|
||||
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq).Map<DispatcherFunction>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,40 +3,40 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageVersion Include="DynamicData" Version="7.12.11" />
|
||||
<PackageVersion Include="DynamicData" Version="7.13.8" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||
<PackageVersion Include="LibHac" Version="0.17.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" />
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.3" />
|
||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.24.2-build21" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.1" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
@@ -44,11 +44,10 @@
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
|
||||
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.30.1" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.1" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
## Compatibility
|
||||
|
||||
As of November 2022, Ryujinx has been tested on approximately 3,800 titles; over 3,600 boot past menus and into gameplay, with roughly 3,200 of those being considered playable.
|
||||
As of April 2023, Ryujinx has been tested on approximately 4,050 titles; over 4,000 boot past menus and into gameplay, with roughly 3,400 of those being considered playable.
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
|
||||
## Usage
|
||||
@@ -96,7 +96,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
|
||||
|
||||
- **GPU**
|
||||
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
|
||||
- **Input**
|
||||
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
using static SDL2.SDL;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SDL2
|
||||
{
|
||||
public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private readonly ManualResetEvent _pauseEvent;
|
||||
private readonly ConcurrentDictionary<SDL2HardwareDeviceSession, byte> _sessions;
|
||||
|
||||
public SDL2HardwareDeviceDriver()
|
||||
{
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_pauseEvent = new ManualResetEvent(true);
|
||||
_sessions = new ConcurrentDictionary<SDL2HardwareDeviceSession, byte>();
|
||||
|
||||
SDL2Driver.Instance.Initialize();
|
||||
}
|
||||
|
||||
public static bool IsSupported => IsSupportedInternal();
|
||||
|
||||
private static bool IsSupportedInternal()
|
||||
{
|
||||
uint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null);
|
||||
|
||||
if (device != 0)
|
||||
{
|
||||
SDL_CloseAudioDevice(device);
|
||||
}
|
||||
|
||||
return device != 0;
|
||||
}
|
||||
|
||||
public ManualResetEvent GetUpdateRequiredEvent()
|
||||
{
|
||||
return _updateRequiredEvent;
|
||||
}
|
||||
|
||||
public ManualResetEvent GetPauseEvent()
|
||||
{
|
||||
return _pauseEvent;
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
channelCount = 2;
|
||||
}
|
||||
|
||||
if (sampleRate == 0)
|
||||
{
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
if (direction != Direction.Output)
|
||||
{
|
||||
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
|
||||
}
|
||||
|
||||
SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||
|
||||
_sessions.TryAdd(session, 0);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
internal bool Unregister(SDL2HardwareDeviceSession session)
|
||||
{
|
||||
return _sessions.TryRemove(session, out _);
|
||||
}
|
||||
|
||||
private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount)
|
||||
{
|
||||
return new SDL_AudioSpec
|
||||
{
|
||||
channels = (byte)requestedChannelCount,
|
||||
format = GetSDL2Format(requestedSampleFormat),
|
||||
freq = (int)requestedSampleRate,
|
||||
samples = (ushort)sampleCount
|
||||
};
|
||||
}
|
||||
|
||||
internal static ushort GetSDL2Format(SampleFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
SampleFormat.PcmInt8 => AUDIO_S8,
|
||||
SampleFormat.PcmInt16 => AUDIO_S16,
|
||||
SampleFormat.PcmInt32 => AUDIO_S32,
|
||||
SampleFormat.PcmFloat => AUDIO_F32,
|
||||
_ => throw new ArgumentException($"Unsupported sample format {format}"),
|
||||
};
|
||||
}
|
||||
|
||||
internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback)
|
||||
{
|
||||
SDL_AudioSpec desired = GetSDL2Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount);
|
||||
|
||||
desired.callback = callback;
|
||||
|
||||
uint device = SDL_OpenAudioDevice(IntPtr.Zero, 0, ref desired, out SDL_AudioSpec got, 0);
|
||||
|
||||
if (device == 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels;
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
|
||||
SDL_CloseAudioDevice(device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (SDL2HardwareDeviceSession session in _sessions.Keys)
|
||||
{
|
||||
session.Dispose();
|
||||
}
|
||||
|
||||
SDL2Driver.Instance.Dispose();
|
||||
|
||||
_pauseEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsSampleRate(uint sampleRate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SupportsSampleFormat(SampleFormat sampleFormat)
|
||||
{
|
||||
return sampleFormat != SampleFormat.PcmInt24;
|
||||
}
|
||||
|
||||
public bool SupportsChannelCount(uint channelCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SupportsDirection(Direction direction)
|
||||
{
|
||||
// TODO: add direction input when supported.
|
||||
return direction == Direction.Output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
using Ryujinx.Audio.Backends.Common;
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
using static SDL2.SDL;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SDL2
|
||||
{
|
||||
class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
{
|
||||
private SDL2HardwareDeviceDriver _driver;
|
||||
private ConcurrentQueue<SDL2AudioBuffer> _queuedBuffers;
|
||||
private DynamicRingBuffer _ringBuffer;
|
||||
private ulong _playedSampleCount;
|
||||
private ManualResetEvent _updateRequiredEvent;
|
||||
private uint _outputStream;
|
||||
private SDL_AudioCallback _callbackDelegate;
|
||||
private int _bytesPerFrame;
|
||||
private uint _sampleCount;
|
||||
private bool _started;
|
||||
private float _volume;
|
||||
private ushort _nativeSampleFormat;
|
||||
|
||||
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
{
|
||||
_driver = driver;
|
||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
_queuedBuffers = new ConcurrentQueue<SDL2AudioBuffer>();
|
||||
_ringBuffer = new DynamicRingBuffer();
|
||||
_callbackDelegate = Update;
|
||||
_bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount;
|
||||
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
||||
_sampleCount = uint.MaxValue;
|
||||
_started = false;
|
||||
_volume = requestedVolume;
|
||||
}
|
||||
|
||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||
{
|
||||
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
||||
bool needAudioSetup = _outputStream == 0 ||
|
||||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
||||
|
||||
if (needAudioSetup)
|
||||
{
|
||||
_sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount);
|
||||
|
||||
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||
|
||||
if (newOutputStream == 0)
|
||||
{
|
||||
// No stream in place, this is unexpected.
|
||||
throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL_CloseAudioDevice(_outputStream);
|
||||
}
|
||||
|
||||
_outputStream = newOutputStream;
|
||||
|
||||
SDL_PauseAudioDevice(_outputStream, _started ? 0 : 1);
|
||||
|
||||
Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength)
|
||||
{
|
||||
Span<byte> streamSpan = new Span<byte>((void*)stream, streamLength);
|
||||
|
||||
int maxFrameCount = (int)GetSampleCount(streamLength);
|
||||
int bufferedFrames = _ringBuffer.Length / _bytesPerFrame;
|
||||
|
||||
int frameCount = Math.Min(bufferedFrames, maxFrameCount);
|
||||
|
||||
if (frameCount == 0)
|
||||
{
|
||||
// SDL2 left the responsibility to the user to clear the buffer.
|
||||
streamSpan.Fill(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] samples = new byte[frameCount * _bytesPerFrame];
|
||||
|
||||
_ringBuffer.Read(samples, 0, samples.Length);
|
||||
|
||||
fixed (byte* p = samples)
|
||||
{
|
||||
IntPtr pStreamSrc = (IntPtr)p;
|
||||
|
||||
// Zero the dest buffer
|
||||
streamSpan.Fill(0);
|
||||
|
||||
// Apply volume to written data
|
||||
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
|
||||
}
|
||||
|
||||
ulong sampleCount = GetSampleCount(samples.Length);
|
||||
|
||||
ulong availaibleSampleCount = sampleCount;
|
||||
|
||||
bool needUpdate = false;
|
||||
|
||||
while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer))
|
||||
{
|
||||
ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed);
|
||||
ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount);
|
||||
|
||||
ulong currentSamplePlayed = Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount);
|
||||
availaibleSampleCount -= playedAudioBufferSampleCount;
|
||||
|
||||
if (currentSamplePlayed == driverBuffer.SampleCount)
|
||||
{
|
||||
_queuedBuffers.TryDequeue(out _);
|
||||
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount);
|
||||
}
|
||||
|
||||
// Notify the output if needed.
|
||||
if (needUpdate)
|
||||
{
|
||||
_updateRequiredEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public override ulong GetPlayedSampleCount()
|
||||
{
|
||||
return Interlocked.Read(ref _playedSampleCount);
|
||||
}
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
return _volume;
|
||||
}
|
||||
|
||||
public override void PrepareToClose() { }
|
||||
|
||||
public override void QueueBuffer(AudioBuffer buffer)
|
||||
{
|
||||
EnsureAudioStreamSetup(buffer);
|
||||
|
||||
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
||||
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
|
||||
_queuedBuffers.Enqueue(driverBuffer);
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
_volume = volume;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (!_started)
|
||||
{
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL_PauseAudioDevice(_outputStream, 0);
|
||||
}
|
||||
|
||||
_started = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL_PauseAudioDevice(_outputStream, 1);
|
||||
}
|
||||
|
||||
_started = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UnregisterBuffer(AudioBuffer buffer) { }
|
||||
|
||||
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
|
||||
{
|
||||
if (!_queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return driverBuffer.DriverIdentifier != buffer.DataPointer;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && _driver.Unregister(this))
|
||||
{
|
||||
PrepareToClose();
|
||||
Stop();
|
||||
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL_CloseAudioDevice(_outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
using Ryujinx.Audio.Backends.SoundIo.Native;
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo
|
||||
{
|
||||
public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private readonly SoundIoContext _audioContext;
|
||||
private readonly SoundIoDeviceContext _audioDevice;
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private readonly ManualResetEvent _pauseEvent;
|
||||
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
|
||||
private int _disposeState;
|
||||
|
||||
public SoundIoHardwareDeviceDriver()
|
||||
{
|
||||
_audioContext = SoundIoContext.Create();
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_pauseEvent = new ManualResetEvent(true);
|
||||
_sessions = new ConcurrentDictionary<SoundIoHardwareDeviceSession, byte>();
|
||||
|
||||
_audioContext.Connect();
|
||||
_audioContext.FlushEvents();
|
||||
|
||||
_audioDevice = FindValidAudioDevice(_audioContext, true);
|
||||
}
|
||||
|
||||
public static bool IsSupported => IsSupportedInternal();
|
||||
|
||||
private static bool IsSupportedInternal()
|
||||
{
|
||||
SoundIoContext context = null;
|
||||
SoundIoDeviceContext device = null;
|
||||
SoundIoOutStreamContext stream = null;
|
||||
|
||||
bool backendDisconnected = false;
|
||||
|
||||
try
|
||||
{
|
||||
context = SoundIoContext.Create();
|
||||
context.OnBackendDisconnect = err =>
|
||||
{
|
||||
backendDisconnected = true;
|
||||
};
|
||||
|
||||
context.Connect();
|
||||
context.FlushEvents();
|
||||
|
||||
if (backendDisconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.OutputDeviceCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
device = FindValidAudioDevice(context);
|
||||
|
||||
if (device == null || backendDisconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stream = device.CreateOutStream();
|
||||
|
||||
if (stream == null || backendDisconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream?.Dispose();
|
||||
context?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static SoundIoDeviceContext FindValidAudioDevice(SoundIoContext audioContext, bool fallback = false)
|
||||
{
|
||||
SoundIoDeviceContext defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex);
|
||||
|
||||
if (!defaultAudioDevice.IsRaw)
|
||||
{
|
||||
return defaultAudioDevice;
|
||||
}
|
||||
|
||||
for (int i = 0; i < audioContext.OutputDeviceCount; i++)
|
||||
{
|
||||
SoundIoDeviceContext audioDevice = audioContext.GetOutputDevice(i);
|
||||
|
||||
if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw)
|
||||
{
|
||||
return audioDevice;
|
||||
}
|
||||
}
|
||||
|
||||
return fallback ? defaultAudioDevice : null;
|
||||
}
|
||||
|
||||
public ManualResetEvent GetUpdateRequiredEvent()
|
||||
{
|
||||
return _updateRequiredEvent;
|
||||
}
|
||||
|
||||
public ManualResetEvent GetPauseEvent()
|
||||
{
|
||||
return _pauseEvent;
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
channelCount = 2;
|
||||
}
|
||||
|
||||
if (sampleRate == 0)
|
||||
{
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
if (direction != Direction.Output)
|
||||
{
|
||||
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
|
||||
}
|
||||
|
||||
SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||
|
||||
_sessions.TryAdd(session, 0);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
internal bool Unregister(SoundIoHardwareDeviceSession session)
|
||||
{
|
||||
return _sessions.TryRemove(session, out _);
|
||||
}
|
||||
|
||||
public static SoundIoFormat GetSoundIoFormat(SampleFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
SampleFormat.PcmInt8 => SoundIoFormat.S8,
|
||||
SampleFormat.PcmInt16 => SoundIoFormat.S16LE,
|
||||
SampleFormat.PcmInt24 => SoundIoFormat.S24LE,
|
||||
SampleFormat.PcmInt32 => SoundIoFormat.S32LE,
|
||||
SampleFormat.PcmFloat => SoundIoFormat.Float32LE,
|
||||
_ => throw new ArgumentException ($"Unsupported sample format {format}"),
|
||||
};
|
||||
}
|
||||
|
||||
internal SoundIoOutStreamContext OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount)
|
||||
{
|
||||
SoundIoFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat);
|
||||
|
||||
if (!_audioDevice.SupportsSampleRate((int)requestedSampleRate))
|
||||
{
|
||||
throw new ArgumentException($"This sound device does not support a sample rate of {requestedSampleRate}Hz");
|
||||
}
|
||||
|
||||
if (!_audioDevice.SupportsFormat(driverSampleFormat))
|
||||
{
|
||||
throw new ArgumentException($"This sound device does not support {requestedSampleFormat}");
|
||||
}
|
||||
|
||||
if (!_audioDevice.SupportsChannelCount((int)requestedChannelCount))
|
||||
{
|
||||
throw new ArgumentException($"This sound device does not support channel count {requestedChannelCount}");
|
||||
}
|
||||
|
||||
SoundIoOutStreamContext result = _audioDevice.CreateOutStream();
|
||||
|
||||
result.Name = "Ryujinx";
|
||||
result.Layout = SoundIoChannelLayout.GetDefaultValue((int)requestedChannelCount);
|
||||
result.Format = driverSampleFormat;
|
||||
result.SampleRate = (int)requestedSampleRate;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal void FlushContextEvents()
|
||||
{
|
||||
_audioContext.FlushEvents();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
|
||||
{
|
||||
session.Dispose();
|
||||
}
|
||||
|
||||
_audioContext.Disconnect();
|
||||
_audioContext.Dispose();
|
||||
_pauseEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsSampleRate(uint sampleRate)
|
||||
{
|
||||
return _audioDevice.SupportsSampleRate((int)sampleRate);
|
||||
}
|
||||
|
||||
public bool SupportsSampleFormat(SampleFormat sampleFormat)
|
||||
{
|
||||
return _audioDevice.SupportsFormat(GetSoundIoFormat(sampleFormat));
|
||||
}
|
||||
|
||||
public bool SupportsChannelCount(uint channelCount)
|
||||
{
|
||||
return _audioDevice.SupportsChannelCount((int)channelCount);
|
||||
}
|
||||
|
||||
public bool SupportsDirection(Direction direction)
|
||||
{
|
||||
// TODO: add direction input when supported.
|
||||
return direction == Direction.Output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
using Ryujinx.Audio.Backends.Common;
|
||||
using Ryujinx.Audio.Backends.Dummy;
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
{
|
||||
public class CompatLayerHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private IHardwareDeviceDriver _realDriver;
|
||||
|
||||
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
|
||||
{
|
||||
_realDriver = realDevice;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_realDriver.Dispose();
|
||||
}
|
||||
|
||||
public ManualResetEvent GetUpdateRequiredEvent()
|
||||
{
|
||||
return _realDriver.GetUpdateRequiredEvent();
|
||||
}
|
||||
|
||||
public ManualResetEvent GetPauseEvent()
|
||||
{
|
||||
return _realDriver.GetPauseEvent();
|
||||
}
|
||||
|
||||
private uint SelectHardwareChannelCount(uint targetChannelCount)
|
||||
{
|
||||
if (_realDriver.SupportsChannelCount(targetChannelCount))
|
||||
{
|
||||
return targetChannelCount;
|
||||
}
|
||||
|
||||
return targetChannelCount switch
|
||||
{
|
||||
6 => SelectHardwareChannelCount(2),
|
||||
2 => SelectHardwareChannelCount(1),
|
||||
1 => throw new ArgumentException("No valid channel configuration found!"),
|
||||
_ => throw new ArgumentException($"Invalid targetChannelCount {targetChannelCount}")
|
||||
};
|
||||
}
|
||||
|
||||
private SampleFormat SelectHardwareSampleFormat(SampleFormat targetSampleFormat)
|
||||
{
|
||||
if (_realDriver.SupportsSampleFormat(targetSampleFormat))
|
||||
{
|
||||
return targetSampleFormat;
|
||||
}
|
||||
|
||||
// Attempt conversion from PCM16.
|
||||
if (targetSampleFormat == SampleFormat.PcmInt16)
|
||||
{
|
||||
// Prefer PCM32 if we need to convert.
|
||||
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt32))
|
||||
{
|
||||
return SampleFormat.PcmInt32;
|
||||
}
|
||||
|
||||
// If not supported, PCM float provides the best quality with a cost lower than PCM24.
|
||||
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmFloat))
|
||||
{
|
||||
return SampleFormat.PcmFloat;
|
||||
}
|
||||
|
||||
// TODO: Implement PCM24 conversion.
|
||||
|
||||
// If nothing is truly supported, attempt PCM8 at the cost of loosing quality.
|
||||
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8))
|
||||
{
|
||||
return SampleFormat.PcmInt8;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException("No valid sample format configuration found!");
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
channelCount = 2;
|
||||
}
|
||||
|
||||
if (sampleRate == 0)
|
||||
{
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
if (!_realDriver.SupportsDirection(direction))
|
||||
{
|
||||
if (direction == Direction.Input)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Audio, "The selected audio backend doesn't support audio input, fallback to dummy...");
|
||||
|
||||
return new DummyHardwareDeviceSessionInput(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
|
||||
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
|
||||
|
||||
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume);
|
||||
|
||||
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
|
||||
{
|
||||
return realSession;
|
||||
}
|
||||
|
||||
if (hardwareSampleFormat != sampleFormat)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Audio, $"{sampleFormat} isn't supported by the audio device, conversion to {hardwareSampleFormat} will happen.");
|
||||
|
||||
if (hardwareSampleFormat < sampleFormat)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Audio, $"{hardwareSampleFormat} has lower quality than {sampleFormat}, expect some loss in audio fidelity.");
|
||||
}
|
||||
}
|
||||
|
||||
if (direction == Direction.Input)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Audio, $"The selected audio backend doesn't support the requested audio input configuration, fallback to dummy...");
|
||||
|
||||
// TODO: We currently don't support audio input upsampling/downsampling, implement this.
|
||||
realSession.Dispose();
|
||||
|
||||
return new DummyHardwareDeviceSessionInput(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
}
|
||||
|
||||
// It must be a HardwareDeviceSessionOutputBase.
|
||||
if (realSession is not HardwareDeviceSessionOutputBase realSessionOutputBase)
|
||||
{
|
||||
throw new InvalidOperationException($"Real driver session class type isn't based on {typeof(HardwareDeviceSessionOutputBase).Name}.");
|
||||
}
|
||||
|
||||
// If we need to do post processing before sending to the hardware device, wrap around it.
|
||||
return new CompatLayerHardwareDeviceSession(realSessionOutputBase, sampleFormat, channelCount);
|
||||
}
|
||||
|
||||
public bool SupportsChannelCount(uint channelCount)
|
||||
{
|
||||
return channelCount == 1 || channelCount == 2 || channelCount == 6;
|
||||
}
|
||||
|
||||
public bool SupportsSampleFormat(SampleFormat sampleFormat)
|
||||
{
|
||||
// TODO: More formats.
|
||||
return sampleFormat == SampleFormat.PcmInt16;
|
||||
}
|
||||
|
||||
public bool SupportsSampleRate(uint sampleRate)
|
||||
{
|
||||
// TODO: More sample rates.
|
||||
return sampleRate == Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
public IHardwareDeviceDriver GetRealDeviceDriver()
|
||||
{
|
||||
return _realDriver;
|
||||
}
|
||||
|
||||
public bool SupportsDirection(Direction direction)
|
||||
{
|
||||
return direction == Direction.Input || direction == Direction.Output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
using Ryujinx.Audio.Backends.Common;
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
{
|
||||
class CompatLayerHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
{
|
||||
private HardwareDeviceSessionOutputBase _realSession;
|
||||
private SampleFormat _userSampleFormat;
|
||||
private uint _userChannelCount;
|
||||
|
||||
public CompatLayerHardwareDeviceSession(HardwareDeviceSessionOutputBase realSession, SampleFormat userSampleFormat, uint userChannelCount) : base(realSession.MemoryManager, realSession.RequestedSampleFormat, realSession.RequestedSampleRate, userChannelCount)
|
||||
{
|
||||
_realSession = realSession;
|
||||
_userSampleFormat = userSampleFormat;
|
||||
_userChannelCount = userChannelCount;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_realSession.Dispose();
|
||||
}
|
||||
|
||||
public override ulong GetPlayedSampleCount()
|
||||
{
|
||||
return _realSession.GetPlayedSampleCount();
|
||||
}
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
return _realSession.GetVolume();
|
||||
}
|
||||
|
||||
public override void PrepareToClose()
|
||||
{
|
||||
_realSession.PrepareToClose();
|
||||
}
|
||||
|
||||
public override void QueueBuffer(AudioBuffer buffer)
|
||||
{
|
||||
SampleFormat realSampleFormat = _realSession.RequestedSampleFormat;
|
||||
|
||||
if (_userSampleFormat != realSampleFormat)
|
||||
{
|
||||
if (_userSampleFormat != SampleFormat.PcmInt16)
|
||||
{
|
||||
throw new NotImplementedException("Converting formats other than PCM16 is not supported.");
|
||||
}
|
||||
|
||||
int userSampleCount = buffer.Data.Length / BackendHelper.GetSampleSize(_userSampleFormat);
|
||||
|
||||
ReadOnlySpan<short> samples = MemoryMarshal.Cast<byte, short>(buffer.Data);
|
||||
byte[] convertedSamples = new byte[BackendHelper.GetSampleSize(realSampleFormat) * userSampleCount];
|
||||
|
||||
switch (realSampleFormat)
|
||||
{
|
||||
case SampleFormat.PcmInt8:
|
||||
PcmHelper.Convert(MemoryMarshal.Cast<byte, sbyte>(convertedSamples), samples);
|
||||
break;
|
||||
case SampleFormat.PcmInt32:
|
||||
PcmHelper.Convert(MemoryMarshal.Cast<byte, int>(convertedSamples), samples);
|
||||
break;
|
||||
case SampleFormat.PcmFloat:
|
||||
PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast<byte, float>(convertedSamples), samples);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Sample format conversion from {_userSampleFormat} to {realSampleFormat} not implemented.");
|
||||
}
|
||||
|
||||
buffer.Data = convertedSamples;
|
||||
}
|
||||
|
||||
_realSession.QueueBuffer(buffer);
|
||||
}
|
||||
|
||||
public override bool RegisterBuffer(AudioBuffer buffer, byte[] samples)
|
||||
{
|
||||
if (samples == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_userChannelCount != _realSession.RequestedChannelCount)
|
||||
{
|
||||
if (_userSampleFormat != SampleFormat.PcmInt16)
|
||||
{
|
||||
throw new NotImplementedException("Downmixing formats other than PCM16 is not supported.");
|
||||
}
|
||||
|
||||
ReadOnlySpan<short> samplesPCM16 = MemoryMarshal.Cast<byte, short>(samples);
|
||||
|
||||
if (_userChannelCount == 6)
|
||||
{
|
||||
samplesPCM16 = Downmixing.DownMixSurroundToStereo(samplesPCM16);
|
||||
|
||||
if (_realSession.RequestedChannelCount == 1)
|
||||
{
|
||||
samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16);
|
||||
}
|
||||
}
|
||||
else if (_userChannelCount == 2 && _realSession.RequestedChannelCount == 1)
|
||||
{
|
||||
samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException($"Downmixing from {_userChannelCount} to {_realSession.RequestedChannelCount} not implemented.");
|
||||
}
|
||||
|
||||
samples = MemoryMarshal.Cast<short, byte>(samplesPCM16).ToArray();
|
||||
}
|
||||
|
||||
AudioBuffer fakeBuffer = new AudioBuffer
|
||||
{
|
||||
BufferTag = buffer.BufferTag,
|
||||
DataPointer = buffer.DataPointer,
|
||||
DataSize = (ulong)samples.Length
|
||||
};
|
||||
|
||||
bool result = _realSession.RegisterBuffer(fakeBuffer, samples);
|
||||
|
||||
if (result)
|
||||
{
|
||||
buffer.Data = fakeBuffer.Data;
|
||||
buffer.DataSize = fakeBuffer.DataSize;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
_realSession.SetVolume(volume);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
_realSession.Start();
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
_realSession.Stop();
|
||||
}
|
||||
|
||||
public override void UnregisterBuffer(AudioBuffer buffer)
|
||||
{
|
||||
_realSession.UnregisterBuffer(buffer);
|
||||
}
|
||||
|
||||
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
|
||||
{
|
||||
return _realSession.WasBufferFullyConsumed(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Memory;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.Dummy
|
||||
{
|
||||
public class DummyHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private ManualResetEvent _updateRequiredEvent;
|
||||
private ManualResetEvent _pauseEvent;
|
||||
|
||||
public DummyHardwareDeviceDriver()
|
||||
{
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_pauseEvent = new ManualResetEvent(true);
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
{
|
||||
if (sampleRate == 0)
|
||||
{
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
if (channelCount == 0)
|
||||
{
|
||||
channelCount = 2;
|
||||
}
|
||||
|
||||
if (direction == Direction.Output)
|
||||
{
|
||||
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DummyHardwareDeviceSessionInput(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
}
|
||||
}
|
||||
|
||||
public ManualResetEvent GetUpdateRequiredEvent()
|
||||
{
|
||||
return _updateRequiredEvent;
|
||||
}
|
||||
|
||||
public ManualResetEvent GetPauseEvent()
|
||||
{
|
||||
return _pauseEvent;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// NOTE: The _updateRequiredEvent will be disposed somewhere else.
|
||||
_pauseEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsSampleRate(uint sampleRate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SupportsSampleFormat(SampleFormat sampleFormat)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SupportsDirection(Direction direction)
|
||||
{
|
||||
return direction == Direction.Output || direction == Direction.Input;
|
||||
}
|
||||
|
||||
public bool SupportsChannelCount(uint channelCount)
|
||||
{
|
||||
return channelCount == 1 || channelCount == 2 || channelCount == 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,516 +0,0 @@
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// An audio device session.
|
||||
/// </summary>
|
||||
class AudioDeviceSession : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The volume of the <see cref="AudioDeviceSession"/>.
|
||||
/// </summary>
|
||||
private float _volume;
|
||||
|
||||
/// <summary>
|
||||
/// The state of the <see cref="AudioDeviceSession"/>.
|
||||
/// </summary>
|
||||
private AudioDeviceState _state;
|
||||
|
||||
/// <summary>
|
||||
/// Array of all buffers currently used or released.
|
||||
/// </summary>
|
||||
private AudioBuffer[] _buffers;
|
||||
|
||||
/// <summary>
|
||||
/// The server index inside <see cref="_buffers"/> (appended but not queued to device driver).
|
||||
/// </summary>
|
||||
private uint _serverBufferIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The hardware index inside <see cref="_buffers"/> (queued to device driver).
|
||||
/// </summary>
|
||||
private uint _hardwareBufferIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The released index inside <see cref="_buffers"/> (released by the device driver).
|
||||
/// </summary>
|
||||
private uint _releasedBufferIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The count of buffer appended (server side).
|
||||
/// </summary>
|
||||
private uint _bufferAppendedCount;
|
||||
|
||||
/// <summary>
|
||||
/// The count of buffer registered (driver side).
|
||||
/// </summary>
|
||||
private uint _bufferRegisteredCount;
|
||||
|
||||
/// <summary>
|
||||
/// The count of buffer released (released by the driver side).
|
||||
/// </summary>
|
||||
private uint _bufferReleasedCount;
|
||||
|
||||
/// <summary>
|
||||
/// The released buffer event.
|
||||
/// </summary>
|
||||
private IWritableEvent _bufferEvent;
|
||||
|
||||
/// <summary>
|
||||
/// The session on the device driver.
|
||||
/// </summary>
|
||||
private IHardwareDeviceSession _hardwareDeviceSession;
|
||||
|
||||
/// <summary>
|
||||
/// Max number of buffers that can be registered to the device driver at a time.
|
||||
/// </summary>
|
||||
private uint _bufferRegisteredLimit;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AudioDeviceSession"/>.
|
||||
/// </summary>
|
||||
/// <param name="deviceSession">The device driver session associated</param>
|
||||
/// <param name="bufferEvent">The release buffer event</param>
|
||||
/// <param name="bufferRegisteredLimit">The max number of buffers that can be registered to the device driver at a time</param>
|
||||
public AudioDeviceSession(IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent, uint bufferRegisteredLimit = 4)
|
||||
{
|
||||
_bufferEvent = bufferEvent;
|
||||
_hardwareDeviceSession = deviceSession;
|
||||
_bufferRegisteredLimit = bufferRegisteredLimit;
|
||||
|
||||
_buffers = new AudioBuffer[Constants.AudioDeviceBufferCountMax];
|
||||
_serverBufferIndex = 0;
|
||||
_hardwareBufferIndex = 0;
|
||||
_releasedBufferIndex = 0;
|
||||
|
||||
_bufferAppendedCount = 0;
|
||||
_bufferRegisteredCount = 0;
|
||||
_bufferReleasedCount = 0;
|
||||
_volume = deviceSession.GetVolume();
|
||||
_state = AudioDeviceState.Stopped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the released buffer event.
|
||||
/// </summary>
|
||||
/// <returns>The released buffer event</returns>
|
||||
public IWritableEvent GetBufferEvent()
|
||||
{
|
||||
return _bufferEvent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the state of the session.
|
||||
/// </summary>
|
||||
/// <returns>The state of the session</returns>
|
||||
public AudioDeviceState GetState()
|
||||
{
|
||||
Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped);
|
||||
|
||||
return _state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total buffer count (server + driver + released).
|
||||
/// </summary>
|
||||
/// <returns>Return the total buffer count</returns>
|
||||
private uint GetTotalBufferCount()
|
||||
{
|
||||
uint bufferCount = _bufferAppendedCount + _bufferRegisteredCount + _bufferReleasedCount;
|
||||
|
||||
Debug.Assert(bufferCount <= Constants.AudioDeviceBufferCountMax);
|
||||
|
||||
return bufferCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new <see cref="AudioBuffer"/> on the server side.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The <see cref="AudioBuffer"/> to register</param>
|
||||
/// <returns>True if the operation succeeded</returns>
|
||||
private bool RegisterBuffer(AudioBuffer buffer)
|
||||
{
|
||||
if (GetTotalBufferCount() == Constants.AudioDeviceBufferCountMax)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_buffers[_serverBufferIndex] = buffer;
|
||||
_serverBufferIndex = (_serverBufferIndex + 1) % Constants.AudioDeviceBufferCountMax;
|
||||
_bufferAppendedCount++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush server buffers to hardware.
|
||||
/// </summary>
|
||||
private void FlushToHardware()
|
||||
{
|
||||
uint bufferToFlushCount = Math.Min(Math.Min(_bufferAppendedCount, 4), _bufferRegisteredLimit - _bufferRegisteredCount);
|
||||
|
||||
AudioBuffer[] buffersToFlush = new AudioBuffer[bufferToFlushCount];
|
||||
|
||||
uint hardwareBufferIndex = _hardwareBufferIndex;
|
||||
|
||||
for (int i = 0; i < buffersToFlush.Length; i++)
|
||||
{
|
||||
buffersToFlush[i] = _buffers[hardwareBufferIndex];
|
||||
|
||||
_bufferAppendedCount--;
|
||||
_bufferRegisteredCount++;
|
||||
|
||||
hardwareBufferIndex = (hardwareBufferIndex + 1) % Constants.AudioDeviceBufferCountMax;
|
||||
}
|
||||
|
||||
_hardwareBufferIndex = hardwareBufferIndex;
|
||||
|
||||
for (int i = 0; i < buffersToFlush.Length; i++)
|
||||
{
|
||||
_hardwareDeviceSession.QueueBuffer(buffersToFlush[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current index of the <see cref="AudioBuffer"/> playing on the driver side.
|
||||
/// </summary>
|
||||
/// <param name="playingIndex">The output index of the <see cref="AudioBuffer"/> playing on the driver side</param>
|
||||
/// <returns>True if any buffer is playing</returns>
|
||||
private bool TryGetPlayingBufferIndex(out uint playingIndex)
|
||||
{
|
||||
if (_bufferRegisteredCount > 0)
|
||||
{
|
||||
playingIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
playingIndex = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to pop the <see cref="AudioBuffer"/> playing on the driver side.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The output <see cref="AudioBuffer"/> playing on the driver side</param>
|
||||
/// <returns>True if any buffer is playing</returns>
|
||||
private bool TryPopPlayingBuffer(out AudioBuffer buffer)
|
||||
{
|
||||
if (_bufferRegisteredCount > 0)
|
||||
{
|
||||
uint bufferIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax;
|
||||
|
||||
buffer = _buffers[bufferIndex];
|
||||
|
||||
_buffers[bufferIndex] = null;
|
||||
|
||||
_bufferRegisteredCount--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
buffer = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to pop a <see cref="AudioBuffer"/> released by the driver side.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The output <see cref="AudioBuffer"/> released by the driver side</param>
|
||||
/// <returns>True if any buffer has been released</returns>
|
||||
public bool TryPopReleasedBuffer(out AudioBuffer buffer)
|
||||
{
|
||||
if (_bufferReleasedCount > 0)
|
||||
{
|
||||
uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
|
||||
|
||||
buffer = _buffers[bufferIndex];
|
||||
|
||||
_buffers[bufferIndex] = null;
|
||||
|
||||
_bufferReleasedCount--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
buffer = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release a <see cref="AudioBuffer"/>.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The <see cref="AudioBuffer"/> to release</param>
|
||||
private void ReleaseBuffer(AudioBuffer buffer)
|
||||
{
|
||||
buffer.PlayedTimestamp = (ulong)PerformanceCounter.ElapsedNanoseconds;
|
||||
|
||||
_bufferRegisteredCount--;
|
||||
_bufferReleasedCount++;
|
||||
|
||||
_releasedBufferIndex = (_releasedBufferIndex + 1) % Constants.AudioDeviceBufferCountMax;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the released buffers.
|
||||
/// </summary>
|
||||
/// <param name="updateForStop">True if the session is currently stopping</param>
|
||||
private void UpdateReleaseBuffers(bool updateForStop = false)
|
||||
{
|
||||
bool wasAnyBuffersReleased = false;
|
||||
|
||||
while (TryGetPlayingBufferIndex(out uint playingIndex))
|
||||
{
|
||||
if (!updateForStop && !_hardwareDeviceSession.WasBufferFullyConsumed(_buffers[playingIndex]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (updateForStop)
|
||||
{
|
||||
_hardwareDeviceSession.UnregisterBuffer(_buffers[playingIndex]);
|
||||
}
|
||||
|
||||
ReleaseBuffer(_buffers[playingIndex]);
|
||||
|
||||
wasAnyBuffersReleased = true;
|
||||
}
|
||||
|
||||
if (wasAnyBuffersReleased)
|
||||
{
|
||||
_bufferEvent.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append a new <see cref="AudioBuffer"/>.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The <see cref="AudioBuffer"/> to append</param>
|
||||
/// <returns>True if the buffer was appended</returns>
|
||||
public bool AppendBuffer(AudioBuffer buffer)
|
||||
{
|
||||
if (_hardwareDeviceSession.RegisterBuffer(buffer))
|
||||
{
|
||||
if (RegisterBuffer(buffer))
|
||||
{
|
||||
FlushToHardware();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_hardwareDeviceSession.UnregisterBuffer(buffer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool AppendUacBuffer(AudioBuffer buffer, uint handle)
|
||||
{
|
||||
// NOTE: On hardware, there is another RegisterBuffer method taking an handle.
|
||||
// This variant of the call always return false (stubbed?) as a result this logic will never succeed.
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the audio session.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
|
||||
public ResultCode Start()
|
||||
{
|
||||
if (_state == AudioDeviceState.Started)
|
||||
{
|
||||
return ResultCode.OperationFailed;
|
||||
}
|
||||
|
||||
_hardwareDeviceSession.Start();
|
||||
|
||||
_state = AudioDeviceState.Started;
|
||||
|
||||
FlushToHardware();
|
||||
|
||||
_hardwareDeviceSession.SetVolume(_volume);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the audio session.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
|
||||
public ResultCode Stop()
|
||||
{
|
||||
if (_state == AudioDeviceState.Started)
|
||||
{
|
||||
_hardwareDeviceSession.Stop();
|
||||
|
||||
UpdateReleaseBuffers(true);
|
||||
|
||||
_state = AudioDeviceState.Stopped;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the volume of the session.
|
||||
/// </summary>
|
||||
/// <returns>The volume of the session</returns>
|
||||
public float GetVolume()
|
||||
{
|
||||
return _hardwareDeviceSession.GetVolume();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the volume of the session.
|
||||
/// </summary>
|
||||
/// <param name="volume">The new volume to set</param>
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
_volume = volume;
|
||||
|
||||
if (_state == AudioDeviceState.Started)
|
||||
{
|
||||
_hardwareDeviceSession.SetVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the count of buffer currently in use (server + driver side).
|
||||
/// </summary>
|
||||
/// <returns>The count of buffer currently in use</returns>
|
||||
public uint GetBufferCount()
|
||||
{
|
||||
return _bufferAppendedCount + _bufferRegisteredCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a buffer is present.
|
||||
/// </summary>
|
||||
/// <param name="bufferTag">The unique tag of the buffer</param>
|
||||
/// <returns>Return true if a buffer is present</returns>
|
||||
public bool ContainsBuffer(ulong bufferTag)
|
||||
{
|
||||
uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
|
||||
|
||||
for (int i = 0; i < GetTotalBufferCount(); i++)
|
||||
{
|
||||
if (_buffers[bufferIndex].BufferTag == bufferTag)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bufferIndex = (bufferIndex + 1) % Constants.AudioDeviceBufferCountMax;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the count of sample played in this session.
|
||||
/// </summary>
|
||||
/// <returns>The count of sample played in this session</returns>
|
||||
public ulong GetPlayedSampleCount()
|
||||
{
|
||||
if (_state == AudioDeviceState.Stopped)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _hardwareDeviceSession.GetPlayedSampleCount();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush all buffers to the initial state.
|
||||
/// </summary>
|
||||
/// <returns>True if any buffer was flushed</returns>
|
||||
public bool FlushBuffers()
|
||||
{
|
||||
if (_state == AudioDeviceState.Stopped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint bufferCount = GetBufferCount();
|
||||
|
||||
while (TryPopReleasedBuffer(out AudioBuffer buffer))
|
||||
{
|
||||
_hardwareDeviceSession.UnregisterBuffer(buffer);
|
||||
}
|
||||
|
||||
while (TryPopPlayingBuffer(out AudioBuffer buffer))
|
||||
{
|
||||
_hardwareDeviceSession.UnregisterBuffer(buffer);
|
||||
}
|
||||
|
||||
if (_bufferRegisteredCount == 0 || (_bufferReleasedCount + _bufferAppendedCount) > Constants.AudioDeviceBufferCountMax)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_bufferReleasedCount += _bufferAppendedCount;
|
||||
_releasedBufferIndex = (_releasedBufferIndex + _bufferAppendedCount) % Constants.AudioDeviceBufferCountMax;
|
||||
_bufferAppendedCount = 0;
|
||||
_hardwareBufferIndex = _serverBufferIndex;
|
||||
|
||||
if (bufferCount > 0)
|
||||
{
|
||||
_bufferEvent.Signal();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the session.
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
if (_state == AudioDeviceState.Started)
|
||||
{
|
||||
UpdateReleaseBuffers();
|
||||
FlushToHardware();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Tell the hardware session that we are ending.
|
||||
_hardwareDeviceSession.PrepareToClose();
|
||||
|
||||
// Unregister all buffers
|
||||
|
||||
while (TryPopReleasedBuffer(out AudioBuffer buffer))
|
||||
{
|
||||
_hardwareDeviceSession.UnregisterBuffer(buffer);
|
||||
}
|
||||
|
||||
while (TryPopPlayingBuffer(out AudioBuffer buffer))
|
||||
{
|
||||
_hardwareDeviceSession.UnregisterBuffer(buffer);
|
||||
}
|
||||
|
||||
// Finally dispose hardware session.
|
||||
_hardwareDeviceSession.Dispose();
|
||||
|
||||
_bufferEvent.Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Audio.Integration
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent an hardware device driver used in <see cref="Output.AudioOutputSystem"/>.
|
||||
/// </summary>
|
||||
public interface IHardwareDeviceDriver : IDisposable
|
||||
{
|
||||
public enum Direction
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
}
|
||||
|
||||
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f);
|
||||
|
||||
ManualResetEvent GetUpdateRequiredEvent();
|
||||
ManualResetEvent GetPauseEvent();
|
||||
|
||||
bool SupportsDirection(Direction direction);
|
||||
bool SupportsSampleRate(uint sampleRate);
|
||||
bool SupportsSampleFormat(SampleFormat sampleFormat);
|
||||
bool SupportsChannelCount(uint channelCount);
|
||||
|
||||
IHardwareDeviceDriver GetRealDeviceDriver()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Device
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a virtual device used by IAudioDevice.
|
||||
/// </summary>
|
||||
public class VirtualDevice
|
||||
{
|
||||
/// <summary>
|
||||
/// All the defined virtual devices.
|
||||
/// </summary>
|
||||
public static readonly VirtualDevice[] Devices = new VirtualDevice[5]
|
||||
{
|
||||
new VirtualDevice("AudioStereoJackOutput", 2, true),
|
||||
new VirtualDevice("AudioBuiltInSpeakerOutput", 2, false),
|
||||
new VirtualDevice("AudioTvOutput", 6, false),
|
||||
new VirtualDevice("AudioUsbDeviceOutput", 2, true),
|
||||
new VirtualDevice("AudioExternalOutput", 6, true),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The count of channels supported by the <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
public uint ChannelCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The system master volume of the <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
public float MasterVolume { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Define if the <see cref="VirtualDevice"/> is provided by an external interface.
|
||||
/// </summary>
|
||||
public bool IsExternalOutput { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="VirtualDevice"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the <see cref="VirtualDevice"/>.</param>
|
||||
/// <param name="channelCount">The count of channels supported by the <see cref="VirtualDevice"/>.</param>
|
||||
/// <param name="isExternalOutput">Indicate if the <see cref="VirtualDevice"/> is provided by an external interface.</param>
|
||||
private VirtualDevice(string name, uint channelCount, bool isExternalOutput)
|
||||
{
|
||||
Name = name;
|
||||
ChannelCount = channelCount;
|
||||
IsExternalOutput = isExternalOutput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the master volume of the <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
/// <param name="volume">The new master volume.</param>
|
||||
public void UpdateMasterVolume(float volume)
|
||||
{
|
||||
Debug.Assert(volume >= 0.0f && volume <= 1.0f);
|
||||
|
||||
MasterVolume = volume;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="VirtualDevice"/> is a usb device.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the <see cref="VirtualDevice"/> is a usb device.</returns>
|
||||
public bool IsUsbDevice()
|
||||
{
|
||||
return Name.Equals("AudioUsbDeviceOutput");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the output device name of the <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
/// <returns>The output device name of the <see cref="VirtualDevice"/>.</returns>
|
||||
public string GetOutputDeviceName()
|
||||
{
|
||||
if (IsExternalOutput)
|
||||
{
|
||||
return "AudioExternalOutput";
|
||||
}
|
||||
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Device
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent an instance containing a registry of <see cref="VirtualDeviceSession"/>.
|
||||
/// </summary>
|
||||
public class VirtualDeviceSessionRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// The session registry, used to store the sessions of a given AppletResourceId.
|
||||
/// </summary>
|
||||
private Dictionary<ulong, VirtualDeviceSession[]> _sessionsRegistry = new Dictionary<ulong, VirtualDeviceSession[]>();
|
||||
|
||||
/// <summary>
|
||||
/// The default <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This is used when the USB device is the default one on older revision.</remarks>
|
||||
public VirtualDevice DefaultDevice => VirtualDevice.Devices[0];
|
||||
|
||||
/// <summary>
|
||||
/// The current active <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
// TODO: make this configurable
|
||||
public VirtualDevice ActiveDevice = VirtualDevice.Devices[2];
|
||||
|
||||
/// <summary>
|
||||
/// Get the associated <see cref="T:VirtualDeviceSession[]"/> from an AppletResourceId.
|
||||
/// </summary>
|
||||
/// <param name="resourceAppletId">The AppletResourceId used.</param>
|
||||
/// <returns>The associated <see cref="T:VirtualDeviceSession[]"/> from an AppletResourceId.</returns>
|
||||
public VirtualDeviceSession[] GetSessionByAppletResourceId(ulong resourceAppletId)
|
||||
{
|
||||
if (_sessionsRegistry.TryGetValue(resourceAppletId, out VirtualDeviceSession[] result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = CreateSessionsFromBehaviourContext();
|
||||
|
||||
_sessionsRegistry.Add(resourceAppletId, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new array of sessions for each <see cref="VirtualDevice"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new array of sessions for each <see cref="VirtualDevice"/>.</returns>
|
||||
private static VirtualDeviceSession[] CreateSessionsFromBehaviourContext()
|
||||
{
|
||||
VirtualDeviceSession[] virtualDeviceSession = new VirtualDeviceSession[VirtualDevice.Devices.Length];
|
||||
|
||||
for (int i = 0; i < virtualDeviceSession.Length; i++)
|
||||
{
|
||||
virtualDeviceSession[i] = new VirtualDeviceSession(VirtualDevice.Devices[i]);
|
||||
}
|
||||
|
||||
return virtualDeviceSession;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public class AudioProcessor : IDisposable
|
||||
{
|
||||
private const int MaxBufferedFrames = 5;
|
||||
private const int TargetBufferedFrames = 3;
|
||||
|
||||
private enum MailboxMessage : uint
|
||||
{
|
||||
Start,
|
||||
Stop,
|
||||
RenderStart,
|
||||
RenderEnd
|
||||
}
|
||||
|
||||
private class RendererSession
|
||||
{
|
||||
public CommandList CommandList;
|
||||
public int RenderingLimit;
|
||||
public ulong AppletResourceId;
|
||||
}
|
||||
|
||||
private Mailbox<MailboxMessage> _mailbox;
|
||||
private RendererSession[] _sessionCommandList;
|
||||
private Thread _workerThread;
|
||||
|
||||
public IHardwareDevice[] OutputDevices { get; private set; }
|
||||
|
||||
private long _lastTime;
|
||||
private long _playbackEnds;
|
||||
private ManualResetEvent _event;
|
||||
|
||||
private ManualResetEvent _pauseEvent;
|
||||
|
||||
public AudioProcessor()
|
||||
{
|
||||
_event = new ManualResetEvent(false);
|
||||
}
|
||||
|
||||
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
|
||||
{
|
||||
// Get the real device driver (In case the compat layer is on top of it).
|
||||
deviceDriver = deviceDriver.GetRealDeviceDriver();
|
||||
|
||||
if (deviceDriver.SupportsChannelCount(6))
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
|
||||
{
|
||||
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
||||
|
||||
// TODO: Before enabling this, we need up-mixing from stereo to 5.1.
|
||||
// uint channelCount = GetHardwareChannelCount(deviceDriver);
|
||||
uint channelCount = 2;
|
||||
|
||||
for (int i = 0; i < OutputDevices.Length; i++)
|
||||
{
|
||||
// TODO: Don't hardcode sample rate.
|
||||
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume);
|
||||
}
|
||||
|
||||
_mailbox = new Mailbox<MailboxMessage>();
|
||||
_sessionCommandList = new RendererSession[Constants.AudioRendererSessionCountMax];
|
||||
_event.Reset();
|
||||
_lastTime = PerformanceCounter.ElapsedNanoseconds;
|
||||
_pauseEvent = deviceDriver.GetPauseEvent();
|
||||
|
||||
StartThread();
|
||||
|
||||
_mailbox.SendMessage(MailboxMessage.Start);
|
||||
|
||||
if (_mailbox.ReceiveResponse() != MailboxMessage.Start)
|
||||
{
|
||||
throw new InvalidOperationException("Audio Processor Start response was invalid!");
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_mailbox.SendMessage(MailboxMessage.Stop);
|
||||
|
||||
if (_mailbox.ReceiveResponse() != MailboxMessage.Stop)
|
||||
{
|
||||
throw new InvalidOperationException("Audio Processor Stop response was invalid!");
|
||||
}
|
||||
|
||||
foreach (IHardwareDevice device in OutputDevices)
|
||||
{
|
||||
device.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(int sessionId, CommandList commands, int renderingLimit, ulong appletResourceId)
|
||||
{
|
||||
_sessionCommandList[sessionId] = new RendererSession
|
||||
{
|
||||
CommandList = commands,
|
||||
RenderingLimit = renderingLimit,
|
||||
AppletResourceId = appletResourceId
|
||||
};
|
||||
}
|
||||
|
||||
public bool HasRemainingCommands(int sessionId)
|
||||
{
|
||||
return _sessionCommandList[sessionId] != null;
|
||||
}
|
||||
|
||||
public void Signal()
|
||||
{
|
||||
_mailbox.SendMessage(MailboxMessage.RenderStart);
|
||||
}
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
if (_mailbox.ReceiveResponse() != MailboxMessage.RenderEnd)
|
||||
{
|
||||
throw new InvalidOperationException("Audio Processor Wait response was invalid!");
|
||||
}
|
||||
|
||||
long increment = Constants.AudioProcessorMaxUpdateTimeTarget;
|
||||
|
||||
long timeNow = PerformanceCounter.ElapsedNanoseconds;
|
||||
|
||||
if (timeNow > _playbackEnds)
|
||||
{
|
||||
// Playback has restarted.
|
||||
_playbackEnds = timeNow;
|
||||
}
|
||||
|
||||
_playbackEnds += increment;
|
||||
|
||||
// The number of frames we are behind where the timer says we should be.
|
||||
long framesBehind = (timeNow - _lastTime) / increment;
|
||||
|
||||
// The number of frames yet to play on the backend.
|
||||
long bufferedFrames = (_playbackEnds - timeNow) / increment + framesBehind;
|
||||
|
||||
// If we've entered a situation where a lot of buffers will be queued on the backend,
|
||||
// Skip some audio frames so that playback can catch up.
|
||||
if (bufferedFrames > MaxBufferedFrames)
|
||||
{
|
||||
// Skip a few frames so that we're not too far behind. (the target number of frames)
|
||||
_lastTime += increment * (bufferedFrames - TargetBufferedFrames);
|
||||
}
|
||||
|
||||
while (timeNow < _lastTime + increment)
|
||||
{
|
||||
_event.WaitOne(1);
|
||||
|
||||
timeNow = PerformanceCounter.ElapsedNanoseconds;
|
||||
}
|
||||
|
||||
_lastTime += increment;
|
||||
}
|
||||
|
||||
private void StartThread()
|
||||
{
|
||||
_workerThread = new Thread(Work)
|
||||
{
|
||||
Name = "AudioProcessor.Worker"
|
||||
};
|
||||
|
||||
_workerThread.Start();
|
||||
}
|
||||
|
||||
private void Work()
|
||||
{
|
||||
if (_mailbox.ReceiveMessage() != MailboxMessage.Start)
|
||||
{
|
||||
throw new InvalidOperationException("Audio Processor Start message was invalid!");
|
||||
}
|
||||
|
||||
_mailbox.SendResponse(MailboxMessage.Start);
|
||||
_mailbox.SendResponse(MailboxMessage.RenderEnd);
|
||||
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio processor");
|
||||
|
||||
while (true)
|
||||
{
|
||||
_pauseEvent?.WaitOne();
|
||||
|
||||
MailboxMessage message = _mailbox.ReceiveMessage();
|
||||
|
||||
if (message == MailboxMessage.Stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (message == MailboxMessage.RenderStart)
|
||||
{
|
||||
long startTicks = PerformanceCounter.ElapsedNanoseconds;
|
||||
|
||||
for (int i = 0; i < _sessionCommandList.Length; i++)
|
||||
{
|
||||
if (_sessionCommandList[i] != null)
|
||||
{
|
||||
_sessionCommandList[i].CommandList.Process(OutputDevices[i]);
|
||||
_sessionCommandList[i].CommandList.Dispose();
|
||||
_sessionCommandList[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
long endTicks = PerformanceCounter.ElapsedNanoseconds;
|
||||
|
||||
long elapsedTime = endTicks - startTicks;
|
||||
|
||||
if (Constants.AudioProcessorMaxUpdateTime < elapsedTime)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - Constants.AudioProcessorMaxUpdateTime}ns)");
|
||||
}
|
||||
|
||||
_mailbox.SendResponse(MailboxMessage.RenderEnd);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, "Stopping audio processor");
|
||||
_mailbox.SendResponse(MailboxMessage.Stop);
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
{
|
||||
if (OutputDevices != null)
|
||||
{
|
||||
foreach (IHardwareDevice outputDevice in OutputDevices)
|
||||
{
|
||||
if (outputDevice != null)
|
||||
{
|
||||
return outputDevice.GetVolume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
if (OutputDevices != null)
|
||||
{
|
||||
foreach (IHardwareDevice outputDevice in OutputDevices)
|
||||
{
|
||||
outputDevice?.SetVolume(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_event.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class CompressorCommand : ICommand
|
||||
{
|
||||
private const int FixedPointPrecision = 15;
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Compressor;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public CompressorParameter Parameter => _parameter;
|
||||
public Memory<CompressorState> State { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private CompressorParameter _parameter;
|
||||
|
||||
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
|
||||
IsEffectEnabled = isEnabled;
|
||||
|
||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
|
||||
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||
{
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref CompressorState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (_parameter.Status == Server.Effect.UsageState.Invalid)
|
||||
{
|
||||
state = new CompressorState(ref _parameter);
|
||||
}
|
||||
else if (_parameter.Status == Server.Effect.UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessCompressor(context, ref state);
|
||||
}
|
||||
|
||||
private unsafe void ProcessCompressor(CommandList context, ref CompressorState state)
|
||||
{
|
||||
Debug.Assert(_parameter.IsChannelCountValid());
|
||||
|
||||
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
||||
{
|
||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
|
||||
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
|
||||
float unknown4 = state.Unknown4;
|
||||
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
|
||||
float previousCompressionEmaAlpha = state.PreviousCompressionEmaAlpha;
|
||||
|
||||
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||
{
|
||||
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
||||
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
||||
}
|
||||
|
||||
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
||||
{
|
||||
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
||||
}
|
||||
|
||||
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
|
||||
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
|
||||
float z = 0.0f;
|
||||
|
||||
bool unknown10OutOfRange = false;
|
||||
|
||||
if (newMean < 1.0e-10f)
|
||||
{
|
||||
z = 1.0f;
|
||||
|
||||
unknown10OutOfRange = state.Unknown10 < -100.0f;
|
||||
}
|
||||
|
||||
if (y >= state.Unknown10 || unknown10OutOfRange)
|
||||
{
|
||||
float tmpGain;
|
||||
|
||||
if (y >= state.Unknown14)
|
||||
{
|
||||
tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
|
||||
}
|
||||
else
|
||||
{
|
||||
tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
|
||||
}
|
||||
|
||||
z = FloatingPointHelper.DecibelToLinearExtended(tmpGain);
|
||||
}
|
||||
|
||||
float unknown4New = z;
|
||||
float compressionEmaAlpha;
|
||||
|
||||
if ((unknown4 - z) <= 0.08f)
|
||||
{
|
||||
compressionEmaAlpha = Parameter.ReleaseCoefficient;
|
||||
|
||||
if ((unknown4 - z) >= -0.08f)
|
||||
{
|
||||
if (MathF.Abs(compressionGainAverage.Read() - z) >= 0.001f)
|
||||
{
|
||||
unknown4New = unknown4;
|
||||
}
|
||||
|
||||
compressionEmaAlpha = previousCompressionEmaAlpha;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
compressionEmaAlpha = Parameter.AttackCoefficient;
|
||||
}
|
||||
|
||||
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
|
||||
}
|
||||
|
||||
unknown4 = unknown4New;
|
||||
previousCompressionEmaAlpha = compressionEmaAlpha;
|
||||
}
|
||||
|
||||
state.InputMovingAverage = inputMovingAverage;
|
||||
state.Unknown4 = unknown4;
|
||||
state.CompressionGainAverage = compressionGainAverage;
|
||||
state.PreviousCompressionEmaAlpha = previousCompressionEmaAlpha;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
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
|
||||
{
|
||||
public class DelayCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Delay;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public DelayParameter Parameter => _parameter;
|
||||
public Memory<DelayState> State { get; }
|
||||
public ulong WorkBuffer { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private DelayParameter _parameter;
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
WorkBuffer = workBuffer;
|
||||
|
||||
IsEffectEnabled = isEnabled;
|
||||
|
||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||
}
|
||||
|
||||
DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 1;
|
||||
|
||||
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float input = inputBuffer[i] * 64;
|
||||
float delayLineValue = state.DelayLines[0].Read();
|
||||
|
||||
float temp = input * inGain + delayLineValue * feedbackGain;
|
||||
|
||||
state.UpdateLowPassFilter(ref temp, channelCount);
|
||||
|
||||
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayStereo(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 2;
|
||||
|
||||
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++)
|
||||
{
|
||||
Vector2 channelInput = new Vector2
|
||||
{
|
||||
X = *((float*)inputBuffers[0] + i) * 64,
|
||||
Y = *((float*)inputBuffers[1] + i) * 64,
|
||||
};
|
||||
|
||||
Vector2 delayLineValues = new Vector2()
|
||||
{
|
||||
X = state.DelayLines[0].Read(),
|
||||
Y = state.DelayLines[1].Read(),
|
||||
};
|
||||
|
||||
Vector2 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
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 | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayQuadraphonic(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 4;
|
||||
|
||||
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++)
|
||||
{
|
||||
Vector4 channelInput = new Vector4
|
||||
{
|
||||
X = *((float*)inputBuffers[0] + i) * 64,
|
||||
Y = *((float*)inputBuffers[1] + i) * 64,
|
||||
Z = *((float*)inputBuffers[2] + i) * 64,
|
||||
W = *((float*)inputBuffers[3] + i) * 64
|
||||
};
|
||||
|
||||
Vector4 delayLineValues = new Vector4()
|
||||
{
|
||||
X = state.DelayLines[0].Read(),
|
||||
Y = state.DelayLines[1].Read(),
|
||||
Z = state.DelayLines[2].Read(),
|
||||
W = state.DelayLines[3].Read()
|
||||
};
|
||||
|
||||
Vector4 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, 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;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelaySurround(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 6;
|
||||
|
||||
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, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
|
||||
0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
|
||||
delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
|
||||
delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
|
||||
0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
Vector6 channelInput = new Vector6
|
||||
{
|
||||
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
|
||||
};
|
||||
|
||||
Vector6 delayLineValues = new Vector6
|
||||
{
|
||||
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()
|
||||
};
|
||||
|
||||
Vector6 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void ProcessDelay(CommandList context, ref DelayState state)
|
||||
{
|
||||
Debug.Assert(Parameter.IsChannelCountValid());
|
||||
|
||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
||||
{
|
||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
||||
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
||||
}
|
||||
|
||||
switch (Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
ProcessDelayMono(ref state, (float*)outputBuffers[0], (float*)inputBuffers[0], context.SampleCount);
|
||||
break;
|
||||
case 2:
|
||||
ProcessDelayStereo(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 4:
|
||||
ProcessDelayQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 6:
|
||||
ProcessDelaySurround(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(Parameter.ChannelCount.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (Parameter.Status == UsageState.Invalid)
|
||||
{
|
||||
state = new DelayState(ref _parameter, WorkBuffer);
|
||||
}
|
||||
else if (Parameter.Status == UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessDelay(context, ref state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Audio.Renderer.Server.Sink;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DeviceSinkCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.DeviceSink;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public string DeviceName { get; }
|
||||
|
||||
public int SessionId { get; }
|
||||
|
||||
public uint InputCount { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
|
||||
public Memory<float> Buffers { get; }
|
||||
|
||||
public DeviceSinkCommand(uint bufferOffset, DeviceSink sink, int sessionId, Memory<float> buffers, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0');
|
||||
SessionId = sessionId;
|
||||
InputCount = sink.Parameter.InputCount;
|
||||
InputBufferIndices = new ushort[InputCount];
|
||||
|
||||
for (int i = 0; i < Math.Min(InputCount, Constants.ChannelCountMax); i++)
|
||||
{
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + sink.Parameter.Input[i]);
|
||||
}
|
||||
|
||||
if (sink.UpsamplerState != null)
|
||||
{
|
||||
Buffers = sink.UpsamplerState.OutputBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffers = buffers;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Span<float> GetBuffer(int index, int sampleCount)
|
||||
{
|
||||
return Buffers.Span.Slice(index * sampleCount, sampleCount);
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
IHardwareDevice device = context.OutputDevice;
|
||||
|
||||
if (device.GetSampleRate() == Constants.TargetSampleRate)
|
||||
{
|
||||
int channelCount = (int)device.GetChannelCount();
|
||||
uint bufferCount = Math.Min(device.GetChannelCount(), InputCount);
|
||||
|
||||
const int sampleCount = Constants.TargetSampleCount;
|
||||
|
||||
short[] outputBuffer = new short[bufferCount * sampleCount];
|
||||
|
||||
for (int i = 0; i < bufferCount; i++)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = GetBuffer(InputBufferIndices[i], sampleCount);
|
||||
|
||||
for (int j = 0; j < sampleCount; j++)
|
||||
{
|
||||
outputBuffer[i + j * channelCount] = PcmHelper.Saturate(inputBuffer[j]);
|
||||
}
|
||||
}
|
||||
|
||||
device.AppendBuffer(outputBuffer, InputCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: support resampling for device only supporting something different
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class Reverb3dCommand : ICommand
|
||||
{
|
||||
private static readonly int[] OutputEarlyIndicesTableMono = new int[20] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[1] { 0 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableStereo = new int[20] { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[2] { 0, 1 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[20] { 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableSurround = new int[40] { 4, 5, 0, 5, 0, 5, 1, 5, 1, 5, 1, 5, 1, 5, 2, 5, 2, 5, 2, 5, 1, 5, 1, 5, 1, 5, 0, 5, 0, 5, 0, 5, 0, 5, 3, 5, 3, 5, 3, 5 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[40] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[6] { 0, 1, 2, 3, -1, 3 };
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Reverb3d;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public Reverb3dParameter Parameter => _parameter;
|
||||
public Memory<Reverb3dState> State { get; }
|
||||
public ulong WorkBuffer { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private Reverb3dParameter _parameter;
|
||||
|
||||
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
WorkBuffer = workBuffer;
|
||||
|
||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
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)]
|
||||
private void ProcessReverb3dMono(ref Reverb3dState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableMono, TargetEarlyDelayLineIndicesTableMono, TargetOutputFeedbackIndicesTableMono);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessReverb3dStereo(ref Reverb3dState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableStereo, TargetEarlyDelayLineIndicesTableStereo, TargetOutputFeedbackIndicesTableStereo);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessReverb3dQuadraphonic(ref Reverb3dState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableQuadraphonic, TargetEarlyDelayLineIndicesTableQuadraphonic, TargetOutputFeedbackIndicesTableQuadraphonic);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessReverb3dSurround(ref Reverb3dState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableSurround, TargetEarlyDelayLineIndicesTableSurround, TargetOutputFeedbackIndicesTableSurround);
|
||||
}
|
||||
|
||||
private unsafe void ProcessReverb3dGeneric(ref Reverb3dState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable)
|
||||
{
|
||||
const int delayLineSampleIndexOffset = 1;
|
||||
|
||||
bool isMono = Parameter.ChannelCount == 1;
|
||||
bool isSurround = Parameter.ChannelCount == 6;
|
||||
|
||||
Span<float> outputValues = stackalloc float[Constants.ChannelCountMax];
|
||||
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
|
||||
Span<float> feedbackValues = stackalloc float[4];
|
||||
Span<float> feedbackOutputValues = stackalloc float[4];
|
||||
Span<float> values = stackalloc float[4];
|
||||
|
||||
for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
|
||||
{
|
||||
outputValues.Fill(0);
|
||||
|
||||
float tapOut = state.PreDelayLine.TapUnsafe(state.ReflectionDelayTime, delayLineSampleIndexOffset);
|
||||
|
||||
for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++)
|
||||
{
|
||||
int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i];
|
||||
int outputIndex = outputEarlyIndicesTable[i];
|
||||
|
||||
float tempTapOut = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], delayLineSampleIndexOffset);
|
||||
|
||||
outputValues[outputIndex] += tempTapOut * state.EarlyGain[earlyDelayIndex];
|
||||
}
|
||||
|
||||
float targetPreDelayValue = 0;
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
||||
targetPreDelayValue += channelInput[channelIndex];
|
||||
}
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
outputValues[i] *= state.EarlyReflectionsGain;
|
||||
}
|
||||
|
||||
state.PreviousPreDelayValue = (targetPreDelayValue * state.TargetPreDelayGain) + (state.PreviousPreDelayValue * state.PreviousPreDelayGain);
|
||||
|
||||
state.PreDelayLine.Update(state.PreviousPreDelayValue);
|
||||
|
||||
for (int i = 0; i < state.FdnDelayLines.Length; i++)
|
||||
{
|
||||
float fdnValue = state.FdnDelayLines[i].Read();
|
||||
|
||||
float feedbackOutputValue = fdnValue * state.DecayDirectFdnGain[i] + state.PreviousFeedbackOutputDecayed[i];
|
||||
|
||||
state.PreviousFeedbackOutputDecayed[i] = (fdnValue * state.DecayCurrentFdnGain[i]) + (feedbackOutputValue * state.DecayCurrentOutputGain[i]);
|
||||
|
||||
feedbackOutputValues[i] = feedbackOutputValue;
|
||||
}
|
||||
|
||||
feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1];
|
||||
feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3];
|
||||
feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3];
|
||||
feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2];
|
||||
|
||||
for (int i = 0; i < state.DecayDelays1.Length; i++)
|
||||
{
|
||||
float temp = state.DecayDelays1[i].Update(tapOut * state.LateReverbGain + feedbackValues[i]);
|
||||
|
||||
values[i] = state.DecayDelays2[i].Update(temp);
|
||||
|
||||
state.FdnDelayLines[i].Update(values[i]);
|
||||
}
|
||||
|
||||
for (int channelIndex = 0; channelIndex < targetOutputFeedbackIndicesTable.Length; channelIndex++)
|
||||
{
|
||||
int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[channelIndex];
|
||||
|
||||
if (targetOutputFeedbackIndex >= 0)
|
||||
{
|
||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = (outputValues[channelIndex] + values[targetOutputFeedbackIndex] + channelInput[channelIndex] * state.DryGain);
|
||||
}
|
||||
}
|
||||
|
||||
if (isMono)
|
||||
{
|
||||
*((float*)outputBuffers[0] + sampleIndex) += values[1];
|
||||
}
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
*((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.FrontCenterDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessReverb3d(CommandList context, ref Reverb3dState state)
|
||||
{
|
||||
Debug.Assert(Parameter.IsChannelCountValid());
|
||||
|
||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
||||
{
|
||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
||||
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
||||
}
|
||||
|
||||
switch (Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
ProcessReverb3dMono(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 2:
|
||||
ProcessReverb3dStereo(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 4:
|
||||
ProcessReverb3dQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 6:
|
||||
ProcessReverb3dSurround(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(Parameter.ChannelCount.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref Reverb3dState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (Parameter.ParameterStatus == UsageState.Invalid)
|
||||
{
|
||||
state = new Reverb3dState(ref _parameter, WorkBuffer);
|
||||
}
|
||||
else if (Parameter.ParameterStatus == UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessReverb3d(context, ref state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class ReverbCommand : ICommand
|
||||
{
|
||||
private static readonly int[] OutputEarlyIndicesTableMono = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private static readonly int[] OutputIndicesTableMono = new int[4] { 0, 0, 0, 0 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[4] { 0, 1, 2, 3 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableStereo = new int[10] { 0, 0, 1, 1, 0, 1, 0, 0, 1, 1 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private static readonly int[] OutputIndicesTableStereo = new int[4] { 0, 0, 1, 1 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[4] { 2, 0, 3, 1 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[10] { 0, 0, 1, 1, 0, 1, 2, 2, 3, 3 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private static readonly int[] OutputIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableSurround = new int[20] { 0, 5, 0, 5, 1, 5, 1, 5, 4, 5, 4, 5, 2, 5, 2, 5, 3, 5, 3, 5 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[20] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 };
|
||||
private static readonly int[] OutputIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, -1, 3 };
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Reverb;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ReverbParameter Parameter => _parameter;
|
||||
public Memory<ReverbState> State { get; }
|
||||
public ulong WorkBuffer { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public bool IsLongSizePreDelaySupported { get; }
|
||||
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private ReverbParameter _parameter;
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
WorkBuffer = workBuffer;
|
||||
|
||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||
}
|
||||
|
||||
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)]
|
||||
private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(ref state,
|
||||
outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableMono,
|
||||
TargetEarlyDelayLineIndicesTableMono,
|
||||
TargetOutputFeedbackIndicesTableMono,
|
||||
OutputIndicesTableMono);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(ref state,
|
||||
outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableStereo,
|
||||
TargetEarlyDelayLineIndicesTableStereo,
|
||||
TargetOutputFeedbackIndicesTableStereo,
|
||||
OutputIndicesTableStereo);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(ref state,
|
||||
outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableQuadraphonic,
|
||||
TargetEarlyDelayLineIndicesTableQuadraphonic,
|
||||
TargetOutputFeedbackIndicesTableQuadraphonic,
|
||||
OutputIndicesTableQuadraphonic);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(ref state,
|
||||
outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableSurround,
|
||||
TargetEarlyDelayLineIndicesTableSurround,
|
||||
TargetOutputFeedbackIndicesTableSurround,
|
||||
OutputIndicesTableSurround);
|
||||
}
|
||||
|
||||
private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable, ReadOnlySpan<int> outputIndicesTable)
|
||||
{
|
||||
bool isSurround = Parameter.ChannelCount == 6;
|
||||
|
||||
float reverbGain = FixedPointHelper.ToFloat(Parameter.ReverbGain, FixedPointPrecision);
|
||||
float lateGain = FixedPointHelper.ToFloat(Parameter.LateGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
|
||||
Span<float> outputValues = stackalloc float[Constants.ChannelCountMax];
|
||||
Span<float> feedbackValues = stackalloc float[4];
|
||||
Span<float> feedbackOutputValues = stackalloc float[4];
|
||||
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
|
||||
|
||||
for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
|
||||
{
|
||||
outputValues.Fill(0);
|
||||
|
||||
for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++)
|
||||
{
|
||||
int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i];
|
||||
int outputIndex = outputEarlyIndicesTable[i];
|
||||
|
||||
float tapOutput = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], 0);
|
||||
|
||||
outputValues[outputIndex] += tapOutput * state.EarlyGain[earlyDelayIndex];
|
||||
}
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
outputValues[5] *= 0.2f;
|
||||
}
|
||||
|
||||
float targetPreDelayValue = 0;
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex) * 64;
|
||||
targetPreDelayValue += channelInput[channelIndex] * reverbGain;
|
||||
}
|
||||
|
||||
state.PreDelayLine.Update(targetPreDelayValue);
|
||||
|
||||
float lateValue = state.PreDelayLine.Tap(state.PreDelayLineDelayTime) * lateGain;
|
||||
|
||||
for (int i = 0; i < state.FdnDelayLines.Length; i++)
|
||||
{
|
||||
feedbackOutputValues[i] = state.FdnDelayLines[i].Read() * state.HighFrequencyDecayDirectGain[i] + state.PreviousFeedbackOutput[i] * state.HighFrequencyDecayPreviousGain[i];
|
||||
state.PreviousFeedbackOutput[i] = feedbackOutputValues[i];
|
||||
}
|
||||
|
||||
feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1];
|
||||
feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3];
|
||||
feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3];
|
||||
feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2];
|
||||
|
||||
for (int i = 0; i < state.FdnDelayLines.Length; i++)
|
||||
{
|
||||
feedbackOutputValues[i] = state.DecayDelays[i].Update(feedbackValues[i] + lateValue);
|
||||
state.FdnDelayLines[i].Update(feedbackOutputValues[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < targetOutputFeedbackIndicesTable.Length; i++)
|
||||
{
|
||||
int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[i];
|
||||
int outputIndex = outputIndicesTable[i];
|
||||
|
||||
if (targetOutputFeedbackIndex >= 0)
|
||||
{
|
||||
outputValues[outputIndex] += feedbackOutputValues[targetOutputFeedbackIndex];
|
||||
}
|
||||
}
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
outputValues[4] += state.FrontCenterDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
|
||||
}
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = (outputValues[channelIndex] * outGain + channelInput[channelIndex] * dryGain) / 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessReverb(CommandList context, ref ReverbState state)
|
||||
{
|
||||
Debug.Assert(Parameter.IsChannelCountValid());
|
||||
|
||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
||||
{
|
||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
||||
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
||||
}
|
||||
|
||||
switch (Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
ProcessReverbMono(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 2:
|
||||
ProcessReverbStereo(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 4:
|
||||
ProcessReverbQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 6:
|
||||
ProcessReverbSurround(ref state, outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(Parameter.ChannelCount.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref ReverbState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (Parameter.Status == Server.Effect.UsageState.Invalid)
|
||||
{
|
||||
state = new ReverbState(ref _parameter, WorkBuffer, IsLongSizePreDelaySupported);
|
||||
}
|
||||
else if (Parameter.Status == Server.Effect.UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessReverb(context, ref state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using Ryujinx.Audio.Renderer.Server.Upsampler;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class UpsampleCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Upsample;
|
||||
|
||||
public uint EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint BufferCount { get; }
|
||||
public uint InputBufferIndex { get; }
|
||||
public uint InputSampleCount { get; }
|
||||
public uint InputSampleRate { get; }
|
||||
|
||||
public UpsamplerState UpsamplerInfo { get; }
|
||||
|
||||
public Memory<float> OutBuffer { get; }
|
||||
|
||||
public UpsampleCommand(uint bufferOffset, UpsamplerState info, uint inputCount, Span<byte> inputBufferOffset, uint bufferCount, uint sampleCount, uint sampleRate, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = 0;
|
||||
OutBuffer = info.OutputBuffer;
|
||||
BufferCount = bufferCount;
|
||||
InputSampleCount = sampleCount;
|
||||
InputSampleRate = sampleRate;
|
||||
info.SourceSampleCount = inputCount;
|
||||
info.InputBufferIndices = new ushort[inputCount];
|
||||
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
|
||||
}
|
||||
|
||||
UpsamplerInfo = info;
|
||||
}
|
||||
|
||||
private Span<float> GetBuffer(int index, int sampleCount)
|
||||
{
|
||||
return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount);
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
float ratio = (float)InputSampleRate / Constants.TargetSampleRate;
|
||||
|
||||
uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
|
||||
|
||||
for (int i = 0; i < bufferCount; i++)
|
||||
{
|
||||
Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
|
||||
Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
|
||||
|
||||
float fraction = 0.0f;
|
||||
|
||||
ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,466 +0,0 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public static class DataSourceHelper
|
||||
{
|
||||
private const int FixedPointPrecision = 15;
|
||||
|
||||
public struct WaveBufferInformation
|
||||
{
|
||||
public uint SourceSampleRate;
|
||||
public float Pitch;
|
||||
public ulong ExtraParameter;
|
||||
public ulong ExtraParameterSize;
|
||||
public int ChannelIndex;
|
||||
public int ChannelCount;
|
||||
public DecodingBehaviour DecodingBehaviour;
|
||||
public SampleRateConversionQuality SrcQuality;
|
||||
public SampleFormat SampleFormat;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetPitchLimitBySrcQuality(SampleRateConversionQuality quality)
|
||||
{
|
||||
return quality switch
|
||||
{
|
||||
SampleRateConversionQuality.Default or SampleRateConversionQuality.Low => 4,
|
||||
SampleRateConversionQuality.High => 8,
|
||||
_ => throw new ArgumentException(quality.ToString()),
|
||||
};
|
||||
}
|
||||
|
||||
public static void ProcessWaveBuffers(IVirtualMemoryManager memoryManager, Span<float> outputBuffer, ref WaveBufferInformation info, Span<WaveBuffer> wavebuffers, ref VoiceUpdateState voiceState, uint targetSampleRate, int sampleCount)
|
||||
{
|
||||
const int tempBufferSize = 0x3F00;
|
||||
|
||||
Span<short> tempBuffer = stackalloc short[tempBufferSize];
|
||||
|
||||
float sampleRateRatio = (float)info.SourceSampleRate / targetSampleRate * info.Pitch;
|
||||
|
||||
float fraction = voiceState.Fraction;
|
||||
int waveBufferIndex = (int)voiceState.WaveBufferIndex;
|
||||
ulong playedSampleCount = voiceState.PlayedSampleCount;
|
||||
int offset = voiceState.Offset;
|
||||
uint waveBufferConsumed = voiceState.WaveBufferConsumed;
|
||||
|
||||
int pitchMaxLength = GetPitchLimitBySrcQuality(info.SrcQuality);
|
||||
|
||||
int totalNeededSize = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCount);
|
||||
|
||||
if (totalNeededSize + pitchMaxLength <= tempBufferSize && totalNeededSize >= 0)
|
||||
{
|
||||
int sourceSampleCountToProcess = sampleCount;
|
||||
|
||||
int maxSampleCountPerIteration = Math.Min((int)MathF.Truncate((tempBufferSize - fraction) / sampleRateRatio), sampleCount);
|
||||
|
||||
bool isStarving = false;
|
||||
|
||||
int i = 0;
|
||||
|
||||
while (i < sourceSampleCountToProcess)
|
||||
{
|
||||
int tempBufferIndex = 0;
|
||||
|
||||
if (!info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion))
|
||||
{
|
||||
voiceState.Pitch.AsSpan().Slice(0, pitchMaxLength).CopyTo(tempBuffer);
|
||||
tempBufferIndex += pitchMaxLength;
|
||||
}
|
||||
|
||||
int sampleCountToProcess = Math.Min(sourceSampleCountToProcess, maxSampleCountPerIteration);
|
||||
|
||||
int y = 0;
|
||||
|
||||
int sampleCountToDecode = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCountToProcess);
|
||||
|
||||
while (y < sampleCountToDecode)
|
||||
{
|
||||
if (waveBufferIndex >= Constants.VoiceWaveBufferCount)
|
||||
{
|
||||
waveBufferIndex = 0;
|
||||
playedSampleCount = 0;
|
||||
}
|
||||
|
||||
if (!voiceState.IsWaveBufferValid[waveBufferIndex])
|
||||
{
|
||||
isStarving = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ref WaveBuffer waveBuffer = ref wavebuffers[waveBufferIndex];
|
||||
|
||||
if (offset == 0 && info.SampleFormat == SampleFormat.Adpcm && waveBuffer.Context != 0)
|
||||
{
|
||||
voiceState.LoopContext = memoryManager.Read<AdpcmLoopContext>(waveBuffer.Context);
|
||||
}
|
||||
|
||||
Span<short> tempSpan = tempBuffer.Slice(tempBufferIndex + y);
|
||||
|
||||
int decodedSampleCount = -1;
|
||||
|
||||
int targetSampleStartOffset;
|
||||
int targetSampleEndOffset;
|
||||
|
||||
if (voiceState.LoopCount > 0 && waveBuffer.LoopStartSampleOffset != 0 && waveBuffer.LoopEndSampleOffset != 0 && waveBuffer.LoopStartSampleOffset <= waveBuffer.LoopEndSampleOffset)
|
||||
{
|
||||
targetSampleStartOffset = (int)waveBuffer.LoopStartSampleOffset;
|
||||
targetSampleEndOffset = (int)waveBuffer.LoopEndSampleOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetSampleStartOffset = (int)waveBuffer.StartSampleOffset;
|
||||
targetSampleEndOffset = (int)waveBuffer.EndSampleOffset;
|
||||
}
|
||||
|
||||
int targetWaveBufferSampleCount = targetSampleEndOffset - targetSampleStartOffset;
|
||||
|
||||
switch (info.SampleFormat)
|
||||
{
|
||||
case SampleFormat.Adpcm:
|
||||
ReadOnlySpan<byte> waveBufferAdpcm = ReadOnlySpan<byte>.Empty;
|
||||
|
||||
if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
|
||||
{
|
||||
// TODO: we are possibly copying a lot of unneeded data here, we should only take what we need.
|
||||
waveBufferAdpcm = memoryManager.GetSpan(waveBuffer.Buffer, (int)waveBuffer.BufferSize);
|
||||
}
|
||||
|
||||
ReadOnlySpan<short> coefficients = MemoryMarshal.Cast<byte, short>(memoryManager.GetSpan(info.ExtraParameter, (int)info.ExtraParameterSize));
|
||||
decodedSampleCount = AdpcmHelper.Decode(tempSpan, waveBufferAdpcm, targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y, coefficients, ref voiceState.LoopContext);
|
||||
break;
|
||||
case SampleFormat.PcmInt16:
|
||||
ReadOnlySpan<short> waveBufferPcm16 = ReadOnlySpan<short>.Empty;
|
||||
|
||||
if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
|
||||
{
|
||||
ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset<short>(targetSampleStartOffset, offset, info.ChannelCount);
|
||||
int bufferSize = PcmHelper.GetBufferSize<short>(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount;
|
||||
|
||||
waveBufferPcm16 = MemoryMarshal.Cast<byte, short>(memoryManager.GetSpan(bufferOffset, bufferSize));
|
||||
}
|
||||
|
||||
decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcm16, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount);
|
||||
break;
|
||||
case SampleFormat.PcmFloat:
|
||||
ReadOnlySpan<float> waveBufferPcmFloat = ReadOnlySpan<float>.Empty;
|
||||
|
||||
if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0)
|
||||
{
|
||||
ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset<float>(targetSampleStartOffset, offset, info.ChannelCount);
|
||||
int bufferSize = PcmHelper.GetBufferSize<float>(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount;
|
||||
|
||||
waveBufferPcmFloat = MemoryMarshal.Cast<byte, float>(memoryManager.GetSpan(bufferOffset, bufferSize));
|
||||
}
|
||||
|
||||
decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcmFloat, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount);
|
||||
break;
|
||||
default:
|
||||
Logger.Error?.Print(LogClass.AudioRenderer, $"Unsupported sample format " + info.SampleFormat);
|
||||
break;
|
||||
}
|
||||
|
||||
Debug.Assert(decodedSampleCount <= sampleCountToDecode);
|
||||
|
||||
if (decodedSampleCount < 0)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.AudioRenderer, "Decoding failed, skipping WaveBuffer");
|
||||
|
||||
voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
|
||||
decodedSampleCount = 0;
|
||||
}
|
||||
|
||||
y += decodedSampleCount;
|
||||
offset += decodedSampleCount;
|
||||
playedSampleCount += (uint)decodedSampleCount;
|
||||
|
||||
if (offset >= targetWaveBufferSampleCount || decodedSampleCount == 0)
|
||||
{
|
||||
offset = 0;
|
||||
|
||||
if (waveBuffer.Looping)
|
||||
{
|
||||
voiceState.LoopCount++;
|
||||
|
||||
if (waveBuffer.LoopCount >= 0)
|
||||
{
|
||||
if (decodedSampleCount == 0 || voiceState.LoopCount > waveBuffer.LoopCount)
|
||||
{
|
||||
voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (decodedSampleCount == 0)
|
||||
{
|
||||
isStarving = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.PlayedSampleCountResetWhenLooping))
|
||||
{
|
||||
playedSampleCount = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Span<int> outputSpanInt = MemoryMarshal.Cast<float, int>(outputBuffer.Slice(i));
|
||||
|
||||
if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion))
|
||||
{
|
||||
for (int j = 0; j < y; j++)
|
||||
{
|
||||
outputBuffer[j] = tempBuffer[j];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<short> tempSpan = tempBuffer.Slice(tempBufferIndex + y);
|
||||
|
||||
tempSpan.Slice(0, sampleCountToDecode - y).Fill(0);
|
||||
|
||||
ToFloat(outputBuffer, outputSpanInt, sampleCountToProcess);
|
||||
|
||||
ResamplerHelper.Resample(outputBuffer, tempBuffer, sampleRateRatio, ref fraction, sampleCountToProcess, info.SrcQuality, y != sourceSampleCountToProcess || info.Pitch != 1.0f);
|
||||
|
||||
tempBuffer.Slice(sampleCountToDecode, pitchMaxLength).CopyTo(voiceState.Pitch.AsSpan());
|
||||
}
|
||||
|
||||
i += sampleCountToProcess;
|
||||
}
|
||||
|
||||
Debug.Assert(sourceSampleCountToProcess == i || !isStarving);
|
||||
|
||||
voiceState.WaveBufferConsumed = waveBufferConsumed;
|
||||
voiceState.Offset = offset;
|
||||
voiceState.PlayedSampleCount = playedSampleCount;
|
||||
voiceState.WaveBufferIndex = (uint)waveBufferIndex;
|
||||
voiceState.Fraction = fraction;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ToFloatAvx(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector256<int>> inputVec = MemoryMarshal.Cast<int, Vector256<int>>(input);
|
||||
Span<Vector256<float>> outputVec = MemoryMarshal.Cast<float, Vector256<float>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 8;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Avx.ConvertToVector256Single(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ToFloatSse2(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector128<int>> inputVec = MemoryMarshal.Cast<int, Vector128<int>>(input);
|
||||
Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Sse2.ConvertToVector128Single(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ToFloatAdvSimd(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector128<int>> inputVec = MemoryMarshal.Cast<int, Vector128<int>>(input);
|
||||
Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = AdvSimd.ConvertToSingle(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToFloatSlow(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToFloat(Span<float> output, ReadOnlySpan<int> input, int sampleCount)
|
||||
{
|
||||
if (Avx.IsSupported)
|
||||
{
|
||||
ToFloatAvx(output, input, sampleCount);
|
||||
}
|
||||
else if (Sse2.IsSupported)
|
||||
{
|
||||
ToFloatSse2(output, input, sampleCount);
|
||||
}
|
||||
else if (AdvSimd.IsSupported)
|
||||
{
|
||||
ToFloatAdvSimd(output, input, sampleCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToFloatSlow(output, input, sampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToIntAvx(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector256<float>> inputVec = MemoryMarshal.Cast<float, Vector256<float>>(input);
|
||||
Span<Vector256<int>> outputVec = MemoryMarshal.Cast<int, Vector256<int>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 8;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Avx.ConvertToVector256Int32(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = (int)input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToIntSse2(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(input);
|
||||
Span<Vector128<int>> outputVec = MemoryMarshal.Cast<int, Vector128<int>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Sse2.ConvertToVector128Int32(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = (int)input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToIntAdvSimd(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(input);
|
||||
Span<Vector128<int>> outputVec = MemoryMarshal.Cast<int, Vector128<int>>(output);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = AdvSimd.ConvertToInt32RoundToZero(inputVec[i]);
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = (int)input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToIntSlow(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
output[i] = (int)input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToInt(Span<int> output, ReadOnlySpan<float> input, int sampleCount)
|
||||
{
|
||||
if (Avx.IsSupported)
|
||||
{
|
||||
ToIntAvx(output, input, sampleCount);
|
||||
}
|
||||
else if (Sse2.IsSupported)
|
||||
{
|
||||
ToIntSse2(output, input, sampleCount);
|
||||
}
|
||||
else if (AdvSimd.IsSupported)
|
||||
{
|
||||
ToIntAdvSimd(output, input, sampleCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public static class PcmHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetCountToDecode(int startSampleOffset, int endSampleOffset, int offset, int count)
|
||||
{
|
||||
return Math.Min(count, endSampleOffset - startSampleOffset - offset);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong GetBufferOffset<T>(int startSampleOffset, int offset, int channelCount) where T : unmanaged
|
||||
{
|
||||
return (ulong)(Unsafe.SizeOf<T>() * channelCount * (startSampleOffset + offset));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetBufferSize<T>(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged
|
||||
{
|
||||
return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf<T>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float ConvertSampleToPcmFloat(short sample)
|
||||
{
|
||||
return (float)sample / short.MaxValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static short ConvertSampleToPcmInt16(float sample)
|
||||
{
|
||||
return Saturate(sample * short.MaxValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TOutput ConvertSample<TInput, TOutput>(TInput value) where TInput: INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
|
||||
{
|
||||
TInput conversionRate = TInput.CreateSaturating(TOutput.MaxValue / TOutput.CreateSaturating(TInput.MaxValue));
|
||||
|
||||
return TOutput.CreateSaturating(value * conversionRate);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Convert<TInput, TOutput>(Span<TOutput> output, ReadOnlySpan<TInput> input) where TInput : INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
|
||||
{
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
output[i] = ConvertSample<TInput, TOutput>(input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ConvertSampleToPcmFloat(Span<float> output, ReadOnlySpan<short> input)
|
||||
{
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
output[i] = ConvertSampleToPcmFloat(input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Decode(Span<short> output, ReadOnlySpan<short> input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount)
|
||||
{
|
||||
if (input.IsEmpty || endSampleOffset < startSampleOffset)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int decodedCount = input.Length / channelCount;
|
||||
|
||||
for (int i = 0; i < decodedCount; i++)
|
||||
{
|
||||
output[i] = input[i * channelCount + channelIndex];
|
||||
}
|
||||
|
||||
return decodedCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Decode(Span<short> output, ReadOnlySpan<float> input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount)
|
||||
{
|
||||
if (input.IsEmpty || endSampleOffset < startSampleOffset)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int decodedCount = input.Length / channelCount;
|
||||
|
||||
for (int i = 0; i < decodedCount; i++)
|
||||
{
|
||||
output[i] = ConvertSampleToPcmInt16(input[i * channelCount + channelIndex]);
|
||||
}
|
||||
|
||||
return decodedCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static short Saturate(float value)
|
||||
{
|
||||
if (value > short.MaxValue)
|
||||
{
|
||||
return short.MaxValue;
|
||||
}
|
||||
|
||||
if (value < short.MinValue)
|
||||
{
|
||||
return short.MinValue;
|
||||
}
|
||||
|
||||
return (short)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,630 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
public static class ResamplerHelper
|
||||
{
|
||||
#region "Default Quality Lookup Tables"
|
||||
private static short[] _normalCurveLut0 = new short[]
|
||||
{
|
||||
6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22,
|
||||
6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48,
|
||||
5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77,
|
||||
5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109,
|
||||
4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147,
|
||||
4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190,
|
||||
3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241,
|
||||
3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300,
|
||||
3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369,
|
||||
2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449,
|
||||
2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541,
|
||||
2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647,
|
||||
2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
|
||||
1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901,
|
||||
1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052,
|
||||
1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221,
|
||||
1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407,
|
||||
1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613,
|
||||
901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838,
|
||||
766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083,
|
||||
647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348,
|
||||
541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635,
|
||||
449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942,
|
||||
369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269,
|
||||
300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618,
|
||||
241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
|
||||
190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377,
|
||||
147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785,
|
||||
109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213,
|
||||
77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659,
|
||||
48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121,
|
||||
22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600
|
||||
};
|
||||
|
||||
private static short[] _normalCurveLut1 = new short[]
|
||||
{
|
||||
-68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36,
|
||||
-568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80,
|
||||
-990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128,
|
||||
-1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180,
|
||||
-1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240,
|
||||
-1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307,
|
||||
-1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382,
|
||||
-2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468,
|
||||
-2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563,
|
||||
-2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668,
|
||||
-2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783,
|
||||
-2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907,
|
||||
-1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
|
||||
-1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176,
|
||||
-1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317,
|
||||
-1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459,
|
||||
-1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599,
|
||||
-1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732,
|
||||
-1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855,
|
||||
-1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962,
|
||||
-907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049,
|
||||
-783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111,
|
||||
-668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141,
|
||||
-563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133,
|
||||
-468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083,
|
||||
-382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
|
||||
-307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830,
|
||||
-240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617,
|
||||
-180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338,
|
||||
-128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990,
|
||||
-80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568,
|
||||
-36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68
|
||||
};
|
||||
|
||||
private static short[] _normalCurveLut2 = new short[]
|
||||
{
|
||||
3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42,
|
||||
2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58,
|
||||
2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76,
|
||||
1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98,
|
||||
1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121,
|
||||
1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146,
|
||||
829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174,
|
||||
583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202,
|
||||
371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230,
|
||||
194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258,
|
||||
47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283,
|
||||
-71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306,
|
||||
-163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
|
||||
-234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337,
|
||||
-284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341,
|
||||
-317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336,
|
||||
-336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317,
|
||||
-341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284,
|
||||
-337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234,
|
||||
-325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163,
|
||||
-306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71,
|
||||
-283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47,
|
||||
-258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194,
|
||||
-230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371,
|
||||
-202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583,
|
||||
-174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
|
||||
-146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115,
|
||||
-121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442,
|
||||
-98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813,
|
||||
-76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227,
|
||||
-58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688,
|
||||
-42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region "High Quality Lookup Tables"
|
||||
private static short[] _highCurveLut0 = new short[]
|
||||
{
|
||||
-582, -23, 8740, 16386, 8833, 8, -590, 0, -573, -54, 8647, 16385, 8925, 40, -598, -1,
|
||||
-565, -84, 8555, 16383, 9018, 72, -606, -1, -557, -113, 8462, 16379, 9110, 105, -614, -2,
|
||||
-549, -142, 8370, 16375, 9203, 139, -622, -2, -541, -170, 8277, 16369, 9295, 173, -630, -3,
|
||||
-533, -198, 8185, 16362, 9387, 208, -638, -4, -525, -225, 8093, 16354, 9480, 244, -646, -5,
|
||||
-516, -251, 8000, 16344, 9572, 280, -654, -5, -508, -277, 7908, 16334, 9664, 317, -662, -6,
|
||||
-500, -302, 7816, 16322, 9756, 355, -670, -7, -492, -327, 7724, 16310, 9847, 393, -678, -8,
|
||||
-484, -351, 7632, 16296, 9939, 432, -686, -9, -476, -374, 7540, 16281, 10030, 471, -694, -10,
|
||||
-468, -397, 7449, 16265, 10121, 511, -702, -11, -460, -419, 7357, 16247, 10212, 552, -709, -13,
|
||||
-452, -441, 7266, 16229, 10303, 593, -717, -14, -445, -462, 7175, 16209, 10394, 635, -724, -15,
|
||||
-437, -483, 7084, 16189, 10484, 678, -732, -16, -429, -503, 6994, 16167, 10574, 722, -739, -18,
|
||||
-421, -523, 6903, 16144, 10664, 766, -747, -19, -414, -542, 6813, 16120, 10754, 810, -754, -21,
|
||||
-406, -560, 6723, 16095, 10843, 856, -761, -22, -398, -578, 6633, 16068, 10932, 902, -768, -24,
|
||||
-391, -596, 6544, 16041, 11021, 949, -775, -26, -383, -612, 6454, 16012, 11109, 996, -782, -27,
|
||||
-376, -629, 6366, 15983, 11197, 1044, -789, -29, -368, -645, 6277, 15952, 11285, 1093, -796, -31,
|
||||
-361, -660, 6189, 15920, 11372, 1142, -802, -33, -354, -675, 6100, 15887, 11459, 1192, -809, -35,
|
||||
-347, -689, 6013, 15853, 11546, 1243, -815, -37, -339, -703, 5925, 15818, 11632, 1294, -821, -39,
|
||||
-332, -717, 5838, 15782, 11718, 1346, -827, -41, -325, -730, 5751, 15745, 11803, 1399, -833, -43,
|
||||
-318, -742, 5665, 15707, 11888, 1452, -839, -46, -312, -754, 5579, 15668, 11973, 1506, -845, -48,
|
||||
-305, -766, 5493, 15627, 12057, 1561, -850, -50, -298, -777, 5408, 15586, 12140, 1616, -855, -53,
|
||||
-291, -787, 5323, 15544, 12224, 1672, -861, -56, -285, -798, 5239, 15500, 12306, 1729, -866, -58,
|
||||
-278, -807, 5155, 15456, 12388, 1786, -871, -61, -272, -817, 5071, 15410, 12470, 1844, -875, -64,
|
||||
-265, -826, 4988, 15364, 12551, 1902, -880, -67, -259, -834, 4905, 15317, 12631, 1962, -884, -70,
|
||||
-253, -842, 4823, 15268, 12711, 2022, -888, -73, -247, -850, 4741, 15219, 12790, 2082, -892, -76,
|
||||
-241, -857, 4659, 15168, 12869, 2143, -896, -79, -235, -864, 4578, 15117, 12947, 2205, -899, -82,
|
||||
-229, -870, 4498, 15065, 13025, 2267, -903, -85, -223, -876, 4417, 15012, 13102, 2331, -906, -89,
|
||||
-217, -882, 4338, 14958, 13178, 2394, -909, -92, -211, -887, 4259, 14903, 13254, 2459, -911, -96,
|
||||
-206, -892, 4180, 14847, 13329, 2523, -914, -100, -200, -896, 4102, 14790, 13403, 2589, -916, -103,
|
||||
-195, -900, 4024, 14732, 13477, 2655, -918, -107, -190, -904, 3947, 14673, 13550, 2722, -919, -111,
|
||||
-184, -908, 3871, 14614, 13622, 2789, -921, -115, -179, -911, 3795, 14553, 13693, 2857, -922, -119,
|
||||
-174, -913, 3719, 14492, 13764, 2926, -923, -123, -169, -916, 3644, 14430, 13834, 2995, -923, -127,
|
||||
-164, -918, 3570, 14367, 13904, 3065, -924, -132, -159, -920, 3496, 14303, 13972, 3136, -924, -136,
|
||||
-154, -921, 3423, 14239, 14040, 3207, -924, -140, -150, -922, 3350, 14173, 14107, 3278, -923, -145,
|
||||
-145, -923, 3278, 14107, 14173, 3350, -922, -150, -140, -924, 3207, 14040, 14239, 3423, -921, -154,
|
||||
-136, -924, 3136, 13972, 14303, 3496, -920, -159, -132, -924, 3065, 13904, 14367, 3570, -918, -164,
|
||||
-127, -923, 2995, 13834, 14430, 3644, -916, -169, -123, -923, 2926, 13764, 14492, 3719, -913, -174,
|
||||
-119, -922, 2857, 13693, 14553, 3795, -911, -179, -115, -921, 2789, 13622, 14614, 3871, -908, -184,
|
||||
-111, -919, 2722, 13550, 14673, 3947, -904, -190, -107, -918, 2655, 13477, 14732, 4024, -900, -195,
|
||||
-103, -916, 2589, 13403, 14790, 4102, -896, -200, -100, -914, 2523, 13329, 14847, 4180, -892, -206,
|
||||
-96, -911, 2459, 13254, 14903, 4259, -887, -211, -92, -909, 2394, 13178, 14958, 4338, -882, -217,
|
||||
-89, -906, 2331, 13102, 15012, 4417, -876, -223, -85, -903, 2267, 13025, 15065, 4498, -870, -229,
|
||||
-82, -899, 2205, 12947, 15117, 4578, -864, -235, -79, -896, 2143, 12869, 15168, 4659, -857, -241,
|
||||
-76, -892, 2082, 12790, 15219, 4741, -850, -247, -73, -888, 2022, 12711, 15268, 4823, -842, -253,
|
||||
-70, -884, 1962, 12631, 15317, 4905, -834, -259, -67, -880, 1902, 12551, 15364, 4988, -826, -265,
|
||||
-64, -875, 1844, 12470, 15410, 5071, -817, -272, -61, -871, 1786, 12388, 15456, 5155, -807, -278,
|
||||
-58, -866, 1729, 12306, 15500, 5239, -798, -285, -56, -861, 1672, 12224, 15544, 5323, -787, -291,
|
||||
-53, -855, 1616, 12140, 15586, 5408, -777, -298, -50, -850, 1561, 12057, 15627, 5493, -766, -305,
|
||||
-48, -845, 1506, 11973, 15668, 5579, -754, -312, -46, -839, 1452, 11888, 15707, 5665, -742, -318,
|
||||
-43, -833, 1399, 11803, 15745, 5751, -730, -325, -41, -827, 1346, 11718, 15782, 5838, -717, -332,
|
||||
-39, -821, 1294, 11632, 15818, 5925, -703, -339, -37, -815, 1243, 11546, 15853, 6013, -689, -347,
|
||||
-35, -809, 1192, 11459, 15887, 6100, -675, -354, -33, -802, 1142, 11372, 15920, 6189, -660, -361,
|
||||
-31, -796, 1093, 11285, 15952, 6277, -645, -368, -29, -789, 1044, 11197, 15983, 6366, -629, -376,
|
||||
-27, -782, 996, 11109, 16012, 6454, -612, -383, -26, -775, 949, 11021, 16041, 6544, -596, -391,
|
||||
-24, -768, 902, 10932, 16068, 6633, -578, -398, -22, -761, 856, 10843, 16095, 6723, -560, -406,
|
||||
-21, -754, 810, 10754, 16120, 6813, -542, -414, -19, -747, 766, 10664, 16144, 6903, -523, -421,
|
||||
-18, -739, 722, 10574, 16167, 6994, -503, -429, -16, -732, 678, 10484, 16189, 7084, -483, -437,
|
||||
-15, -724, 635, 10394, 16209, 7175, -462, -445, -14, -717, 593, 10303, 16229, 7266, -441, -452,
|
||||
-13, -709, 552, 10212, 16247, 7357, -419, -460, -11, -702, 511, 10121, 16265, 7449, -397, -468,
|
||||
-10, -694, 471, 10030, 16281, 7540, -374, -476, -9, -686, 432, 9939, 16296, 7632, -351, -484,
|
||||
-8, -678, 393, 9847, 16310, 7724, -327, -492, -7, -670, 355, 9756, 16322, 7816, -302, -500,
|
||||
-6, -662, 317, 9664, 16334, 7908, -277, -508, -5, -654, 280, 9572, 16344, 8000, -251, -516,
|
||||
-5, -646, 244, 9480, 16354, 8093, -225, -525, -4, -638, 208, 9387, 16362, 8185, -198, -533,
|
||||
-3, -630, 173, 9295, 16369, 8277, -170, -541, -2, -622, 139, 9203, 16375, 8370, -142, -549,
|
||||
-2, -614, 105, 9110, 16379, 8462, -113, -557, -1, -606, 72, 9018, 16383, 8555, -84, -565,
|
||||
-1, -598, 40, 8925, 16385, 8647, -54, -573, 0, -590, 8, 8833, 16386, 8740, -23, -582,
|
||||
};
|
||||
|
||||
private static short[] _highCurveLut1 = new short[]
|
||||
{
|
||||
-12, 47, -134, 32767, 81, -16, 2, 0, -26, 108, -345, 32760, 301, -79, 17, -1,
|
||||
-40, 168, -552, 32745, 526, -144, 32, -2, -53, 226, -753, 32723, 755, -210, 47, -3,
|
||||
-66, 284, -950, 32694, 989, -277, 63, -5, -78, 340, -1143, 32658, 1226, -346, 79, -6,
|
||||
-90, 394, -1331, 32615, 1469, -415, 96, -8, -101, 447, -1514, 32564, 1715, -486, 113, -9,
|
||||
-112, 499, -1692, 32506, 1966, -557, 130, -11, -123, 550, -1865, 32441, 2221, -630, 148, -13,
|
||||
-133, 599, -2034, 32369, 2480, -703, 166, -14, -143, 646, -2198, 32290, 2743, -778, 185, -16,
|
||||
-152, 693, -2357, 32204, 3010, -853, 204, -18, -162, 738, -2512, 32110, 3281, -929, 223, -20,
|
||||
-170, 781, -2662, 32010, 3555, -1007, 242, -23, -178, 823, -2807, 31903, 3834, -1084, 262, -25,
|
||||
-186, 864, -2947, 31789, 4116, -1163, 282, -27, -194, 903, -3082, 31668, 4403, -1242, 303, -30,
|
||||
-201, 940, -3213, 31540, 4692, -1322, 323, -32, -207, 977, -3339, 31406, 4985, -1403, 344, -35,
|
||||
-214, 1011, -3460, 31265, 5282, -1484, 365, -37, -220, 1045, -3577, 31117, 5582, -1566, 387, -40,
|
||||
-225, 1077, -3688, 30963, 5885, -1648, 409, -43, -230, 1107, -3796, 30802, 6191, -1730, 431, -46,
|
||||
-235, 1136, -3898, 30635, 6501, -1813, 453, -49, -240, 1164, -3996, 30462, 6813, -1896, 475, -52,
|
||||
-244, 1190, -4089, 30282, 7128, -1980, 498, -55, -247, 1215, -4178, 30097, 7446, -2064, 520, -58,
|
||||
-251, 1239, -4262, 29905, 7767, -2148, 543, -62, -254, 1261, -4342, 29707, 8091, -2231, 566, -65,
|
||||
-257, 1281, -4417, 29503, 8416, -2315, 589, -69, -259, 1301, -4488, 29293, 8745, -2399, 613, -72,
|
||||
-261, 1319, -4555, 29078, 9075, -2483, 636, -76, -263, 1336, -4617, 28857, 9408, -2567, 659, -80,
|
||||
-265, 1351, -4674, 28631, 9743, -2651, 683, -83, -266, 1365, -4728, 28399, 10080, -2734, 706, -87,
|
||||
-267, 1378, -4777, 28161, 10418, -2817, 730, -91, -267, 1389, -4822, 27919, 10759, -2899, 753, -95,
|
||||
-268, 1400, -4863, 27671, 11100, -2981, 777, -99, -268, 1409, -4900, 27418, 11444, -3063, 800, -103,
|
||||
-268, 1416, -4933, 27161, 11789, -3144, 824, -107, -267, 1423, -4962, 26898, 12135, -3224, 847, -112,
|
||||
-267, 1428, -4987, 26631, 12482, -3303, 870, -116, -266, 1433, -5008, 26359, 12830, -3382, 893, -120,
|
||||
-265, 1436, -5026, 26083, 13179, -3460, 916, -125, -264, 1438, -5039, 25802, 13529, -3537, 939, -129,
|
||||
-262, 1438, -5049, 25517, 13880, -3613, 962, -133, -260, 1438, -5055, 25228, 14231, -3687, 984, -138,
|
||||
-258, 1437, -5058, 24935, 14582, -3761, 1006, -142, -256, 1435, -5058, 24639, 14934, -3833, 1028, -147,
|
||||
-254, 1431, -5053, 24338, 15286, -3904, 1049, -151, -252, 1427, -5046, 24034, 15638, -3974, 1071, -155,
|
||||
-249, 1422, -5035, 23726, 15989, -4042, 1091, -160, -246, 1416, -5021, 23415, 16341, -4109, 1112, -164,
|
||||
-243, 1408, -5004, 23101, 16691, -4174, 1132, -169, -240, 1400, -4984, 22783, 17042, -4237, 1152, -173,
|
||||
-237, 1392, -4960, 22463, 17392, -4299, 1171, -178, -234, 1382, -4934, 22140, 17740, -4358, 1190, -182,
|
||||
-230, 1371, -4905, 21814, 18088, -4416, 1209, -186, -227, 1360, -4873, 21485, 18435, -4472, 1226, -191,
|
||||
-223, 1348, -4839, 21154, 18781, -4526, 1244, -195, -219, 1335, -4801, 20821, 19125, -4578, 1260, -199,
|
||||
-215, 1321, -4761, 20486, 19468, -4627, 1277, -203, -211, 1307, -4719, 20148, 19809, -4674, 1292, -207,
|
||||
-207, 1292, -4674, 19809, 20148, -4719, 1307, -211, -203, 1277, -4627, 19468, 20486, -4761, 1321, -215,
|
||||
-199, 1260, -4578, 19125, 20821, -4801, 1335, -219, -195, 1244, -4526, 18781, 21154, -4839, 1348, -223,
|
||||
-191, 1226, -4472, 18435, 21485, -4873, 1360, -227, -186, 1209, -4416, 18088, 21814, -4905, 1371, -230,
|
||||
-182, 1190, -4358, 17740, 22140, -4934, 1382, -234, -178, 1171, -4299, 17392, 22463, -4960, 1392, -237,
|
||||
-173, 1152, -4237, 17042, 22783, -4984, 1400, -240, -169, 1132, -4174, 16691, 23101, -5004, 1408, -243,
|
||||
-164, 1112, -4109, 16341, 23415, -5021, 1416, -246, -160, 1091, -4042, 15989, 23726, -5035, 1422, -249,
|
||||
-155, 1071, -3974, 15638, 24034, -5046, 1427, -252, -151, 1049, -3904, 15286, 24338, -5053, 1431, -254,
|
||||
-147, 1028, -3833, 14934, 24639, -5058, 1435, -256, -142, 1006, -3761, 14582, 24935, -5058, 1437, -258,
|
||||
-138, 984, -3687, 14231, 25228, -5055, 1438, -260, -133, 962, -3613, 13880, 25517, -5049, 1438, -262,
|
||||
-129, 939, -3537, 13529, 25802, -5039, 1438, -264, -125, 916, -3460, 13179, 26083, -5026, 1436, -265,
|
||||
-120, 893, -3382, 12830, 26359, -5008, 1433, -266, -116, 870, -3303, 12482, 26631, -4987, 1428, -267,
|
||||
-112, 847, -3224, 12135, 26898, -4962, 1423, -267, -107, 824, -3144, 11789, 27161, -4933, 1416, -268,
|
||||
-103, 800, -3063, 11444, 27418, -4900, 1409, -268, -99, 777, -2981, 11100, 27671, -4863, 1400, -268,
|
||||
-95, 753, -2899, 10759, 27919, -4822, 1389, -267, -91, 730, -2817, 10418, 28161, -4777, 1378, -267,
|
||||
-87, 706, -2734, 10080, 28399, -4728, 1365, -266, -83, 683, -2651, 9743, 28631, -4674, 1351, -265,
|
||||
-80, 659, -2567, 9408, 28857, -4617, 1336, -263, -76, 636, -2483, 9075, 29078, -4555, 1319, -261,
|
||||
-72, 613, -2399, 8745, 29293, -4488, 1301, -259, -69, 589, -2315, 8416, 29503, -4417, 1281, -257,
|
||||
-65, 566, -2231, 8091, 29707, -4342, 1261, -254, -62, 543, -2148, 7767, 29905, -4262, 1239, -251,
|
||||
-58, 520, -2064, 7446, 30097, -4178, 1215, -247, -55, 498, -1980, 7128, 30282, -4089, 1190, -244,
|
||||
-52, 475, -1896, 6813, 30462, -3996, 1164, -240, -49, 453, -1813, 6501, 30635, -3898, 1136, -235,
|
||||
-46, 431, -1730, 6191, 30802, -3796, 1107, -230, -43, 409, -1648, 5885, 30963, -3688, 1077, -225,
|
||||
-40, 387, -1566, 5582, 31117, -3577, 1045, -220, -37, 365, -1484, 5282, 31265, -3460, 1011, -214,
|
||||
-35, 344, -1403, 4985, 31406, -3339, 977, -207, -32, 323, -1322, 4692, 31540, -3213, 940, -201,
|
||||
-30, 303, -1242, 4403, 31668, -3082, 903, -194, -27, 282, -1163, 4116, 31789, -2947, 864, -186,
|
||||
-25, 262, -1084, 3834, 31903, -2807, 823, -178, -23, 242, -1007, 3555, 32010, -2662, 781, -170,
|
||||
-20, 223, -929, 3281, 32110, -2512, 738, -162, -18, 204, -853, 3010, 32204, -2357, 693, -152,
|
||||
-16, 185, -778, 2743, 32290, -2198, 646, -143, -14, 166, -703, 2480, 32369, -2034, 599, -133,
|
||||
-13, 148, -630, 2221, 32441, -1865, 550, -123, -11, 130, -557, 1966, 32506, -1692, 499, -112,
|
||||
-9, 113, -486, 1715, 32564, -1514, 447, -101, -8, 96, -415, 1469, 32615, -1331, 394, -90,
|
||||
-6, 79, -346, 1226, 32658, -1143, 340, -78, -5, 63, -277, 989, 32694, -950, 284, -66,
|
||||
-3, 47, -210, 755, 32723, -753, 226, -53, -2, 32, -144, 526, 32745, -552, 168, -40,
|
||||
-1, 17, -79, 301, 32760, -345, 108, -26, 0, 2, -16, 81, 32767, -134, 47, -12,
|
||||
};
|
||||
|
||||
private static short[] _highCurveLut2 = new short[]
|
||||
{
|
||||
418, -2538, 6118, 24615, 6298, -2563, 417, 0, 420, -2512, 5939, 24611, 6479, -2588, 415, 1,
|
||||
421, -2485, 5761, 24605, 6662, -2612, 412, 2, 422, -2458, 5585, 24595, 6846, -2635, 409, 3,
|
||||
423, -2430, 5410, 24582, 7030, -2658, 406, 4, 423, -2402, 5236, 24565, 7216, -2680, 403, 5,
|
||||
423, -2373, 5064, 24546, 7403, -2701, 399, 6, 423, -2343, 4893, 24523, 7591, -2721, 395, 7,
|
||||
423, -2313, 4724, 24496, 7780, -2741, 391, 8, 422, -2283, 4556, 24467, 7970, -2759, 386, 9,
|
||||
421, -2252, 4390, 24434, 8161, -2777, 381, 11, 420, -2221, 4225, 24398, 8353, -2794, 376, 12,
|
||||
419, -2190, 4062, 24359, 8545, -2810, 370, 14, 418, -2158, 3900, 24316, 8739, -2825, 364, 15,
|
||||
416, -2126, 3740, 24271, 8933, -2839, 358, 17, 414, -2093, 3582, 24222, 9127, -2851, 351, 19,
|
||||
412, -2060, 3425, 24170, 9323, -2863, 344, 21, 410, -2027, 3270, 24115, 9519, -2874, 336, 22,
|
||||
407, -1993, 3117, 24056, 9715, -2884, 328, 24, 404, -1960, 2966, 23995, 9912, -2893, 319, 26,
|
||||
402, -1926, 2816, 23930, 10110, -2900, 311, 29, 398, -1892, 2668, 23863, 10308, -2907, 301, 31,
|
||||
395, -1858, 2522, 23792, 10506, -2912, 292, 33, 392, -1823, 2378, 23718, 10705, -2916, 282, 35,
|
||||
389, -1789, 2235, 23641, 10904, -2919, 271, 38, 385, -1754, 2095, 23561, 11103, -2920, 261, 40,
|
||||
381, -1719, 1956, 23478, 11303, -2921, 249, 43, 377, -1684, 1819, 23393, 11502, -2920, 238, 45,
|
||||
373, -1649, 1684, 23304, 11702, -2917, 225, 48, 369, -1615, 1551, 23212, 11902, -2914, 213, 51,
|
||||
365, -1580, 1420, 23118, 12102, -2909, 200, 54, 361, -1545, 1291, 23020, 12302, -2902, 186, 57,
|
||||
356, -1510, 1163, 22920, 12502, -2895, 173, 60, 352, -1475, 1038, 22817, 12702, -2885, 158, 63,
|
||||
347, -1440, 915, 22711, 12901, -2875, 143, 66, 342, -1405, 793, 22602, 13101, -2863, 128, 69,
|
||||
338, -1370, 674, 22491, 13300, -2849, 113, 73, 333, -1335, 557, 22377, 13499, -2834, 97, 76,
|
||||
328, -1301, 441, 22260, 13698, -2817, 80, 80, 323, -1266, 328, 22141, 13896, -2799, 63, 83,
|
||||
318, -1232, 217, 22019, 14094, -2779, 46, 87, 313, -1197, 107, 21894, 14291, -2758, 28, 91,
|
||||
307, -1163, 0, 21767, 14488, -2735, 9, 95, 302, -1129, -105, 21637, 14684, -2710, -9, 98,
|
||||
297, -1096, -208, 21506, 14879, -2684, -29, 102, 292, -1062, -310, 21371, 15074, -2656, -48, 106,
|
||||
286, -1029, -409, 21234, 15268, -2626, -69, 111, 281, -996, -506, 21095, 15461, -2595, -89, 115,
|
||||
276, -963, -601, 20954, 15654, -2562, -110, 119, 270, -930, -694, 20810, 15846, -2527, -132, 123,
|
||||
265, -898, -785, 20664, 16036, -2490, -154, 128, 260, -866, -874, 20516, 16226, -2452, -176, 132,
|
||||
254, -834, -961, 20366, 16415, -2411, -199, 137, 249, -803, -1046, 20213, 16602, -2369, -222, 141,
|
||||
243, -771, -1129, 20059, 16789, -2326, -246, 146, 238, -740, -1209, 19902, 16974, -2280, -270, 151,
|
||||
233, -710, -1288, 19744, 17158, -2232, -294, 156, 227, -680, -1365, 19583, 17341, -2183, -319, 160,
|
||||
222, -650, -1440, 19421, 17523, -2132, -345, 165, 217, -620, -1513, 19257, 17703, -2079, -370, 170,
|
||||
211, -591, -1583, 19091, 17882, -2023, -396, 175, 206, -562, -1652, 18923, 18059, -1966, -423, 180,
|
||||
201, -533, -1719, 18754, 18235, -1907, -450, 185, 196, -505, -1784, 18582, 18410, -1847, -477, 191,
|
||||
191, -477, -1847, 18410, 18582, -1784, -505, 196, 185, -450, -1907, 18235, 18754, -1719, -533, 201,
|
||||
180, -423, -1966, 18059, 18923, -1652, -562, 206, 175, -396, -2023, 17882, 19091, -1583, -591, 211,
|
||||
170, -370, -2079, 17703, 19257, -1513, -620, 217, 165, -345, -2132, 17523, 19421, -1440, -650, 222,
|
||||
160, -319, -2183, 17341, 19583, -1365, -680, 227, 156, -294, -2232, 17158, 19744, -1288, -710, 233,
|
||||
151, -270, -2280, 16974, 19902, -1209, -740, 238, 146, -246, -2326, 16789, 20059, -1129, -771, 243,
|
||||
141, -222, -2369, 16602, 20213, -1046, -803, 249, 137, -199, -2411, 16415, 20366, -961, -834, 254,
|
||||
132, -176, -2452, 16226, 20516, -874, -866, 260, 128, -154, -2490, 16036, 20664, -785, -898, 265,
|
||||
123, -132, -2527, 15846, 20810, -694, -930, 270, 119, -110, -2562, 15654, 20954, -601, -963, 276,
|
||||
115, -89, -2595, 15461, 21095, -506, -996, 281, 111, -69, -2626, 15268, 21234, -409, -1029, 286,
|
||||
106, -48, -2656, 15074, 21371, -310, -1062, 292, 102, -29, -2684, 14879, 21506, -208, -1096, 297,
|
||||
98, -9, -2710, 14684, 21637, -105, -1129, 302, 95, 9, -2735, 14488, 21767, 0, -1163, 307,
|
||||
91, 28, -2758, 14291, 21894, 107, -1197, 313, 87, 46, -2779, 14094, 22019, 217, -1232, 318,
|
||||
83, 63, -2799, 13896, 22141, 328, -1266, 323, 80, 80, -2817, 13698, 22260, 441, -1301, 328,
|
||||
76, 97, -2834, 13499, 22377, 557, -1335, 333, 73, 113, -2849, 13300, 22491, 674, -1370, 338,
|
||||
69, 128, -2863, 13101, 22602, 793, -1405, 342, 66, 143, -2875, 12901, 22711, 915, -1440, 347,
|
||||
63, 158, -2885, 12702, 22817, 1038, -1475, 352, 60, 173, -2895, 12502, 22920, 1163, -1510, 356,
|
||||
57, 186, -2902, 12302, 23020, 1291, -1545, 361, 54, 200, -2909, 12102, 23118, 1420, -1580, 365,
|
||||
51, 213, -2914, 11902, 23212, 1551, -1615, 369, 48, 225, -2917, 11702, 23304, 1684, -1649, 373,
|
||||
45, 238, -2920, 11502, 23393, 1819, -1684, 377, 43, 249, -2921, 11303, 23478, 1956, -1719, 381,
|
||||
40, 261, -2920, 11103, 23561, 2095, -1754, 385, 38, 271, -2919, 10904, 23641, 2235, -1789, 389,
|
||||
35, 282, -2916, 10705, 23718, 2378, -1823, 392, 33, 292, -2912, 10506, 23792, 2522, -1858, 395,
|
||||
31, 301, -2907, 10308, 23863, 2668, -1892, 398, 29, 311, -2900, 10110, 23930, 2816, -1926, 402,
|
||||
26, 319, -2893, 9912, 23995, 2966, -1960, 404, 24, 328, -2884, 9715, 24056, 3117, -1993, 407,
|
||||
22, 336, -2874, 9519, 24115, 3270, -2027, 410, 21, 344, -2863, 9323, 24170, 3425, -2060, 412,
|
||||
19, 351, -2851, 9127, 24222, 3582, -2093, 414, 17, 358, -2839, 8933, 24271, 3740, -2126, 416,
|
||||
15, 364, -2825, 8739, 24316, 3900, -2158, 418, 14, 370, -2810, 8545, 24359, 4062, -2190, 419,
|
||||
12, 376, -2794, 8353, 24398, 4225, -2221, 420, 11, 381, -2777, 8161, 24434, 4390, -2252, 421,
|
||||
9, 386, -2759, 7970, 24467, 4556, -2283, 422, 8, 391, -2741, 7780, 24496, 4724, -2313, 423,
|
||||
7, 395, -2721, 7591, 24523, 4893, -2343, 423, 6, 399, -2701, 7403, 24546, 5064, -2373, 423,
|
||||
5, 403, -2680, 7216, 24565, 5236, -2402, 423, 4, 406, -2658, 7030, 24582, 5410, -2430, 423,
|
||||
3, 409, -2635, 6846, 24595, 5585, -2458, 422, 2, 412, -2612, 6662, 24605, 5761, -2485, 421,
|
||||
1, 415, -2588, 6479, 24611, 5939, -2512, 420, 0, 417, -2563, 6298, 24615, 6118, -2538, 418,
|
||||
};
|
||||
#endregion
|
||||
|
||||
private static float[] _normalCurveLut0F;
|
||||
private static float[] _normalCurveLut1F;
|
||||
private static float[] _normalCurveLut2F;
|
||||
|
||||
private static float[] _highCurveLut0F;
|
||||
private static float[] _highCurveLut1F;
|
||||
private static float[] _highCurveLut2F;
|
||||
|
||||
static ResamplerHelper()
|
||||
{
|
||||
_normalCurveLut0F = _normalCurveLut0.Select(x => x / 32768f).ToArray();
|
||||
_normalCurveLut1F = _normalCurveLut1.Select(x => x / 32768f).ToArray();
|
||||
_normalCurveLut2F = _normalCurveLut2.Select(x => x / 32768f).ToArray();
|
||||
|
||||
_highCurveLut0F = _highCurveLut0.Select(x => x / 32768f).ToArray();
|
||||
_highCurveLut1F = _highCurveLut1.Select(x => x / 32768f).ToArray();
|
||||
_highCurveLut2F = _highCurveLut2.Select(x => x / 32768f).ToArray();
|
||||
}
|
||||
|
||||
private const int FixedPointPrecision = 15;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Resample(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount, SampleRateConversionQuality srcQuality, bool needPitch)
|
||||
{
|
||||
switch (srcQuality)
|
||||
{
|
||||
case SampleRateConversionQuality.Default:
|
||||
ResampleDefaultQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount, needPitch);
|
||||
break;
|
||||
case SampleRateConversionQuality.Low:
|
||||
ResampleLowQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount);
|
||||
break;
|
||||
case SampleRateConversionQuality.High:
|
||||
ResampleHighQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"{srcQuality}");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ReadOnlySpan<float> GetDefaultParameter(float ratio)
|
||||
{
|
||||
if (ratio <= 1.0f)
|
||||
{
|
||||
return _normalCurveLut1F;
|
||||
}
|
||||
else if (ratio > 1.333313f)
|
||||
{
|
||||
return _normalCurveLut0F;
|
||||
}
|
||||
|
||||
return _normalCurveLut2F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe static void ResampleDefaultQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount, bool needPitch)
|
||||
{
|
||||
ReadOnlySpan<float> parameters = GetDefaultParameter(ratio);
|
||||
|
||||
int inputBufferIndex = 0, i = 0;
|
||||
|
||||
// TODO: REV8 fast path (when needPitch == false the input index progression is constant + we need SIMD)
|
||||
|
||||
if (Sse41.IsSupported)
|
||||
{
|
||||
if (ratio == 1f)
|
||||
{
|
||||
fixed (short* pInput = inputBuffer)
|
||||
{
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
{
|
||||
Vector128<float> parameter = Sse.LoadVector128(pParameters);
|
||||
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3);
|
||||
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter);
|
||||
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputBufferIndex = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
fixed (short* pInput = inputBuffer)
|
||||
{
|
||||
fixed (float* pOutput = outputBuffer, pParameters = parameters)
|
||||
{
|
||||
for (; i < (sampleCount & ~3); i += 4)
|
||||
{
|
||||
uint baseIndex0 = (uint)(fraction * 128) * 4;
|
||||
uint inputIndex0 = (uint)inputBufferIndex;
|
||||
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
|
||||
uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4;
|
||||
uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction;
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)fraction;
|
||||
|
||||
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
||||
fraction -= (int)fraction;
|
||||
|
||||
Vector128<float> parameter0 = Sse.LoadVector128(pParameters + baseIndex0);
|
||||
Vector128<float> parameter1 = Sse.LoadVector128(pParameters + baseIndex1);
|
||||
Vector128<float> parameter2 = Sse.LoadVector128(pParameters + baseIndex2);
|
||||
Vector128<float> parameter3 = Sse.LoadVector128(pParameters + baseIndex3);
|
||||
|
||||
Vector128<int> intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0);
|
||||
Vector128<int> intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1);
|
||||
Vector128<int> intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2);
|
||||
Vector128<int> intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3);
|
||||
|
||||
Vector128<float> input0 = Sse2.ConvertToVector128Single(intInput0);
|
||||
Vector128<float> input1 = Sse2.ConvertToVector128Single(intInput1);
|
||||
Vector128<float> input2 = Sse2.ConvertToVector128Single(intInput2);
|
||||
Vector128<float> input3 = Sse2.ConvertToVector128Single(intInput3);
|
||||
|
||||
Vector128<float> mix0 = Sse.Multiply(input0, parameter0);
|
||||
Vector128<float> mix1 = Sse.Multiply(input1, parameter1);
|
||||
Vector128<float> mix2 = Sse.Multiply(input2, parameter2);
|
||||
Vector128<float> mix3 = Sse.Multiply(input3, parameter3);
|
||||
|
||||
Vector128<float> mix01 = Sse3.HorizontalAdd(mix0, mix1);
|
||||
Vector128<float> mix23 = Sse3.HorizontalAdd(mix2, mix3);
|
||||
|
||||
Vector128<float> mix0123 = Sse3.HorizontalAdd(mix01, mix23);
|
||||
|
||||
Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < sampleCount; i++)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 4;
|
||||
ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 4);
|
||||
ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 4);
|
||||
|
||||
outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
|
||||
currentInput[1] * parameter[1] +
|
||||
currentInput[2] * parameter[2] +
|
||||
currentInput[3] * parameter[3]);
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)fraction;
|
||||
|
||||
// Only keep lower part (safe as fraction isn't supposed to be negative)
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ReadOnlySpan<float> GetHighParameter(float ratio)
|
||||
{
|
||||
if (ratio <= 1.0f)
|
||||
{
|
||||
return _highCurveLut1F;
|
||||
}
|
||||
else if (ratio > 1.333313f)
|
||||
{
|
||||
return _highCurveLut0F;
|
||||
}
|
||||
|
||||
return _highCurveLut2F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ResampleHighQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
{
|
||||
ReadOnlySpan<float> parameters = GetHighParameter(ratio);
|
||||
|
||||
int inputBufferIndex = 0;
|
||||
|
||||
// TODO: fast path
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
int baseIndex = (int)(fraction * 128) * 8;
|
||||
ReadOnlySpan<float> parameter = parameters.Slice(baseIndex, 8);
|
||||
ReadOnlySpan<short> currentInput = inputBuffer.Slice(inputBufferIndex, 8);
|
||||
|
||||
outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] +
|
||||
currentInput[1] * parameter[1] +
|
||||
currentInput[2] * parameter[2] +
|
||||
currentInput[3] * parameter[3] +
|
||||
currentInput[4] * parameter[4] +
|
||||
currentInput[5] * parameter[5] +
|
||||
currentInput[6] * parameter[6] +
|
||||
currentInput[7] * parameter[7]);
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ResampleLowQuality(Span<float> outputBuffer, ReadOnlySpan<short> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
{
|
||||
int inputBufferIndex = 0;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
int outputData = inputBuffer[inputBufferIndex];
|
||||
|
||||
if (fraction > 1.0f)
|
||||
{
|
||||
outputData = inputBuffer[inputBufferIndex + 1];
|
||||
}
|
||||
|
||||
outputBuffer[i] = outputData;
|
||||
|
||||
fraction += ratio;
|
||||
inputBufferIndex += (int)MathF.Truncate(fraction);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ResampleForUpsampler(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float ratio, ref float fraction, int sampleCount)
|
||||
{
|
||||
// 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 s0, s1, s2, s3;
|
||||
|
||||
s1 = inputBuffer[inputBufferIndex];
|
||||
|
||||
if (inputBufferIndex == 0 || inputBufferIndex > cubicEnd)
|
||||
{
|
||||
// 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];
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
fraction -= (int)fraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a compressor effect.
|
||||
/// </summary>
|
||||
public class CompressorEffect : BaseEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The compressor parameter.
|
||||
/// </summary>
|
||||
public CompressorParameter Parameter;
|
||||
|
||||
/// <summary>
|
||||
/// The compressor state.
|
||||
/// </summary>
|
||||
public Memory<CompressorState> State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="CompressorEffect"/>.
|
||||
/// </summary>
|
||||
public CompressorEffect()
|
||||
{
|
||||
State = new CompressorState[1];
|
||||
}
|
||||
|
||||
public override EffectType TargetEffectType => EffectType.Compressor;
|
||||
|
||||
public override ulong GetWorkBuffer(int index)
|
||||
{
|
||||
return GetSingleBuffer();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
|
||||
{
|
||||
// Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised.
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
}
|
||||
|
||||
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
|
||||
{
|
||||
Debug.Assert(IsTypeValid(ref parameter));
|
||||
|
||||
UpdateParameterBase(ref parameter);
|
||||
|
||||
Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0];
|
||||
IsEnabled = parameter.IsEnabled;
|
||||
|
||||
updateErrorInfo = new BehaviourParameter.ErrorInfo();
|
||||
}
|
||||
|
||||
public override void UpdateForCommandGeneration()
|
||||
{
|
||||
UpdateUsageStateForCommandGeneration();
|
||||
|
||||
Parameter.Status = UsageState.Enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,636 +0,0 @@
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Performance;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using Ryujinx.Audio.Renderer.Server.Mix;
|
||||
using Ryujinx.Audio.Renderer.Server.Performance;
|
||||
using Ryujinx.Audio.Renderer.Server.Sink;
|
||||
using Ryujinx.Audio.Renderer.Server.Splitter;
|
||||
using Ryujinx.Audio.Renderer.Server.Voice;
|
||||
using Ryujinx.Audio.Renderer.Utils;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
public class StateUpdater
|
||||
{
|
||||
private readonly ReadOnlyMemory<byte> _inputOrigin;
|
||||
private ReadOnlyMemory<byte> _outputOrigin;
|
||||
private ReadOnlyMemory<byte> _input;
|
||||
|
||||
private Memory<byte> _output;
|
||||
private uint _processHandle;
|
||||
private BehaviourContext _behaviourContext;
|
||||
|
||||
private UpdateDataHeader _inputHeader;
|
||||
private Memory<UpdateDataHeader> _outputHeader;
|
||||
|
||||
private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0];
|
||||
|
||||
public StateUpdater(ReadOnlyMemory<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext)
|
||||
{
|
||||
_input = input;
|
||||
_inputOrigin = _input;
|
||||
_output = output;
|
||||
_outputOrigin = _output;
|
||||
_processHandle = processHandle;
|
||||
_behaviourContext = behaviourContext;
|
||||
|
||||
_inputHeader = SpanIOHelper.Read<UpdateDataHeader>(ref _input);
|
||||
|
||||
_outputHeader = SpanMemoryManager<UpdateDataHeader>.Cast(_output.Slice(0, Unsafe.SizeOf<UpdateDataHeader>()));
|
||||
OutputHeader.Initialize(_behaviourContext.UserRevision);
|
||||
_output = _output.Slice(Unsafe.SizeOf<UpdateDataHeader>());
|
||||
}
|
||||
|
||||
public ResultCode UpdateBehaviourContext()
|
||||
{
|
||||
BehaviourParameter parameter = SpanIOHelper.Read<BehaviourParameter>(ref _input);
|
||||
|
||||
if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
_behaviourContext.ClearError();
|
||||
_behaviourContext.UpdateFlags(parameter.Flags);
|
||||
|
||||
if (_inputHeader.BehaviourSize != Unsafe.SizeOf<BehaviourParameter>())
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateMemoryPools(Span<MemoryPoolState> memoryPools)
|
||||
{
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
if (memoryPools.Length * Unsafe.SizeOf<MemoryPoolInParameter>() != _inputHeader.MemoryPoolsSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
foreach (ref MemoryPoolState memoryPool in memoryPools)
|
||||
{
|
||||
MemoryPoolInParameter parameter = SpanIOHelper.Read<MemoryPoolInParameter>(ref _input);
|
||||
|
||||
ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef<MemoryPoolOutStatus>(ref _output)[0];
|
||||
|
||||
PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus);
|
||||
|
||||
if (updateResult != PoolMapper.UpdateResult.Success &&
|
||||
updateResult != PoolMapper.UpdateResult.MapError &&
|
||||
updateResult != PoolMapper.UpdateResult.UnmapError)
|
||||
{
|
||||
if (updateResult != PoolMapper.UpdateResult.InvalidParameter)
|
||||
{
|
||||
throw new InvalidOperationException($"{updateResult}");
|
||||
}
|
||||
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
}
|
||||
|
||||
OutputHeader.MemoryPoolsSize = (uint)(Unsafe.SizeOf<MemoryPoolOutStatus>() * memoryPools.Length);
|
||||
OutputHeader.TotalSize += OutputHeader.MemoryPoolsSize;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateVoiceChannelResources(VoiceContext context)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<VoiceChannelResourceInParameter>() != _inputHeader.VoiceResourcesSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
VoiceChannelResourceInParameter parameter = SpanIOHelper.Read<VoiceChannelResourceInParameter>(ref _input);
|
||||
|
||||
ref VoiceChannelResource resource = ref context.GetChannelResource(i);
|
||||
|
||||
resource.Id = parameter.Id;
|
||||
parameter.Mix.AsSpan().CopyTo(resource.Mix.AsSpan());
|
||||
resource.IsUsed = parameter.IsUsed;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateVoices(VoiceContext context, Memory<MemoryPoolState> memoryPools)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<VoiceInParameter>() != _inputHeader.VoicesSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<VoiceInParameter> parameters = MemoryMarshal.Cast<byte, VoiceInParameter>(_input.Slice(0, (int)_inputHeader.VoicesSize).Span);
|
||||
|
||||
_input = _input.Slice((int)_inputHeader.VoicesSize);
|
||||
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
// First make everything not in use.
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
ref VoiceState state = ref context.GetState(i);
|
||||
|
||||
state.InUse = false;
|
||||
}
|
||||
|
||||
// Start processing
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
VoiceInParameter parameter = parameters[i];
|
||||
|
||||
Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax];
|
||||
|
||||
ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<VoiceOutStatus>(ref _output)[0];
|
||||
|
||||
if (parameter.InUse)
|
||||
{
|
||||
ref VoiceState currentVoiceState = ref context.GetState(i);
|
||||
|
||||
for (int channelResourceIndex = 0; channelResourceIndex < parameter.ChannelCount; channelResourceIndex++)
|
||||
{
|
||||
int channelId = parameter.ChannelResourceIds[channelResourceIndex];
|
||||
|
||||
Debug.Assert(channelId >= 0 && channelId < context.GetCount());
|
||||
|
||||
voiceUpdateStates[channelResourceIndex] = context.GetUpdateStateForCpu(channelId);
|
||||
}
|
||||
|
||||
if (parameter.IsNew)
|
||||
{
|
||||
currentVoiceState.Initialize();
|
||||
}
|
||||
|
||||
currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext);
|
||||
|
||||
if (updateParameterError.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref updateParameterError);
|
||||
}
|
||||
|
||||
currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext);
|
||||
|
||||
foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan())
|
||||
{
|
||||
if (errorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref errorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates);
|
||||
}
|
||||
}
|
||||
|
||||
int currentOutputSize = _output.Length;
|
||||
|
||||
OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf<VoiceOutStatus>() * context.GetCount());
|
||||
OutputHeader.TotalSize += OutputHeader.VoicesSize;
|
||||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private static void ResetEffect<T>(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
|
||||
{
|
||||
effect.ForceUnmapBuffers(mapper);
|
||||
|
||||
switch (parameter.Type)
|
||||
{
|
||||
case EffectType.Invalid:
|
||||
effect = new BaseEffect();
|
||||
break;
|
||||
case EffectType.BufferMix:
|
||||
effect = new BufferMixEffect();
|
||||
break;
|
||||
case EffectType.AuxiliaryBuffer:
|
||||
effect = new AuxiliaryBufferEffect();
|
||||
break;
|
||||
case EffectType.Delay:
|
||||
effect = new DelayEffect();
|
||||
break;
|
||||
case EffectType.Reverb:
|
||||
effect = new ReverbEffect();
|
||||
break;
|
||||
case EffectType.Reverb3d:
|
||||
effect = new Reverb3dEffect();
|
||||
break;
|
||||
case EffectType.BiquadFilter:
|
||||
effect = new BiquadFilterEffect();
|
||||
break;
|
||||
case EffectType.Limiter:
|
||||
effect = new LimiterEffect();
|
||||
break;
|
||||
case EffectType.CaptureBuffer:
|
||||
effect = new CaptureBufferEffect();
|
||||
break;
|
||||
case EffectType.Compressor:
|
||||
effect = new CompressorEffect();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
{
|
||||
if (_behaviourContext.IsEffectInfoVersion2Supported())
|
||||
{
|
||||
return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools);
|
||||
}
|
||||
else
|
||||
{
|
||||
return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools);
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion2>() != _inputHeader.EffectsSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<EffectInParameterVersion2> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion2>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
|
||||
|
||||
_input = _input.Slice((int)_inputHeader.EffectsSize);
|
||||
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
EffectInParameterVersion2 parameter = parameters[i];
|
||||
|
||||
ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion2>(ref _output)[0];
|
||||
|
||||
ref BaseEffect effect = ref context.GetEffect(i);
|
||||
|
||||
if (!effect.IsTypeValid(ref parameter))
|
||||
{
|
||||
ResetEffect(ref effect, ref parameter, mapper);
|
||||
}
|
||||
|
||||
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref updateErrorInfo);
|
||||
}
|
||||
|
||||
effect.StoreStatus(ref outStatus, isAudioRendererActive);
|
||||
|
||||
if (parameter.IsNew)
|
||||
{
|
||||
effect.InitializeResultState(ref context.GetDspState(i));
|
||||
effect.InitializeResultState(ref context.GetState(i));
|
||||
}
|
||||
|
||||
effect.UpdateResultState(ref outStatus.ResultState, ref context.GetState(i));
|
||||
}
|
||||
|
||||
int currentOutputSize = _output.Length;
|
||||
|
||||
OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatusVersion2>() * context.GetCount());
|
||||
OutputHeader.TotalSize += OutputHeader.EffectsSize;
|
||||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools)
|
||||
{
|
||||
if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion1>() != _inputHeader.EffectsSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<EffectInParameterVersion1> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion1>(_input.Slice(0, (int)_inputHeader.EffectsSize).Span);
|
||||
|
||||
_input = _input.Slice((int)_inputHeader.EffectsSize);
|
||||
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
EffectInParameterVersion1 parameter = parameters[i];
|
||||
|
||||
ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion1>(ref _output)[0];
|
||||
|
||||
ref BaseEffect effect = ref context.GetEffect(i);
|
||||
|
||||
if (!effect.IsTypeValid(ref parameter))
|
||||
{
|
||||
ResetEffect(ref effect, ref parameter, mapper);
|
||||
}
|
||||
|
||||
effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref updateErrorInfo);
|
||||
}
|
||||
|
||||
effect.StoreStatus(ref outStatus, isAudioRendererActive);
|
||||
}
|
||||
|
||||
int currentOutputSize = _output.Length;
|
||||
|
||||
OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf<EffectOutStatusVersion1>() * context.GetCount());
|
||||
OutputHeader.TotalSize += OutputHeader.EffectsSize;
|
||||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateSplitter(SplitterContext context)
|
||||
{
|
||||
if (context.Update(_input.Span, out int consumedSize))
|
||||
{
|
||||
_input = _input.Slice(consumedSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan<MixParameter> parameters)
|
||||
{
|
||||
uint maxMixStateCount = mixContext.GetCount();
|
||||
uint totalRequiredMixBufferCount = 0;
|
||||
|
||||
for (int i = 0; i < inputMixCount; i++)
|
||||
{
|
||||
if (parameters[i].IsUsed)
|
||||
{
|
||||
if (parameters[i].DestinationMixId != Constants.UnusedMixId &&
|
||||
parameters[i].DestinationMixId > maxMixStateCount &&
|
||||
parameters[i].MixId != Constants.FinalMixId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
totalRequiredMixBufferCount += parameters[i].BufferCount;
|
||||
}
|
||||
}
|
||||
|
||||
return totalRequiredMixBufferCount > mixBufferCount;
|
||||
}
|
||||
|
||||
public ResultCode UpdateMixes(MixContext mixContext, uint mixBufferCount, EffectContext effectContext, SplitterContext splitterContext)
|
||||
{
|
||||
uint mixCount;
|
||||
uint inputMixSize;
|
||||
uint inputSize = 0;
|
||||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast<byte, MixInParameterDirtyOnlyUpdate>(_input.Span)[0];
|
||||
|
||||
mixCount = parameter.MixCount;
|
||||
|
||||
inputSize += (uint)Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>();
|
||||
}
|
||||
else
|
||||
{
|
||||
mixCount = mixContext.GetCount();
|
||||
}
|
||||
|
||||
inputMixSize = mixCount * (uint)Unsafe.SizeOf<MixParameter>();
|
||||
|
||||
inputSize += inputMixSize;
|
||||
|
||||
if (inputSize != _inputHeader.MixesSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
_input = _input.Slice(Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>());
|
||||
}
|
||||
|
||||
ReadOnlySpan<MixParameter> parameters = MemoryMarshal.Cast<byte, MixParameter>(_input.Span.Slice(0, (int)inputMixSize));
|
||||
|
||||
_input = _input.Slice((int)inputMixSize);
|
||||
|
||||
if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters))
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
bool isMixContextDirty = false;
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
MixParameter parameter = parameters[i];
|
||||
|
||||
int mixId = i;
|
||||
|
||||
if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported())
|
||||
{
|
||||
mixId = parameter.MixId;
|
||||
}
|
||||
|
||||
ref MixState mix = ref mixContext.GetState(mixId);
|
||||
|
||||
if (parameter.IsUsed != mix.IsUsed)
|
||||
{
|
||||
mix.IsUsed = parameter.IsUsed;
|
||||
|
||||
if (parameter.IsUsed)
|
||||
{
|
||||
mix.ClearEffectProcessingOrder();
|
||||
}
|
||||
|
||||
isMixContextDirty = true;
|
||||
}
|
||||
|
||||
if (mix.IsUsed)
|
||||
{
|
||||
isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext);
|
||||
}
|
||||
}
|
||||
|
||||
if (isMixContextDirty)
|
||||
{
|
||||
if (_behaviourContext.IsSplitterSupported() && splitterContext.UsingSplitter())
|
||||
{
|
||||
if (!mixContext.Sort(splitterContext))
|
||||
{
|
||||
return ResultCode.InvalidMixSorting;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mixContext.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter)
|
||||
{
|
||||
sink.CleanUp();
|
||||
|
||||
switch (parameter.Type)
|
||||
{
|
||||
case SinkType.Invalid:
|
||||
sink = new BaseSink();
|
||||
break;
|
||||
case SinkType.CircularBuffer:
|
||||
sink = new CircularBufferSink();
|
||||
break;
|
||||
case SinkType.Device:
|
||||
sink = new DeviceSink();
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"SinkType {parameter.Type} not implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode UpdateSinks(SinkContext context, Memory<MemoryPoolState> memoryPools)
|
||||
{
|
||||
PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled());
|
||||
|
||||
if (context.GetCount() * Unsafe.SizeOf<SinkInParameter>() != _inputHeader.SinksSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
int initialOutputSize = _output.Length;
|
||||
|
||||
ReadOnlySpan<SinkInParameter> parameters = MemoryMarshal.Cast<byte, SinkInParameter>(_input.Slice(0, (int)_inputHeader.SinksSize).Span);
|
||||
|
||||
_input = _input.Slice((int)_inputHeader.SinksSize);
|
||||
|
||||
for (int i = 0; i < context.GetCount(); i++)
|
||||
{
|
||||
SinkInParameter parameter = parameters[i];
|
||||
ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef<SinkOutStatus>(ref _output)[0];
|
||||
ref BaseSink sink = ref context.GetSink(i);
|
||||
|
||||
if (!sink.IsTypeValid(ref parameter))
|
||||
{
|
||||
ResetSink(ref sink, ref parameter);
|
||||
}
|
||||
|
||||
sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper);
|
||||
|
||||
if (updateErrorInfo.ErrorCode != ResultCode.Success)
|
||||
{
|
||||
_behaviourContext.AppendError(ref updateErrorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
int currentOutputSize = _output.Length;
|
||||
|
||||
OutputHeader.SinksSize = (uint)(Unsafe.SizeOf<SinkOutStatus>() * context.GetCount());
|
||||
OutputHeader.TotalSize += OutputHeader.SinksSize;
|
||||
|
||||
Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdatePerformanceBuffer(PerformanceManager manager, Span<byte> performanceOutput)
|
||||
{
|
||||
if (Unsafe.SizeOf<PerformanceInParameter>() != _inputHeader.PerformanceBufferSize)
|
||||
{
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
PerformanceInParameter parameter = SpanIOHelper.Read<PerformanceInParameter>(ref _input);
|
||||
|
||||
ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<PerformanceOutStatus>(ref _output)[0];
|
||||
|
||||
if (manager != null)
|
||||
{
|
||||
outStatus.HistorySize = manager.CopyHistories(performanceOutput);
|
||||
|
||||
manager.SetTargetNodeId(parameter.TargetNodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
outStatus.HistorySize = 0;
|
||||
}
|
||||
|
||||
OutputHeader.PerformanceBufferSize = (uint)Unsafe.SizeOf<PerformanceOutStatus>();
|
||||
OutputHeader.TotalSize += OutputHeader.PerformanceBufferSize;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateErrorInfo()
|
||||
{
|
||||
ref BehaviourErrorInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef<BehaviourErrorInfoOutStatus>(ref _output)[0];
|
||||
|
||||
_behaviourContext.CopyErrorInfo(outStatus.ErrorInfos.AsSpan(), out outStatus.ErrorInfosCount);
|
||||
|
||||
OutputHeader.BehaviourSize = (uint)Unsafe.SizeOf<BehaviourErrorInfoOutStatus>();
|
||||
OutputHeader.TotalSize += OutputHeader.BehaviourSize;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateRendererInfo(ulong elapsedFrameCount)
|
||||
{
|
||||
ref RendererInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef<RendererInfoOutStatus>(ref _output)[0];
|
||||
|
||||
outStatus.ElapsedFrameCount = elapsedFrameCount;
|
||||
|
||||
OutputHeader.RenderInfoSize = (uint)Unsafe.SizeOf<RendererInfoOutStatus>();
|
||||
OutputHeader.TotalSize += OutputHeader.RenderInfoSize;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode CheckConsumedSize()
|
||||
{
|
||||
int consumedInputSize = _inputOrigin.Length - _input.Length;
|
||||
int consumedOutputSize = _outputOrigin.Length - _output.Length;
|
||||
|
||||
if (consumedInputSize != _inputHeader.TotalSize)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed input size mismatch (got {consumedInputSize} expected {_inputHeader.TotalSize})");
|
||||
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
if (consumedOutputSize != OutputHeader.TotalSize)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed output size mismatch (got {consumedOutputSize} expected {OutputHeader.TotalSize})");
|
||||
|
||||
return ResultCode.InvalidUpdateInfo;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
||||
{
|
||||
/// <summary>
|
||||
/// Server state for a upsampling.
|
||||
/// </summary>
|
||||
public class UpsamplerState
|
||||
{
|
||||
/// <summary>
|
||||
/// The output buffer containing the target samples.
|
||||
/// </summary>
|
||||
public Memory<float> OutputBuffer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The target sample count.
|
||||
/// </summary>
|
||||
public uint SampleCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The index of the <see cref="UpsamplerState"/>. (used to free it)
|
||||
/// </summary>
|
||||
private int _index;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="UpsamplerManager"/>.
|
||||
/// </summary>
|
||||
private UpsamplerManager _manager;
|
||||
|
||||
/// <summary>
|
||||
/// The source sample count.
|
||||
/// </summary>
|
||||
public uint SourceSampleCount;
|
||||
|
||||
/// <summary>
|
||||
/// The input buffer indices of the buffers holding the samples that need upsampling.
|
||||
/// </summary>
|
||||
public ushort[] InputBufferIndices;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="UpsamplerState"/>.
|
||||
/// </summary>
|
||||
/// <param name="manager">The upsampler manager.</param>
|
||||
/// <param name="index">The index of the <see cref="UpsamplerState"/>. (used to free it)</param>
|
||||
/// <param name="outputBuffer">The output buffer used to contain the target samples.</param>
|
||||
/// <param name="sampleCount">The target sample count.</param>
|
||||
public UpsamplerState(UpsamplerManager manager, int index, Memory<float> outputBuffer, uint sampleCount)
|
||||
{
|
||||
_manager = manager;
|
||||
_index = index;
|
||||
OutputBuffer = outputBuffer;
|
||||
SampleCount = sampleCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release the <see cref="UpsamplerState"/>.
|
||||
/// </summary>
|
||||
public void Release()
|
||||
{
|
||||
_manager.Free(_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,699 +0,0 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Renderer.Common.BehaviourParameter;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Voice
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = Alignment)]
|
||||
public struct VoiceState
|
||||
{
|
||||
public const int Alignment = 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the voice is used.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool InUse;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the voice is new.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsNew;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool WasPlaying;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SampleFormat"/> of the voice.
|
||||
/// </summary>
|
||||
public SampleFormat SampleFormat;
|
||||
|
||||
/// <summary>
|
||||
/// The sample rate of the voice.
|
||||
/// </summary>
|
||||
public uint SampleRate;
|
||||
|
||||
/// <summary>
|
||||
/// The total channel count used.
|
||||
/// </summary>
|
||||
public uint ChannelsCount;
|
||||
|
||||
/// <summary>
|
||||
/// Id of the voice.
|
||||
/// </summary>
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// Node id of the voice.
|
||||
/// </summary>
|
||||
public int NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// The target mix id of the voice.
|
||||
/// </summary>
|
||||
public int MixId;
|
||||
|
||||
/// <summary>
|
||||
/// The current voice <see cref="Types.PlayState"/>.
|
||||
/// </summary>
|
||||
public Types.PlayState PlayState;
|
||||
|
||||
/// <summary>
|
||||
/// The previous voice <see cref="Types.PlayState"/>.
|
||||
/// </summary>
|
||||
public Types.PlayState PreviousPlayState;
|
||||
|
||||
/// <summary>
|
||||
/// The priority of the voice.
|
||||
/// </summary>
|
||||
public uint Priority;
|
||||
|
||||
/// <summary>
|
||||
/// Target sorting position of the voice. (used to sort voice with the same <see cref="Priority"/>)
|
||||
/// </summary>
|
||||
public uint SortingOrder;
|
||||
|
||||
/// <summary>
|
||||
/// The pitch used on the voice.
|
||||
/// </summary>
|
||||
public float Pitch;
|
||||
|
||||
/// <summary>
|
||||
/// The output volume of the voice.
|
||||
/// </summary>
|
||||
public float Volume;
|
||||
|
||||
/// <summary>
|
||||
/// The previous output volume of the voice.
|
||||
/// </summary>
|
||||
public float PreviousVolume;
|
||||
|
||||
/// <summary>
|
||||
/// Biquad filters to apply to the output of the voice.
|
||||
/// </summary>
|
||||
public Array2<BiquadFilterParameter> BiquadFilters;
|
||||
|
||||
/// <summary>
|
||||
/// Total count of <see cref="WaveBufferInternal"/> of the voice.
|
||||
/// </summary>
|
||||
public uint WaveBuffersCount;
|
||||
|
||||
/// <summary>
|
||||
/// Current playing <see cref="WaveBufferInternal"/> of the voice.
|
||||
/// </summary>
|
||||
public uint WaveBuffersIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Change the behaviour of the voice.
|
||||
/// </summary>
|
||||
/// <remarks>This was added on REV5.</remarks>
|
||||
public DecodingBehaviour DecodingBehaviour;
|
||||
|
||||
/// <summary>
|
||||
/// User state <see cref="AddressInfo"/> required by the data source.
|
||||
/// </summary>
|
||||
/// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the GC-ADPCM coefficients.</remarks>
|
||||
public AddressInfo DataSourceStateAddressInfo;
|
||||
|
||||
/// <summary>
|
||||
/// The wavebuffers of this voice.
|
||||
/// </summary>
|
||||
public Array4<WaveBuffer> WaveBuffers;
|
||||
|
||||
/// <summary>
|
||||
/// The channel resource ids associated to the voice.
|
||||
/// </summary>
|
||||
public Array6<int> ChannelResourceIds;
|
||||
|
||||
/// <summary>
|
||||
/// The target splitter id of the voice.
|
||||
/// </summary>
|
||||
public uint SplitterId;
|
||||
|
||||
/// <summary>
|
||||
/// Change the Sample Rate Conversion (SRC) quality of the voice.
|
||||
/// </summary>
|
||||
/// <remarks>This was added on REV8.</remarks>
|
||||
public SampleRateConversionQuality SrcQuality;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the voice was dropped.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool VoiceDropFlag;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the data source state work buffer wasn't mapped.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool DataSourceStateUnmapped;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if any of the <see cref="WaveBuffer.BufferAddressInfo"/> work buffer wasn't mapped.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool BufferInfoUnmapped;
|
||||
|
||||
/// <summary>
|
||||
/// The biquad filter initialization state storage.
|
||||
/// </summary>
|
||||
private BiquadFilterNeedInitializationArrayStruct _biquadFilterNeedInitialization;
|
||||
|
||||
/// <summary>
|
||||
/// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played.
|
||||
/// </summary>
|
||||
/// <remarks>This was added on REV5.</remarks>
|
||||
public byte FlushWaveBufferCount;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Constants.VoiceBiquadFilterCount)]
|
||||
private struct BiquadFilterNeedInitializationArrayStruct { }
|
||||
|
||||
/// <summary>
|
||||
/// The biquad filter initialization state array.
|
||||
/// </summary>
|
||||
public Span<bool> BiquadFilterNeedInitialization => SpanHelpers.AsSpan<BiquadFilterNeedInitializationArrayStruct, bool>(ref _biquadFilterNeedInitialization);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
IsNew = false;
|
||||
VoiceDropFlag = false;
|
||||
DataSourceStateUnmapped = false;
|
||||
BufferInfoUnmapped = false;
|
||||
FlushWaveBufferCount = 0;
|
||||
PlayState = Types.PlayState.Stopped;
|
||||
Priority = Constants.VoiceLowestPriority;
|
||||
Id = 0;
|
||||
NodeId = 0;
|
||||
SampleRate = 0;
|
||||
SampleFormat = SampleFormat.Invalid;
|
||||
ChannelsCount = 0;
|
||||
Pitch = 0.0f;
|
||||
Volume = 0.0f;
|
||||
PreviousVolume = 0.0f;
|
||||
BiquadFilters.AsSpan().Fill(new BiquadFilterParameter());
|
||||
WaveBuffersCount = 0;
|
||||
WaveBuffersIndex = 0;
|
||||
MixId = Constants.UnusedMixId;
|
||||
SplitterId = Constants.UnusedSplitterId;
|
||||
DataSourceStateAddressInfo.Setup(0, 0);
|
||||
|
||||
InitializeWaveBuffers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="WaveBuffer"/> in this <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
private void InitializeWaveBuffers()
|
||||
{
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
WaveBuffers[i].StartSampleOffset = 0;
|
||||
WaveBuffers[i].EndSampleOffset = 0;
|
||||
WaveBuffers[i].ShouldLoop = false;
|
||||
WaveBuffers[i].IsEndOfStream = false;
|
||||
WaveBuffers[i].BufferAddressInfo.Setup(0, 0);
|
||||
WaveBuffers[i].ContextAddressInfo.Setup(0, 0);
|
||||
WaveBuffers[i].IsSendToAudioProcessor = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the voice needs to be skipped.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the voice needs to be skipped.</returns>
|
||||
public bool ShouldSkip()
|
||||
{
|
||||
return !InUse || WaveBuffersCount == 0 || DataSourceStateUnmapped || BufferInfoUnmapped || VoiceDropFlag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the mix has any destinations.
|
||||
/// </summary>
|
||||
/// <returns>True if the mix has any destinations.</returns>
|
||||
public bool HasAnyDestination()
|
||||
{
|
||||
return MixId != Constants.UnusedMixId || SplitterId != Constants.UnusedSplitterId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicate if the server voice information needs to be updated.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <returns>Return true, if the server voice information needs to be updated.</returns>
|
||||
private bool ShouldUpdateParameters(ref VoiceInParameter parameter)
|
||||
{
|
||||
if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress)
|
||||
{
|
||||
return DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize;
|
||||
}
|
||||
|
||||
return DataSourceStateAddressInfo.CpuAddress != parameter.DataSourceStateAddress ||
|
||||
DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize ||
|
||||
DataSourceStateUnmapped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state from a user parameter.
|
||||
/// </summary>
|
||||
/// <param name="outErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="poolMapper">The mapper to use.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext)
|
||||
{
|
||||
InUse = parameter.InUse;
|
||||
Id = parameter.Id;
|
||||
NodeId = parameter.NodeId;
|
||||
|
||||
UpdatePlayState(parameter.PlayState);
|
||||
|
||||
SrcQuality = parameter.SrcQuality;
|
||||
|
||||
Priority = parameter.Priority;
|
||||
SortingOrder = parameter.SortingOrder;
|
||||
SampleRate = parameter.SampleRate;
|
||||
SampleFormat = parameter.SampleFormat;
|
||||
ChannelsCount = parameter.ChannelCount;
|
||||
Pitch = parameter.Pitch;
|
||||
Volume = parameter.Volume;
|
||||
parameter.BiquadFilters.AsSpan().CopyTo(BiquadFilters.AsSpan());
|
||||
WaveBuffersCount = parameter.WaveBuffersCount;
|
||||
WaveBuffersIndex = parameter.WaveBuffersIndex;
|
||||
|
||||
if (behaviourContext.IsFlushVoiceWaveBuffersSupported())
|
||||
{
|
||||
FlushWaveBufferCount += parameter.FlushWaveBufferCount;
|
||||
}
|
||||
|
||||
MixId = parameter.MixId;
|
||||
|
||||
if (behaviourContext.IsSplitterSupported())
|
||||
{
|
||||
SplitterId = parameter.SplitterId;
|
||||
}
|
||||
else
|
||||
{
|
||||
SplitterId = Constants.UnusedSplitterId;
|
||||
}
|
||||
|
||||
parameter.ChannelResourceIds.AsSpan().CopyTo(ChannelResourceIds.AsSpan());
|
||||
|
||||
DecodingBehaviour behaviour = DecodingBehaviour.Default;
|
||||
|
||||
if (behaviourContext.IsDecodingBehaviourFlagSupported())
|
||||
{
|
||||
behaviour = parameter.DecodingBehaviourFlags;
|
||||
}
|
||||
|
||||
DecodingBehaviour = behaviour;
|
||||
|
||||
if (parameter.ResetVoiceDropFlag)
|
||||
{
|
||||
VoiceDropFlag = false;
|
||||
}
|
||||
|
||||
if (ShouldUpdateParameters(ref parameter))
|
||||
{
|
||||
DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
outErrorInfo = new ErrorInfo();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal play state from user play state.
|
||||
/// </summary>
|
||||
/// <param name="userPlayState">The target user play state.</param>
|
||||
public void UpdatePlayState(PlayState userPlayState)
|
||||
{
|
||||
Types.PlayState oldServerPlayState = PlayState;
|
||||
|
||||
PreviousPlayState = oldServerPlayState;
|
||||
|
||||
Types.PlayState newServerPlayState;
|
||||
|
||||
switch (userPlayState)
|
||||
{
|
||||
case Common.PlayState.Start:
|
||||
newServerPlayState = Types.PlayState.Started;
|
||||
break;
|
||||
|
||||
case Common.PlayState.Stop:
|
||||
if (oldServerPlayState == Types.PlayState.Stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
newServerPlayState = Types.PlayState.Stopping;
|
||||
break;
|
||||
|
||||
case Common.PlayState.Pause:
|
||||
newServerPlayState = Types.PlayState.Paused;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unhandled PlayState.{userPlayState}");
|
||||
}
|
||||
|
||||
PlayState = newServerPlayState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the status of the voice to the given user output.
|
||||
/// </summary>
|
||||
/// <param name="outStatus">The given user output.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates)
|
||||
{
|
||||
#if DEBUG
|
||||
// Sanity check in debug mode of the internal state
|
||||
if (!parameter.IsNew && !IsNew)
|
||||
{
|
||||
for (int i = 1; i < ChannelsCount; i++)
|
||||
{
|
||||
ref VoiceUpdateState stateA = ref voiceUpdateStates[i - 1].Span[0];
|
||||
ref VoiceUpdateState stateB = ref voiceUpdateStates[i].Span[0];
|
||||
|
||||
Debug.Assert(stateA.WaveBufferConsumed == stateB.WaveBufferConsumed);
|
||||
Debug.Assert(stateA.PlayedSampleCount == stateB.PlayedSampleCount);
|
||||
Debug.Assert(stateA.Offset == stateB.Offset);
|
||||
Debug.Assert(stateA.WaveBufferIndex == stateB.WaveBufferIndex);
|
||||
Debug.Assert(stateA.Fraction == stateB.Fraction);
|
||||
Debug.Assert(stateA.IsWaveBufferValid.SequenceEqual(stateB.IsWaveBufferValid));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (parameter.IsNew || IsNew)
|
||||
{
|
||||
IsNew = true;
|
||||
|
||||
outStatus.VoiceDropFlag = false;
|
||||
outStatus.PlayedWaveBuffersCount = 0;
|
||||
outStatus.PlayedSampleCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ref VoiceUpdateState state = ref voiceUpdateStates[0].Span[0];
|
||||
|
||||
outStatus.VoiceDropFlag = VoiceDropFlag;
|
||||
outStatus.PlayedWaveBuffersCount = state.WaveBufferConsumed;
|
||||
outStatus.PlayedSampleCount = state.PlayedSampleCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state of all the <see cref="WaveBuffer"/> of the <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
/// <param name="errorInfos">An array of <see cref="ErrorInfo"/> used to report errors when mapping any of the <see cref="WaveBuffer"/>.</param>
|
||||
/// <param name="parameter">The user parameter.</param>
|
||||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
|
||||
{
|
||||
errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2];
|
||||
|
||||
if (parameter.IsNew)
|
||||
{
|
||||
InitializeWaveBuffers();
|
||||
|
||||
for (int i = 0; i < parameter.ChannelCount; i++)
|
||||
{
|
||||
voiceUpdateStates[i].Span[0].IsWaveBufferValid.Fill(false);
|
||||
}
|
||||
}
|
||||
|
||||
ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0];
|
||||
|
||||
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
|
||||
{
|
||||
UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state of one of the <see cref="WaveBuffer"/> of the <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
/// <param name="errorInfos">A <see cref="Span{ErrorInfo}"/> used to report errors when mapping the <see cref="WaveBuffer"/>.</param>
|
||||
/// <param name="waveBuffer">The <see cref="WaveBuffer"/> to update.</param>
|
||||
/// <param name="inputWaveBuffer">The <see cref="WaveBufferInternal"/> from the user input.</param>
|
||||
/// <param name="sampleFormat">The <see cref="SampleFormat"/> from the user input.</param>
|
||||
/// <param name="isValid">If set to true, the server side wavebuffer is considered valid.</param>
|
||||
/// <param name="mapper">The mapper to use.</param>
|
||||
/// <param name="behaviourContext">The behaviour context.</param>
|
||||
private void UpdateWaveBuffer(Span<ErrorInfo> errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
|
||||
{
|
||||
if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0)
|
||||
{
|
||||
mapper.ForceUnmap(ref waveBuffer.BufferAddressInfo);
|
||||
waveBuffer.BufferAddressInfo.Setup(0, 0);
|
||||
}
|
||||
|
||||
if (!inputWaveBuffer.SentToServer || BufferInfoUnmapped)
|
||||
{
|
||||
if (inputWaveBuffer.IsSampleOffsetValid(sampleFormat))
|
||||
{
|
||||
Debug.Assert(waveBuffer.IsSendToAudioProcessor);
|
||||
|
||||
waveBuffer.IsSendToAudioProcessor = false;
|
||||
waveBuffer.StartSampleOffset = inputWaveBuffer.StartSampleOffset;
|
||||
waveBuffer.EndSampleOffset = inputWaveBuffer.EndSampleOffset;
|
||||
waveBuffer.ShouldLoop = inputWaveBuffer.ShouldLoop;
|
||||
waveBuffer.IsEndOfStream = inputWaveBuffer.IsEndOfStream;
|
||||
waveBuffer.LoopStartSampleOffset = inputWaveBuffer.LoopFirstSampleOffset;
|
||||
waveBuffer.LoopEndSampleOffset = inputWaveBuffer.LoopLastSampleOffset;
|
||||
waveBuffer.LoopCount = inputWaveBuffer.LoopCount;
|
||||
|
||||
BufferInfoUnmapped = !mapper.TryAttachBuffer(out ErrorInfo bufferInfoError, ref waveBuffer.BufferAddressInfo, inputWaveBuffer.Address, inputWaveBuffer.Size);
|
||||
|
||||
errorInfos[0] = bufferInfoError;
|
||||
|
||||
if (sampleFormat == SampleFormat.Adpcm && behaviourContext.IsAdpcmLoopContextBugFixed() && inputWaveBuffer.ContextAddress != 0)
|
||||
{
|
||||
bool adpcmLoopContextMapped = mapper.TryAttachBuffer(out ErrorInfo adpcmLoopContextInfoError,
|
||||
ref waveBuffer.ContextAddressInfo,
|
||||
inputWaveBuffer.ContextAddress,
|
||||
inputWaveBuffer.ContextSize);
|
||||
|
||||
errorInfos[1] = adpcmLoopContextInfoError;
|
||||
|
||||
if (adpcmLoopContextMapped)
|
||||
{
|
||||
BufferInfoUnmapped = DataSourceStateUnmapped;
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferInfoUnmapped = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
waveBuffer.ContextAddressInfo.Setup(0, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorInfos[0].ErrorCode = ResultCode.InvalidAddressInfo;
|
||||
errorInfos[0].ExtraErrorInfo = inputWaveBuffer.Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the resources associated to this <see cref="VoiceState"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The voice context.</param>
|
||||
private void ResetResources(VoiceContext context)
|
||||
{
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
int channelResourceId = ChannelResourceIds[i];
|
||||
|
||||
ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId);
|
||||
|
||||
Debug.Assert(voiceChannelResource.IsUsed);
|
||||
|
||||
Memory<VoiceUpdateState> dspSharedState = context.GetUpdateStateForDsp(channelResourceId);
|
||||
|
||||
MemoryMarshal.Cast<VoiceUpdateState, byte>(dspSharedState.Span).Fill(0);
|
||||
|
||||
voiceChannelResource.UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush a certain amount of <see cref="WaveBuffer"/>.
|
||||
/// </summary>
|
||||
/// <param name="waveBufferCount">The amount of wavebuffer to flush.</param>
|
||||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
/// <param name="channelCount">The channel count from user input.</param>
|
||||
private void FlushWaveBuffers(uint waveBufferCount, Memory<VoiceUpdateState>[] voiceUpdateStates, uint channelCount)
|
||||
{
|
||||
uint waveBufferIndex = WaveBuffersIndex;
|
||||
|
||||
for (int i = 0; i < waveBufferCount; i++)
|
||||
{
|
||||
WaveBuffers[(int)waveBufferIndex].IsSendToAudioProcessor = true;
|
||||
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
{
|
||||
ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0];
|
||||
|
||||
voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount;
|
||||
voiceUpdateState.WaveBufferConsumed++;
|
||||
voiceUpdateState.IsWaveBufferValid[(int)waveBufferIndex] = false;
|
||||
}
|
||||
|
||||
waveBufferIndex = (waveBufferIndex + 1) % Constants.VoiceWaveBufferCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal parameters for command generation.
|
||||
/// </summary>
|
||||
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
|
||||
/// <returns>Return true if this voice should be played.</returns>
|
||||
public bool UpdateParametersForCommandGeneration(Memory<VoiceUpdateState>[] voiceUpdateStates)
|
||||
{
|
||||
if (FlushWaveBufferCount != 0)
|
||||
{
|
||||
FlushWaveBuffers(FlushWaveBufferCount, voiceUpdateStates, ChannelsCount);
|
||||
|
||||
FlushWaveBufferCount = 0;
|
||||
}
|
||||
|
||||
switch (PlayState)
|
||||
{
|
||||
case Types.PlayState.Started:
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
ref WaveBuffer wavebuffer = ref WaveBuffers[i];
|
||||
|
||||
if (!wavebuffer.IsSendToAudioProcessor)
|
||||
{
|
||||
for (int y = 0; y < ChannelsCount; y++)
|
||||
{
|
||||
Debug.Assert(!voiceUpdateStates[y].Span[0].IsWaveBufferValid[i]);
|
||||
|
||||
voiceUpdateStates[y].Span[0].IsWaveBufferValid[i] = true;
|
||||
}
|
||||
|
||||
wavebuffer.IsSendToAudioProcessor = true;
|
||||
}
|
||||
}
|
||||
|
||||
WasPlaying = false;
|
||||
|
||||
ref VoiceUpdateState primaryVoiceUpdateState = ref voiceUpdateStates[0].Span[0];
|
||||
|
||||
for (int i = 0; i < primaryVoiceUpdateState.IsWaveBufferValid.Length; i++)
|
||||
{
|
||||
if (primaryVoiceUpdateState.IsWaveBufferValid[i])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
case Types.PlayState.Stopping:
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
ref WaveBuffer wavebuffer = ref WaveBuffers[i];
|
||||
|
||||
wavebuffer.IsSendToAudioProcessor = true;
|
||||
|
||||
for (int j = 0; j < ChannelsCount; j++)
|
||||
{
|
||||
ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0];
|
||||
|
||||
if (voiceUpdateState.IsWaveBufferValid[i])
|
||||
{
|
||||
voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount;
|
||||
voiceUpdateState.WaveBufferConsumed++;
|
||||
}
|
||||
|
||||
voiceUpdateState.IsWaveBufferValid[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[i].Span[0];
|
||||
|
||||
voiceUpdateState.Offset = 0;
|
||||
voiceUpdateState.PlayedSampleCount = 0;
|
||||
voiceUpdateState.Pitch.AsSpan().Fill(0);
|
||||
voiceUpdateState.Fraction = 0;
|
||||
voiceUpdateState.LoopContext = new Dsp.State.AdpcmLoopContext();
|
||||
}
|
||||
|
||||
PlayState = Types.PlayState.Stopped;
|
||||
WasPlaying = PreviousPlayState == Types.PlayState.Started;
|
||||
|
||||
return WasPlaying;
|
||||
|
||||
case Types.PlayState.Stopped:
|
||||
case Types.PlayState.Paused:
|
||||
foreach (ref WaveBuffer wavebuffer in WaveBuffers.AsSpan())
|
||||
{
|
||||
wavebuffer.BufferAddressInfo.GetReference(true);
|
||||
wavebuffer.ContextAddressInfo.GetReference(true);
|
||||
}
|
||||
|
||||
if (SampleFormat == SampleFormat.Adpcm)
|
||||
{
|
||||
if (DataSourceStateAddressInfo.CpuAddress != 0)
|
||||
{
|
||||
DataSourceStateAddressInfo.GetReference(true);
|
||||
}
|
||||
}
|
||||
|
||||
WasPlaying = PreviousPlayState == Types.PlayState.Started;
|
||||
|
||||
return WasPlaying;
|
||||
default:
|
||||
throw new NotImplementedException($"{PlayState}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the internal state for command generation.
|
||||
/// </summary>
|
||||
/// <param name="context">The voice context.</param>
|
||||
/// <returns>Return true if this voice should be played.</returns>
|
||||
public bool UpdateForCommandGeneration(VoiceContext context)
|
||||
{
|
||||
if (IsNew)
|
||||
{
|
||||
ResetResources(context);
|
||||
PreviousVolume = Volume;
|
||||
IsNew = false;
|
||||
}
|
||||
|
||||
Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax];
|
||||
|
||||
for (int i = 0; i < ChannelsCount; i++)
|
||||
{
|
||||
voiceUpdateStates[i] = context.GetUpdateStateForDsp(ChannelResourceIds[i]);
|
||||
}
|
||||
|
||||
return UpdateParametersForCommandGeneration(voiceUpdateStates);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user