commit 4d37254f2b2309f8627089b2e245d36fc47caa2f Author: stossy11 <69031796+stossy11@users.noreply.github.com> Date: Thu Oct 3 15:29:55 2024 +1000 cool diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..ae4c97662 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,272 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +[*] + +#### Core EditorConfig Options #### + +# Set default charset +charset = utf-8 + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = lf +insert_final_newline = true + +# JSON files +[*.json] + +# Indentation and spacing +indent_size = 2 +tab_width = 2 + +# C# files +[*.cs] + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:silent + +#### 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 +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_switch_expression = false:silent + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_method_group_conversion = true + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_implicit_object_creation_when_type_is_apparent = true + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interfaces_should_be_prefixed_with_I.severity = suggestion +dotnet_naming_rule.interfaces_should_be_prefixed_with_I.symbols = interface +dotnet_naming_rule.interfaces_should_be_prefixed_with_I.style = IPascalCase + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = PascalCase + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = PascalCase + +dotnet_naming_rule.private_static_readonly_fields_should_be_camel_case_and_prefixed_with__.symbols = private_static_readonly_fields +dotnet_naming_rule.private_static_readonly_fields_should_be_camel_case_and_prefixed_with__.severity = suggestion +dotnet_naming_rule.private_static_readonly_fields_should_be_camel_case_and_prefixed_with__.style = _camelCase + +dotnet_naming_rule.local_constants_should_be_pascal_case.symbols = local_constants +dotnet_naming_rule.local_constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_constants_should_be_pascal_case.style = PascalCase + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static, readonly + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.applicable_accessibilities = local +dotnet_naming_symbols.local_constants.required_modifiers = const + +# Naming styles + +dotnet_naming_style._camelCase.required_prefix = _ +dotnet_naming_style._camelCase.required_suffix = +dotnet_naming_style._camelCase.word_separator = +dotnet_naming_style._camelCase.capitalization = camel_case + +dotnet_naming_style.PascalCase.required_prefix = +dotnet_naming_style.PascalCase.required_suffix = +dotnet_naming_style.PascalCase.word_separator = +dotnet_naming_style.PascalCase.capitalization = pascal_case + +dotnet_naming_style.IPascalCase.required_prefix = I +dotnet_naming_style.IPascalCase.required_suffix = +dotnet_naming_style.IPascalCase.word_separator = +dotnet_naming_style.IPascalCase.capitalization = pascal_case + +# TODO: +# .NET 8 migration (new warnings are caused by the NET 8 C# compiler and analyzer) +# The following info messages might need to be fixed in the source code instead of hiding the actual message +# Without the following lines, dotnet format would fail +# Disable "Collection initialization can be simplified" +dotnet_diagnostic.IDE0028.severity = none +dotnet_diagnostic.IDE0300.severity = none +dotnet_diagnostic.IDE0301.severity = none +dotnet_diagnostic.IDE0302.severity = none +dotnet_diagnostic.IDE0305.severity = none +# Disable "'new' expression can be simplified" +dotnet_diagnostic.IDE0090.severity = none +# Disable "Use primary constructor" +dotnet_diagnostic.IDE0290.severity = none +# Disable "Member '' does not access instance data and can be marked as static" +dotnet_diagnostic.CA1822.severity = none +# Disable "Change type of field '' from '' to '' for improved performance" +dotnet_diagnostic.CA1859.severity = none +# Disable "Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array" +dotnet_diagnostic.CA1861.severity = none +# Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'" +dotnet_diagnostic.CA1862.severity = none + +[src/Ryujinx.HLE/HOS/Services/**.cs] +# Disable "mark members as static" rule for services +dotnet_diagnostic.CA1822.severity = none + +[src/Ryujinx.Ava/UI/ViewModels/**.cs] +# Disable "mark members as static" rule for ViewModels +dotnet_diagnostic.CA1822.severity = none + +[src/Ryujinx.Tests/Cpu/*.cs] +# Disable naming rules for CPU tests +dotnet_diagnostic.IDE1006.severity = none diff --git a/.git-2/HEAD b/.git-2/HEAD new file mode 100644 index 000000000..eca458f25 --- /dev/null +++ b/.git-2/HEAD @@ -0,0 +1 @@ +ref: refs/heads/libryujinx_ios diff --git a/.git-2/config b/.git-2/config new file mode 100644 index 000000000..d3059572c --- /dev/null +++ b/.git-2/config @@ -0,0 +1,15 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[submodule] + active = . +[remote "origin"] + url = https://github.com/Spidy123222/LibRyujinx + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "libryujinx_ios"] + remote = origin + merge = refs/heads/libryujinx_ios diff --git a/.git-2/description b/.git-2/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/.git-2/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/.git-2/hooks/applypatch-msg.sample b/.git-2/hooks/applypatch-msg.sample new file mode 100755 index 000000000..a5d7b84a6 --- /dev/null +++ b/.git-2/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/.git-2/hooks/commit-msg.sample b/.git-2/hooks/commit-msg.sample new file mode 100755 index 000000000..b58d1184a --- /dev/null +++ b/.git-2/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/.git-2/hooks/fsmonitor-watchman.sample b/.git-2/hooks/fsmonitor-watchman.sample new file mode 100755 index 000000000..23e856f5d --- /dev/null +++ b/.git-2/hooks/fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/.git-2/hooks/post-update.sample b/.git-2/hooks/post-update.sample new file mode 100755 index 000000000..ec17ec193 --- /dev/null +++ b/.git-2/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/.git-2/hooks/pre-applypatch.sample b/.git-2/hooks/pre-applypatch.sample new file mode 100755 index 000000000..4142082bc --- /dev/null +++ b/.git-2/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/.git-2/hooks/pre-commit.sample b/.git-2/hooks/pre-commit.sample new file mode 100755 index 000000000..e144712c8 --- /dev/null +++ b/.git-2/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/.git-2/hooks/pre-merge-commit.sample b/.git-2/hooks/pre-merge-commit.sample new file mode 100755 index 000000000..399eab192 --- /dev/null +++ b/.git-2/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/.git-2/hooks/pre-push.sample b/.git-2/hooks/pre-push.sample new file mode 100755 index 000000000..4ce688d32 --- /dev/null +++ b/.git-2/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/.git-2/hooks/pre-rebase.sample b/.git-2/hooks/pre-rebase.sample new file mode 100755 index 000000000..6cbef5c37 --- /dev/null +++ b/.git-2/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/.git-2/hooks/pre-receive.sample b/.git-2/hooks/pre-receive.sample new file mode 100755 index 000000000..a1fd29ec1 --- /dev/null +++ b/.git-2/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/.git-2/hooks/prepare-commit-msg.sample b/.git-2/hooks/prepare-commit-msg.sample new file mode 100755 index 000000000..10fa14c5a --- /dev/null +++ b/.git-2/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/.git-2/hooks/push-to-checkout.sample b/.git-2/hooks/push-to-checkout.sample new file mode 100755 index 000000000..af5a0c001 --- /dev/null +++ b/.git-2/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/.git-2/index b/.git-2/index new file mode 100644 index 000000000..f4df63d39 Binary files /dev/null and b/.git-2/index differ diff --git a/.git-2/info/exclude b/.git-2/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/.git-2/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/.git-2/logs/HEAD b/.git-2/logs/HEAD new file mode 100644 index 000000000..673204fad --- /dev/null +++ b/.git-2/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 946166b058a6179727ef8b1ef92d8cc327809496 stossy11 <69031796+stossy11@users.noreply.github.com> 1727869894 +1000 clone: from https://github.com/Spidy123222/LibRyujinx diff --git a/.git-2/logs/refs/heads/libryujinx_ios b/.git-2/logs/refs/heads/libryujinx_ios new file mode 100644 index 000000000..673204fad --- /dev/null +++ b/.git-2/logs/refs/heads/libryujinx_ios @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 946166b058a6179727ef8b1ef92d8cc327809496 stossy11 <69031796+stossy11@users.noreply.github.com> 1727869894 +1000 clone: from https://github.com/Spidy123222/LibRyujinx diff --git a/.git-2/logs/refs/remotes/origin/HEAD b/.git-2/logs/refs/remotes/origin/HEAD new file mode 100644 index 000000000..c3dd3b721 --- /dev/null +++ b/.git-2/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 a1cb1dfdc96224da59d19b5519551bcaff7c09ac stossy11 <69031796+stossy11@users.noreply.github.com> 1727869894 +1000 clone: from https://github.com/Spidy123222/LibRyujinx diff --git a/.git-2/objects/00/2846c6fb0c3dab1033273b695f770d58567d03 b/.git-2/objects/00/2846c6fb0c3dab1033273b695f770d58567d03 new file mode 100644 index 000000000..779e8e74c Binary files /dev/null and b/.git-2/objects/00/2846c6fb0c3dab1033273b695f770d58567d03 differ diff --git a/.git-2/objects/00/8e8e356c629f48e3e140d9b986ee711f395528 b/.git-2/objects/00/8e8e356c629f48e3e140d9b986ee711f395528 new file mode 100644 index 000000000..a8d6dfd19 Binary files /dev/null and b/.git-2/objects/00/8e8e356c629f48e3e140d9b986ee711f395528 differ diff --git a/.git-2/objects/01/646449caedc958effc3b450360aefd260671ab b/.git-2/objects/01/646449caedc958effc3b450360aefd260671ab new file mode 100644 index 000000000..6c1cd6542 Binary files /dev/null and b/.git-2/objects/01/646449caedc958effc3b450360aefd260671ab differ diff --git a/.git-2/objects/02/9ffd86b92cafa4e861fdfb4260bb865254fa68 b/.git-2/objects/02/9ffd86b92cafa4e861fdfb4260bb865254fa68 new file mode 100644 index 000000000..c992bbd98 --- /dev/null +++ b/.git-2/objects/02/9ffd86b92cafa4e861fdfb4260bb865254fa68 @@ -0,0 +1,4 @@ +xmQo0]c1F":X詍6+8ͽwpp_)GgP9z,e޳5v݋>9ix"%g b!xf!렵|( +TUv"Ҫ0ǡ.M5L ,nt'g89,VD1\M(ECST +f;Q|e"/uH^Y*liinIJ4ЉJLCO5m\mv۲z+6DJ͙n8U<z($Gаl m=_ +iOjƃvZI \ No newline at end of file diff --git a/.git-2/objects/05/399984c2c04a05dbc838b39c6c95a77d85458d b/.git-2/objects/05/399984c2c04a05dbc838b39c6c95a77d85458d new file mode 100644 index 000000000..4bd5e0b0d Binary files /dev/null and b/.git-2/objects/05/399984c2c04a05dbc838b39c6c95a77d85458d differ diff --git a/.git-2/objects/05/6fa78b8a58d04e90ef834960db0114f41b8703 b/.git-2/objects/05/6fa78b8a58d04e90ef834960db0114f41b8703 new file mode 100644 index 000000000..dd4303271 Binary files /dev/null and b/.git-2/objects/05/6fa78b8a58d04e90ef834960db0114f41b8703 differ diff --git a/.git-2/objects/0a/345a6f35bebb70127dd307d159c5719cc40804 b/.git-2/objects/0a/345a6f35bebb70127dd307d159c5719cc40804 new file mode 100644 index 000000000..4fd18e303 Binary files /dev/null and b/.git-2/objects/0a/345a6f35bebb70127dd307d159c5719cc40804 differ diff --git a/.git-2/objects/0f/e69526a10c7c2e937c7889c2bf8a81f148e877 b/.git-2/objects/0f/e69526a10c7c2e937c7889c2bf8a81f148e877 new file mode 100644 index 000000000..cbbb904bf Binary files /dev/null and b/.git-2/objects/0f/e69526a10c7c2e937c7889c2bf8a81f148e877 differ diff --git a/.git-2/objects/10/038f1b70cf28677eeb706f968d126ba8bb9db9 b/.git-2/objects/10/038f1b70cf28677eeb706f968d126ba8bb9db9 new file mode 100644 index 000000000..d55fb1aa1 Binary files /dev/null and b/.git-2/objects/10/038f1b70cf28677eeb706f968d126ba8bb9db9 differ diff --git a/.git-2/objects/14/9c35cfefe2ba54ca06602fa4447c7fa54d6f12 b/.git-2/objects/14/9c35cfefe2ba54ca06602fa4447c7fa54d6f12 new file mode 100644 index 000000000..7b8966728 Binary files /dev/null and b/.git-2/objects/14/9c35cfefe2ba54ca06602fa4447c7fa54d6f12 differ diff --git a/.git-2/objects/1e/81697b587683ddeb520b9bf3cf3cd6c7dd3b13 b/.git-2/objects/1e/81697b587683ddeb520b9bf3cf3cd6c7dd3b13 new file mode 100644 index 000000000..58cd8bf66 Binary files /dev/null and b/.git-2/objects/1e/81697b587683ddeb520b9bf3cf3cd6c7dd3b13 differ diff --git a/.git-2/objects/21/4c44c40c4f20ac7146bc07b10b7f0a97193dd4 b/.git-2/objects/21/4c44c40c4f20ac7146bc07b10b7f0a97193dd4 new file mode 100644 index 000000000..a8b5a9e02 Binary files /dev/null and b/.git-2/objects/21/4c44c40c4f20ac7146bc07b10b7f0a97193dd4 differ diff --git a/.git-2/objects/24/bb4019981946f572bd0c5a3a972e212673f7be b/.git-2/objects/24/bb4019981946f572bd0c5a3a972e212673f7be new file mode 100644 index 000000000..5269eddd2 Binary files /dev/null and b/.git-2/objects/24/bb4019981946f572bd0c5a3a972e212673f7be differ diff --git a/.git-2/objects/24/e7672e31d23c4908128135c3eb85893bc46382 b/.git-2/objects/24/e7672e31d23c4908128135c3eb85893bc46382 new file mode 100644 index 000000000..02288f82a Binary files /dev/null and b/.git-2/objects/24/e7672e31d23c4908128135c3eb85893bc46382 differ diff --git a/.git-2/objects/28/c34bc7052f3d4609729bebd8f6152bdbd8f829 b/.git-2/objects/28/c34bc7052f3d4609729bebd8f6152bdbd8f829 new file mode 100644 index 000000000..cb829fe0f Binary files /dev/null and b/.git-2/objects/28/c34bc7052f3d4609729bebd8f6152bdbd8f829 differ diff --git a/.git-2/objects/2d/658007db8b91bc012d75468435581d76f8855a b/.git-2/objects/2d/658007db8b91bc012d75468435581d76f8855a new file mode 100644 index 000000000..e6a82d890 Binary files /dev/null and b/.git-2/objects/2d/658007db8b91bc012d75468435581d76f8855a differ diff --git a/.git-2/objects/2e/f1d765ddf0968a646f51bef28992841ee1e3a3 b/.git-2/objects/2e/f1d765ddf0968a646f51bef28992841ee1e3a3 new file mode 100644 index 000000000..228a69ef6 Binary files /dev/null and b/.git-2/objects/2e/f1d765ddf0968a646f51bef28992841ee1e3a3 differ diff --git a/.git-2/objects/32/7ae95a511cc5f9ac93f4cd5e81fb3acd464850 b/.git-2/objects/32/7ae95a511cc5f9ac93f4cd5e81fb3acd464850 new file mode 100644 index 000000000..3d4924c38 Binary files /dev/null and b/.git-2/objects/32/7ae95a511cc5f9ac93f4cd5e81fb3acd464850 differ diff --git a/.git-2/objects/36/691f5531375cdfaf78fe8b8fee3721e39bb2c7 b/.git-2/objects/36/691f5531375cdfaf78fe8b8fee3721e39bb2c7 new file mode 100644 index 000000000..387a6e24b Binary files /dev/null and b/.git-2/objects/36/691f5531375cdfaf78fe8b8fee3721e39bb2c7 differ diff --git a/.git-2/objects/39/8339b871446e5c1a414ff363c4da7605ccde0e b/.git-2/objects/39/8339b871446e5c1a414ff363c4da7605ccde0e new file mode 100644 index 000000000..3121ab621 Binary files /dev/null and b/.git-2/objects/39/8339b871446e5c1a414ff363c4da7605ccde0e differ diff --git a/.git-2/objects/3d/731f50507705ca2dacbe99f473fe475aab5e2c b/.git-2/objects/3d/731f50507705ca2dacbe99f473fe475aab5e2c new file mode 100644 index 000000000..9a112f811 Binary files /dev/null and b/.git-2/objects/3d/731f50507705ca2dacbe99f473fe475aab5e2c differ diff --git a/.git-2/objects/3f/55fc338260da982be385558e0f74288bc2209d b/.git-2/objects/3f/55fc338260da982be385558e0f74288bc2209d new file mode 100644 index 000000000..9630863a7 Binary files /dev/null and b/.git-2/objects/3f/55fc338260da982be385558e0f74288bc2209d differ diff --git a/.git-2/objects/3f/ef2d2664d4f5a2352e6e48f538797983113c84 b/.git-2/objects/3f/ef2d2664d4f5a2352e6e48f538797983113c84 new file mode 100644 index 000000000..fdb330baf Binary files /dev/null and b/.git-2/objects/3f/ef2d2664d4f5a2352e6e48f538797983113c84 differ diff --git a/.git-2/objects/42/6796ef2a7eaf278d3f8580490c23c9640b69c8 b/.git-2/objects/42/6796ef2a7eaf278d3f8580490c23c9640b69c8 new file mode 100644 index 000000000..43cfe9527 Binary files /dev/null and b/.git-2/objects/42/6796ef2a7eaf278d3f8580490c23c9640b69c8 differ diff --git a/.git-2/objects/42/850b432dddfb6ac70b1b623f5f2d000f9bd21a b/.git-2/objects/42/850b432dddfb6ac70b1b623f5f2d000f9bd21a new file mode 100644 index 000000000..a481d4899 Binary files /dev/null and b/.git-2/objects/42/850b432dddfb6ac70b1b623f5f2d000f9bd21a differ diff --git a/.git-2/objects/49/e3f9da07a500ad2facc15cfc829a488ff93b49 b/.git-2/objects/49/e3f9da07a500ad2facc15cfc829a488ff93b49 new file mode 100644 index 000000000..19c58d590 Binary files /dev/null and b/.git-2/objects/49/e3f9da07a500ad2facc15cfc829a488ff93b49 differ diff --git a/.git-2/objects/4a/7b4514f598cc76ecd9077f9533fe55b1977aa8 b/.git-2/objects/4a/7b4514f598cc76ecd9077f9533fe55b1977aa8 new file mode 100644 index 000000000..5598b8ec1 Binary files /dev/null and b/.git-2/objects/4a/7b4514f598cc76ecd9077f9533fe55b1977aa8 differ diff --git a/.git-2/objects/4e/70e1edecf097e00a09c9efc0b60b9a4fbe1841 b/.git-2/objects/4e/70e1edecf097e00a09c9efc0b60b9a4fbe1841 new file mode 100644 index 000000000..ec103140a Binary files /dev/null and b/.git-2/objects/4e/70e1edecf097e00a09c9efc0b60b9a4fbe1841 differ diff --git a/.git-2/objects/57/7e5610c0afc00db7ad9ab2e11b61588b7f0e90 b/.git-2/objects/57/7e5610c0afc00db7ad9ab2e11b61588b7f0e90 new file mode 100644 index 000000000..8346b3b8f Binary files /dev/null and b/.git-2/objects/57/7e5610c0afc00db7ad9ab2e11b61588b7f0e90 differ diff --git a/.git-2/objects/58/f5b4d1953ca79b30ba8f4d1d930fca61bc2e30 b/.git-2/objects/58/f5b4d1953ca79b30ba8f4d1d930fca61bc2e30 new file mode 100644 index 000000000..3546fe520 Binary files /dev/null and b/.git-2/objects/58/f5b4d1953ca79b30ba8f4d1d930fca61bc2e30 differ diff --git a/.git-2/objects/5d/647ad665fcb9addd049d64633a740777130b17 b/.git-2/objects/5d/647ad665fcb9addd049d64633a740777130b17 new file mode 100644 index 000000000..c7e73ec24 Binary files /dev/null and b/.git-2/objects/5d/647ad665fcb9addd049d64633a740777130b17 differ diff --git a/.git-2/objects/60/338b15427a1e97c31c3d072d6af033ec7ae796 b/.git-2/objects/60/338b15427a1e97c31c3d072d6af033ec7ae796 new file mode 100644 index 000000000..1deef5293 Binary files /dev/null and b/.git-2/objects/60/338b15427a1e97c31c3d072d6af033ec7ae796 differ diff --git a/.git-2/objects/69/171ed3d7f0a6b5f4048cc2b51ba7268b62d337 b/.git-2/objects/69/171ed3d7f0a6b5f4048cc2b51ba7268b62d337 new file mode 100644 index 000000000..46b0594bf Binary files /dev/null and b/.git-2/objects/69/171ed3d7f0a6b5f4048cc2b51ba7268b62d337 differ diff --git a/.git-2/objects/6a/f76adf8b836a5f7265d033bbebf3d218d68b09 b/.git-2/objects/6a/f76adf8b836a5f7265d033bbebf3d218d68b09 new file mode 100644 index 000000000..b35e12b77 Binary files /dev/null and b/.git-2/objects/6a/f76adf8b836a5f7265d033bbebf3d218d68b09 differ diff --git a/.git-2/objects/6d/745c71c1d38f474ed88091a77bdb822f39a672 b/.git-2/objects/6d/745c71c1d38f474ed88091a77bdb822f39a672 new file mode 100644 index 000000000..5b09333aa Binary files /dev/null and b/.git-2/objects/6d/745c71c1d38f474ed88091a77bdb822f39a672 differ diff --git a/.git-2/objects/6f/3f6488f68c63b105e4fc89e8075c6e6980ba6b b/.git-2/objects/6f/3f6488f68c63b105e4fc89e8075c6e6980ba6b new file mode 100644 index 000000000..16c5dd7c5 Binary files /dev/null and b/.git-2/objects/6f/3f6488f68c63b105e4fc89e8075c6e6980ba6b differ diff --git a/.git-2/objects/6f/f35b46efe0d2fc27e05dc5b0b834940f13f2f8 b/.git-2/objects/6f/f35b46efe0d2fc27e05dc5b0b834940f13f2f8 new file mode 100644 index 000000000..318ca41fa Binary files /dev/null and b/.git-2/objects/6f/f35b46efe0d2fc27e05dc5b0b834940f13f2f8 differ diff --git a/.git-2/objects/71/662aad8383fb8a372039f5da7592409650d032 b/.git-2/objects/71/662aad8383fb8a372039f5da7592409650d032 new file mode 100644 index 000000000..e1239fd07 Binary files /dev/null and b/.git-2/objects/71/662aad8383fb8a372039f5da7592409650d032 differ diff --git a/.git-2/objects/74/3e46de111609d7b2d73b19c7fed2581c2bad7a b/.git-2/objects/74/3e46de111609d7b2d73b19c7fed2581c2bad7a new file mode 100644 index 000000000..7271a877c Binary files /dev/null and b/.git-2/objects/74/3e46de111609d7b2d73b19c7fed2581c2bad7a differ diff --git a/.git-2/objects/76/8d114ee406d6627f5da96d6c98edea85362b26 b/.git-2/objects/76/8d114ee406d6627f5da96d6c98edea85362b26 new file mode 100644 index 000000000..63dbf1ab2 Binary files /dev/null and b/.git-2/objects/76/8d114ee406d6627f5da96d6c98edea85362b26 differ diff --git a/.git-2/objects/77/8d7d301e4b8a1b545cae783443070e670a02bb b/.git-2/objects/77/8d7d301e4b8a1b545cae783443070e670a02bb new file mode 100644 index 000000000..494b75b54 Binary files /dev/null and b/.git-2/objects/77/8d7d301e4b8a1b545cae783443070e670a02bb differ diff --git a/.git-2/objects/7d/7a6ab4188aacdd552cd97735874f0e1dd7e4ff b/.git-2/objects/7d/7a6ab4188aacdd552cd97735874f0e1dd7e4ff new file mode 100644 index 000000000..19aa0de91 Binary files /dev/null and b/.git-2/objects/7d/7a6ab4188aacdd552cd97735874f0e1dd7e4ff differ diff --git a/.git-2/objects/81/061ba51cbe0cc4063f175d349e4e77ede3ed94 b/.git-2/objects/81/061ba51cbe0cc4063f175d349e4e77ede3ed94 new file mode 100644 index 000000000..7ab5774f5 Binary files /dev/null and b/.git-2/objects/81/061ba51cbe0cc4063f175d349e4e77ede3ed94 differ diff --git a/.git-2/objects/82/3124ef597aa8ed4fd90a60975e24352c40337a b/.git-2/objects/82/3124ef597aa8ed4fd90a60975e24352c40337a new file mode 100644 index 000000000..bb2e80027 Binary files /dev/null and b/.git-2/objects/82/3124ef597aa8ed4fd90a60975e24352c40337a differ diff --git a/.git-2/objects/85/27e1744f9d831d483a3f7828a99abe4182c28f b/.git-2/objects/85/27e1744f9d831d483a3f7828a99abe4182c28f new file mode 100644 index 000000000..33715b72c Binary files /dev/null and b/.git-2/objects/85/27e1744f9d831d483a3f7828a99abe4182c28f differ diff --git a/.git-2/objects/88/e72b872a732b8e72c98dcb9f71f0f458d74adc b/.git-2/objects/88/e72b872a732b8e72c98dcb9f71f0f458d74adc new file mode 100644 index 000000000..a10e69806 Binary files /dev/null and b/.git-2/objects/88/e72b872a732b8e72c98dcb9f71f0f458d74adc differ diff --git a/.git-2/objects/89/83ec05ad9542591115afbbcfa295a56aa65622 b/.git-2/objects/89/83ec05ad9542591115afbbcfa295a56aa65622 new file mode 100644 index 000000000..03a7f0020 Binary files /dev/null and b/.git-2/objects/89/83ec05ad9542591115afbbcfa295a56aa65622 differ diff --git a/.git-2/objects/94/48ce09fc705784b8da023633daeaf99ef2fa1b b/.git-2/objects/94/48ce09fc705784b8da023633daeaf99ef2fa1b new file mode 100644 index 000000000..21659e3d1 Binary files /dev/null and b/.git-2/objects/94/48ce09fc705784b8da023633daeaf99ef2fa1b differ diff --git a/.git-2/objects/97/37e98f60431dfbffd62d6eef9f99a042b8f43a b/.git-2/objects/97/37e98f60431dfbffd62d6eef9f99a042b8f43a new file mode 100644 index 000000000..50ebe503a Binary files /dev/null and b/.git-2/objects/97/37e98f60431dfbffd62d6eef9f99a042b8f43a differ diff --git a/.git-2/objects/99/f692282235ee90c0e4ed818db841cbec9498f2 b/.git-2/objects/99/f692282235ee90c0e4ed818db841cbec9498f2 new file mode 100644 index 000000000..17e5c3086 Binary files /dev/null and b/.git-2/objects/99/f692282235ee90c0e4ed818db841cbec9498f2 differ diff --git a/.git-2/objects/9e/c1ce5bfda080c3a09df91dbc4cbc28f10e8969 b/.git-2/objects/9e/c1ce5bfda080c3a09df91dbc4cbc28f10e8969 new file mode 100644 index 000000000..1ded54dcb Binary files /dev/null and b/.git-2/objects/9e/c1ce5bfda080c3a09df91dbc4cbc28f10e8969 differ diff --git a/.git-2/objects/9f/495913c6d39d0c4305b470dd86cb5f41f69a55 b/.git-2/objects/9f/495913c6d39d0c4305b470dd86cb5f41f69a55 new file mode 100644 index 000000000..5cc911b31 Binary files /dev/null and b/.git-2/objects/9f/495913c6d39d0c4305b470dd86cb5f41f69a55 differ diff --git a/.git-2/objects/a0/852b9b82b41d27cd338f86a1e2fb76ee1716b4 b/.git-2/objects/a0/852b9b82b41d27cd338f86a1e2fb76ee1716b4 new file mode 100644 index 000000000..524f7fe9b Binary files /dev/null and b/.git-2/objects/a0/852b9b82b41d27cd338f86a1e2fb76ee1716b4 differ diff --git a/.git-2/objects/a2/5308b2fde1fc43ebdae9015047dffd4a8454c4 b/.git-2/objects/a2/5308b2fde1fc43ebdae9015047dffd4a8454c4 new file mode 100644 index 000000000..22a811679 Binary files /dev/null and b/.git-2/objects/a2/5308b2fde1fc43ebdae9015047dffd4a8454c4 differ diff --git a/.git-2/objects/a5/3bbe171ed2c3db91144dba237e7aed815f07d7 b/.git-2/objects/a5/3bbe171ed2c3db91144dba237e7aed815f07d7 new file mode 100644 index 000000000..f98c3b417 Binary files /dev/null and b/.git-2/objects/a5/3bbe171ed2c3db91144dba237e7aed815f07d7 differ diff --git a/.git-2/objects/af/d55e8a32a6a14db3e43cca976b83e83cec042f b/.git-2/objects/af/d55e8a32a6a14db3e43cca976b83e83cec042f new file mode 100644 index 000000000..f95b999ab Binary files /dev/null and b/.git-2/objects/af/d55e8a32a6a14db3e43cca976b83e83cec042f differ diff --git a/.git-2/objects/b3/e69e85c74825fa64dc6c6ea77049dd0f892458 b/.git-2/objects/b3/e69e85c74825fa64dc6c6ea77049dd0f892458 new file mode 100644 index 000000000..08fec9795 Binary files /dev/null and b/.git-2/objects/b3/e69e85c74825fa64dc6c6ea77049dd0f892458 differ diff --git a/.git-2/objects/b5/5544efe8853129798e770db482c245b036a508 b/.git-2/objects/b5/5544efe8853129798e770db482c245b036a508 new file mode 100644 index 000000000..a932bc2bc Binary files /dev/null and b/.git-2/objects/b5/5544efe8853129798e770db482c245b036a508 differ diff --git a/.git-2/objects/b5/d7ebe9a36817d022ed49d85a44a2da7b64be28 b/.git-2/objects/b5/d7ebe9a36817d022ed49d85a44a2da7b64be28 new file mode 100644 index 000000000..b06bad429 Binary files /dev/null and b/.git-2/objects/b5/d7ebe9a36817d022ed49d85a44a2da7b64be28 differ diff --git a/.git-2/objects/bc/f55dc4bff42bd5546b75aad4e7fe2a42fa1bbe b/.git-2/objects/bc/f55dc4bff42bd5546b75aad4e7fe2a42fa1bbe new file mode 100644 index 000000000..42536bee7 Binary files /dev/null and b/.git-2/objects/bc/f55dc4bff42bd5546b75aad4e7fe2a42fa1bbe differ diff --git a/.git-2/objects/be/ba7f15945328ee2ad254f40195bac2593473e2 b/.git-2/objects/be/ba7f15945328ee2ad254f40195bac2593473e2 new file mode 100644 index 000000000..153f3d577 Binary files /dev/null and b/.git-2/objects/be/ba7f15945328ee2ad254f40195bac2593473e2 differ diff --git a/.git-2/objects/c7/202981b911b92040e4129595af0289f09d1caf b/.git-2/objects/c7/202981b911b92040e4129595af0289f09d1caf new file mode 100644 index 000000000..c726c4e49 Binary files /dev/null and b/.git-2/objects/c7/202981b911b92040e4129595af0289f09d1caf differ diff --git a/.git-2/objects/ca/c4a68476108f35db663336ccf54f91e6559d21 b/.git-2/objects/ca/c4a68476108f35db663336ccf54f91e6559d21 new file mode 100644 index 000000000..27aff5040 Binary files /dev/null and b/.git-2/objects/ca/c4a68476108f35db663336ccf54f91e6559d21 differ diff --git a/.git-2/objects/cc/a130b58a1b030a3953e08250305b028615e2c3 b/.git-2/objects/cc/a130b58a1b030a3953e08250305b028615e2c3 new file mode 100644 index 000000000..84b471096 Binary files /dev/null and b/.git-2/objects/cc/a130b58a1b030a3953e08250305b028615e2c3 differ diff --git a/.git-2/objects/d1/3622aae912aa244792cf26e8d05e4bd38e00fe b/.git-2/objects/d1/3622aae912aa244792cf26e8d05e4bd38e00fe new file mode 100644 index 000000000..c302d8011 Binary files /dev/null and b/.git-2/objects/d1/3622aae912aa244792cf26e8d05e4bd38e00fe differ diff --git a/.git-2/objects/d1/e3d02806337e109fea649d46e65d7b4bcd9156 b/.git-2/objects/d1/e3d02806337e109fea649d46e65d7b4bcd9156 new file mode 100644 index 000000000..295c4d9db Binary files /dev/null and b/.git-2/objects/d1/e3d02806337e109fea649d46e65d7b4bcd9156 differ diff --git a/.git-2/objects/d2/785a68158cedc69fb79be0dbb64323d566df04 b/.git-2/objects/d2/785a68158cedc69fb79be0dbb64323d566df04 new file mode 100644 index 000000000..4bdd7b2d6 Binary files /dev/null and b/.git-2/objects/d2/785a68158cedc69fb79be0dbb64323d566df04 differ diff --git a/.git-2/objects/d8/22aff1ea6b7d3d3116233c984df2ae9a9562ce b/.git-2/objects/d8/22aff1ea6b7d3d3116233c984df2ae9a9562ce new file mode 100644 index 000000000..b210d4c34 Binary files /dev/null and b/.git-2/objects/d8/22aff1ea6b7d3d3116233c984df2ae9a9562ce differ diff --git a/.git-2/objects/d8/8c0923812f379ba8d1acb5decab3ce1a3435a2 b/.git-2/objects/d8/8c0923812f379ba8d1acb5decab3ce1a3435a2 new file mode 100644 index 000000000..a0d5109af Binary files /dev/null and b/.git-2/objects/d8/8c0923812f379ba8d1acb5decab3ce1a3435a2 differ diff --git a/.git-2/objects/d9/5dbbf032187e3abf9451243f74a5b6e83e2d15 b/.git-2/objects/d9/5dbbf032187e3abf9451243f74a5b6e83e2d15 new file mode 100644 index 000000000..221bc590c Binary files /dev/null and b/.git-2/objects/d9/5dbbf032187e3abf9451243f74a5b6e83e2d15 differ diff --git a/.git-2/objects/db/b0cad3b34b4544804c0c74040e9599638b6ded b/.git-2/objects/db/b0cad3b34b4544804c0c74040e9599638b6ded new file mode 100644 index 000000000..7b6945c07 Binary files /dev/null and b/.git-2/objects/db/b0cad3b34b4544804c0c74040e9599638b6ded differ diff --git a/.git-2/objects/dc/73432f622866db5c1fff64dc2676e3f50f75d2 b/.git-2/objects/dc/73432f622866db5c1fff64dc2676e3f50f75d2 new file mode 100644 index 000000000..6a6c0285e Binary files /dev/null and b/.git-2/objects/dc/73432f622866db5c1fff64dc2676e3f50f75d2 differ diff --git a/.git-2/objects/de/e36f25ef10094c5a361b373ae169a3e94ab85e b/.git-2/objects/de/e36f25ef10094c5a361b373ae169a3e94ab85e new file mode 100644 index 000000000..c2c3cd7aa Binary files /dev/null and b/.git-2/objects/de/e36f25ef10094c5a361b373ae169a3e94ab85e differ diff --git a/.git-2/objects/e2/388d1bfc2f000cd506210c68f30ca2abe88623 b/.git-2/objects/e2/388d1bfc2f000cd506210c68f30ca2abe88623 new file mode 100644 index 000000000..25b806e54 Binary files /dev/null and b/.git-2/objects/e2/388d1bfc2f000cd506210c68f30ca2abe88623 differ diff --git a/.git-2/objects/e4/df341b2ac44ddd4f51e5f9159de7069d4df395 b/.git-2/objects/e4/df341b2ac44ddd4f51e5f9159de7069d4df395 new file mode 100644 index 000000000..247217721 Binary files /dev/null and b/.git-2/objects/e4/df341b2ac44ddd4f51e5f9159de7069d4df395 differ diff --git a/.git-2/objects/e5/d823dcc6799a3b03eb178292a557c3c5f20632 b/.git-2/objects/e5/d823dcc6799a3b03eb178292a557c3c5f20632 new file mode 100644 index 000000000..cd84bc033 Binary files /dev/null and b/.git-2/objects/e5/d823dcc6799a3b03eb178292a557c3c5f20632 differ diff --git a/.git-2/objects/ef/2f2108e7974e89448bf954ab5f2d98bc598478 b/.git-2/objects/ef/2f2108e7974e89448bf954ab5f2d98bc598478 new file mode 100644 index 000000000..6ea73670e Binary files /dev/null and b/.git-2/objects/ef/2f2108e7974e89448bf954ab5f2d98bc598478 differ diff --git a/.git-2/objects/ef/cc56d72fb8dbd3adb909052ad92b90cb834722 b/.git-2/objects/ef/cc56d72fb8dbd3adb909052ad92b90cb834722 new file mode 100644 index 000000000..a2a6a7ed2 Binary files /dev/null and b/.git-2/objects/ef/cc56d72fb8dbd3adb909052ad92b90cb834722 differ diff --git a/.git-2/objects/f1/fc9cfae3857893e295c980eeb0eb6f4b7ececc b/.git-2/objects/f1/fc9cfae3857893e295c980eeb0eb6f4b7ececc new file mode 100644 index 000000000..c5e8ed882 Binary files /dev/null and b/.git-2/objects/f1/fc9cfae3857893e295c980eeb0eb6f4b7ececc differ diff --git a/.git-2/objects/f6/ff7a00f5dcd1e235d6b1c936b8d2ee3623b782 b/.git-2/objects/f6/ff7a00f5dcd1e235d6b1c936b8d2ee3623b782 new file mode 100644 index 000000000..e261b8de8 Binary files /dev/null and b/.git-2/objects/f6/ff7a00f5dcd1e235d6b1c936b8d2ee3623b782 differ diff --git a/.git-2/objects/f9/dee0b8131f7d26b2cf77000991b43ef06c8c3c b/.git-2/objects/f9/dee0b8131f7d26b2cf77000991b43ef06c8c3c new file mode 100644 index 000000000..f95471ea8 Binary files /dev/null and b/.git-2/objects/f9/dee0b8131f7d26b2cf77000991b43ef06c8c3c differ diff --git a/.git-2/objects/fe/ea76e5f79c23546af1304def5d54dccaf16b88 b/.git-2/objects/fe/ea76e5f79c23546af1304def5d54dccaf16b88 new file mode 100644 index 000000000..fbb70667b Binary files /dev/null and b/.git-2/objects/fe/ea76e5f79c23546af1304def5d54dccaf16b88 differ diff --git a/.git-2/objects/pack/pack-9f36c8704c8540bfb6ad30e80731f709d26362d1.idx b/.git-2/objects/pack/pack-9f36c8704c8540bfb6ad30e80731f709d26362d1.idx new file mode 100644 index 000000000..ad2dfb6de Binary files /dev/null and b/.git-2/objects/pack/pack-9f36c8704c8540bfb6ad30e80731f709d26362d1.idx differ diff --git a/.git-2/objects/pack/pack-9f36c8704c8540bfb6ad30e80731f709d26362d1.pack b/.git-2/objects/pack/pack-9f36c8704c8540bfb6ad30e80731f709d26362d1.pack new file mode 100644 index 000000000..36c9d3488 Binary files /dev/null and b/.git-2/objects/pack/pack-9f36c8704c8540bfb6ad30e80731f709d26362d1.pack differ diff --git a/.git-2/packed-refs b/.git-2/packed-refs new file mode 100644 index 000000000..6b6b170a9 --- /dev/null +++ b/.git-2/packed-refs @@ -0,0 +1,12 @@ +# pack-refs with: peeled fully-peeled sorted +bf29ae1340106e1161a6fa0170b4a835892e4810 refs/remotes/origin/dependabot/github_actions/actions/github-script-7 +d851675d300e0759484794614068dd06e51597be refs/remotes/origin/dependabot/nuget/Concentus-2.2.2 +8e7bedc76e8d8134f22126a64f222daa9181c3ad refs/remotes/origin/dependabot/nuget/NUnit-4.2.2 +905e56c360289260e771a87cf397883ba0576b45 refs/remotes/origin/dependabot/nuget/NUnit3TestAdapter-4.6.0 +2fc086ac4836d7b5a3d0ed4aa2ad8fb814322e0a refs/remotes/origin/dependabot/nuget/multi-4d30b1d7d2 +ecdf3074fe5ea8312c7feba096e26aaa5c5ee8ee refs/remotes/origin/dependabot/nuget/multi-f7dbf17a22 +a1cb1dfdc96224da59d19b5519551bcaff7c09ac refs/remotes/origin/ios-ht +9c3870d62f018143bea5de0cfc1523ea5d326090 refs/remotes/origin/ios-lightning +0f1b737b0768082ca9bfa630b3748f7f8ceb09cc refs/remotes/origin/libryujinx_bionic +946166b058a6179727ef8b1ef92d8cc327809496 refs/remotes/origin/libryujinx_ios +ac0a5822c8bcb3f5c37dcae4afbabde7ee0a4756 refs/remotes/origin/libryujinx_rebased diff --git a/.git-2/refs/heads/libryujinx_ios b/.git-2/refs/heads/libryujinx_ios new file mode 100644 index 000000000..5957bce0d --- /dev/null +++ b/.git-2/refs/heads/libryujinx_ios @@ -0,0 +1 @@ +946166b058a6179727ef8b1ef92d8cc327809496 diff --git a/.git-2/refs/remotes/origin/HEAD b/.git-2/refs/remotes/origin/HEAD new file mode 100644 index 000000000..d812c749e --- /dev/null +++ b/.git-2/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/ios-ht diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..e39a7f135 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..9c8cbebd7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +patreon: Ryujinx diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..68be1f5e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..abad80a3e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ryujinx Discord + url: https://discord.gg/N2FmfVc + about: This is for development related issues. For support and technical issues, please come to our Discord server. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..383bbb151 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml b/.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml new file mode 100644 index 000000000..d815ddfd9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/missing_service_call.yml b/.github/ISSUE_TEMPLATE/missing_service_call.yml new file mode 100644 index 000000000..80aae533b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/missing_service_call.yml @@ -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 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/missing_shader_instruction.yml b/.github/ISSUE_TEMPLATE/missing_shader_instruction.yml new file mode 100644 index 000000000..df37859a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/missing_shader_instruction.yml @@ -0,0 +1,19 @@ +name: Missing Shader Instruction +description: Shader Instruction is missing in Ryujinx. +title: "[GPU]" +labels: [gpu, not-implemented] +body: + - type: textarea + id: instruction + attributes: + label: Shader instruction + description: What shader instruction is missing? + 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 diff --git a/.github/csc.json b/.github/csc.json new file mode 100644 index 000000000..2b960edd1 --- /dev/null +++ b/.github/csc.json @@ -0,0 +1,18 @@ +{ + "problemMatcher": [ + { + "owner": "csc", + "pattern": [ + { + "regexp": "^((?:\\\\|/)(?:[^\\\\/:]+(?:\\\\|/))+[^\\\\/]+)\\((\\d+),(\\d+)\\):\\s+([a-zA-Z]+)\\s+([^:]+):\\s+([^[]+)\\s+\\[", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "code": 5, + "message": 6 + } + ] + } + ] +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..1516f8a7d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,24 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + labels: + - "infra" + reviewers: + - marysaka + commit-message: + prefix: "ci" + + - package-ecosystem: nuget + directory: / + open-pull-requests-limit: 5 + schedule: + interval: daily + labels: + - "infra" + reviewers: + - marysaka + commit-message: + prefix: nuget diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..587830be1 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,33 @@ +audio: 'src/Ryujinx.Audio*/**' + +cpu: + - 'src/ARMeilleure/**' + - 'src/Ryujinx.Cpu/**' + - 'src/Ryujinx.Memory/**' + +gpu: + - 'src/Ryujinx.Graphics.*/**' + - 'src/Spv.Generator/**' + - 'src/Ryujinx.ShaderTools/**' + +'graphics-backend:opengl': 'src/Ryujinx.Graphics.OpenGL/**' +'graphics-backend:vulkan': + - 'src/Ryujinx.Graphics.Vulkan/**' + - 'src/Spv.Generator/**' + +gui: + - 'src/Ryujinx/**' + - 'src/Ryujinx.Ui.Common/**' + - 'src/Ryujinx.Ui.LocaleGenerator/**' + - 'src/Ryujinx.Ava/**' + +horizon: + - 'src/Ryujinx.HLE/**' + - 'src/Ryujinx.Horizon*/**' + +kernel: 'src/Ryujinx.HLE/HOS/Kernel/**' + +infra: + - '.github/**' + - 'distribution/**' + - 'Directory.Packages.props' \ No newline at end of file diff --git a/.github/reviewers.yml b/.github/reviewers.yml new file mode 100644 index 000000000..052594f23 --- /dev/null +++ b/.github/reviewers.yml @@ -0,0 +1,32 @@ +audio: + - marysaka + +cpu: + - gdkchan + - riperiperi + - marysaka + - LDj3SNuD + +gpu: + - gdkchan + - riperiperi + - marysaka + +gui: + - Ack77 + - emmauss + - TSRBerry + - marysaka + +horizon: + - gdkchan + - Ack77 + - marysaka + - TSRBerry + +infra: + - marysaka + - TSRBerry + +default: + - '@developers' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..7d2fbad65 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,158 @@ +name: Build job + +on: + workflow_call: + +env: + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + RYUJINX_BASE_VERSION: "1.1.0" + +jobs: + build: + name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 45 + strategy: + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + configuration: [Debug, Release] + include: + - os: ubuntu-latest + OS_NAME: Linux x64 + DOTNET_RUNTIME_IDENTIFIER: linux-x64 + RELEASE_ZIP_OS_NAME: linux_x64 + + - os: macOS-latest + OS_NAME: macOS x64 + DOTNET_RUNTIME_IDENTIFIER: osx-x64 + RELEASE_ZIP_OS_NAME: osx_x64 + + - os: windows-latest + OS_NAME: Windows x64 + DOTNET_RUNTIME_IDENTIFIER: win-x64 + RELEASE_ZIP_OS_NAME: win_x64 + + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Overwrite csc problem matcher + run: echo "::add-matcher::.github/csc.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 + uses: TSRBerry/unstable-commands@v1 + with: + commands: dotnet test --no-build -c "${{ matrix.configuration }}" + timeout-minutes: 10 + retry-codes: 139 + + - 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 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 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 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' && 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' && 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' && matrix.os != 'macOS-latest' + + build_macos: + name: macOS Universal (${{ matrix.configuration }}) + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + matrix: + configuration: [ Debug, Release ] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + 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 Ryujinx.Ava + run: | + ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" + + - name: Publish macOS Ryujinx.Headless.SDL2 + run: | + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./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' + + - 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 }}-macos_universal + path: "publish_headless/*.tar.gz" + if: github.event_name == 'pull_request' diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 000000000..4e77b58ed --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,74 @@ +name: Perform checks + +on: + pull_request: + branches: [ master ] + paths: + - '**' + - '!.github/**' + - '!*.yml' + - '!*.config' + - '!README.md' + - '.github/workflows/*.yml' + +permissions: + pull-requests: write + checks: write + +concurrency: + group: pr-checks-${{ github.event.number }} + cancel-in-progress: true + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Overwrite csc problem matcher + run: echo "::add-matcher::.github/csc.json" + + - run: dotnet restore + + - name: Print dotnet format version + run: dotnet format --version + + - name: Run dotnet format whitespace + run: | + dotnet format whitespace --verify-no-changes --report ./whitespace-report.json -v d + + # For some unknown reason this step sometimes fails with exit code 139 (segfault?), + # so in that case we'll try again (3 tries max). + - name: Run dotnet format style + uses: TSRBerry/unstable-commands@v1 + with: + commands: dotnet format style --severity info --verify-no-changes --report ./style-report.json -v d + timeout-minutes: 5 + retry-codes: 139 + + # For some unknown reason this step sometimes fails with exit code 139 (segfault?), + # so in that case we'll try again (3 tries max). + - name: Run dotnet format analyzers + uses: TSRBerry/unstable-commands@v1 + with: + commands: dotnet format analyzers --severity info --verify-no-changes --report ./analyzers-report.json -v d + timeout-minutes: 5 + retry-codes: 139 + + - name: Upload report + if: failure() + uses: actions/upload-artifact@v3 + with: + name: dotnet-format + path: ./*-report.json + + pr_build: + uses: ./.github/workflows/build.yml + needs: format + secrets: inherit diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml new file mode 100644 index 000000000..f529bea03 --- /dev/null +++ b/.github/workflows/flatpak.yml @@ -0,0 +1,174 @@ +name: Flatpak release job + +on: + workflow_call: + inputs: + ryujinx_version: + required: true + type: string + + +concurrency: flatpak-release + +jobs: + release: + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} + 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@v4 + with: + path: Ryujinx + + - uses: actions/setup-dotnet@v4 + 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@v4 + 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 + # With .NET 8.0.100, Microsoft.NET.ILLink.Tasks isn't restored by default and only seems to appears when publishing. + # So we just publish to grab the dependencies + run: dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained + + - 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='', + ) + ) + + - 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 \ No newline at end of file diff --git a/.github/workflows/nightly_pr_comment.yml b/.github/workflows/nightly_pr_comment.yml new file mode 100644 index 000000000..2d0fa598b --- /dev/null +++ b/.github/workflows/nightly_pr_comment.yml @@ -0,0 +1,71 @@ +name: Comment PR artifacts links + +on: + workflow_run: + workflows: ['Perform checks'] + types: [completed] + +jobs: + pr_comment: + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} + steps: + - uses: actions/github-script@v7 + with: + script: | + const {owner, repo} = context.repo; + const run_id = ${{github.event.workflow_run.id}}; + const pull_head_sha = '${{github.event.workflow_run.head_sha}}'; + + const issue_number = await (async () => { + const pulls = await github.rest.pulls.list({owner, repo}); + for await (const {data} of github.paginate.iterator(pulls)) { + for (const pull of data) { + if (pull.head.sha === pull_head_sha) { + return pull.number; + } + } + } + })(); + if (issue_number) { + core.info(`Using pull request ${issue_number}`); + } else { + return core.error(`No matching pull request found`); + } + + const {data: {artifacts}} = await github.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id}); + if (!artifacts.length) { + return core.error(`No artifacts found`); + } + let body = `Download the artifacts for this pull request:\n`; + let hidden_avalonia_artifacts = `\n\n
Experimental GUI (Avalonia)\n`; + let hidden_headless_artifacts = `\n\n
GUI-less (SDL2)\n`; + let hidden_debug_artifacts = `\n\n
Only for Developers\n`; + for (const art of artifacts) { + if(art.name.includes('Debug')) { + hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; + } else if(art.name.includes('ava-ryujinx')) { + hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; + } else if(art.name.includes('sdl2-ryujinx-headless')) { + hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; + } else { + body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; + } + } + hidden_avalonia_artifacts += `\n
`; + hidden_headless_artifacts += `\n
`; + hidden_debug_artifacts += `\n
`; + body += hidden_avalonia_artifacts; + body += hidden_headless_artifacts; + body += hidden_debug_artifacts; + + const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number}); + const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]'); + if (existing_comment) { + core.info(`Updating comment ${existing_comment.id}`); + await github.rest.issues.updateComment({repo, owner, comment_id: existing_comment.id, body}); + } else { + core.info(`Creating a comment`); + await github.rest.issues.createComment({repo, owner, issue_number, body}); + } \ No newline at end of file diff --git a/.github/workflows/pr_triage.yml b/.github/workflows/pr_triage.yml new file mode 100644 index 000000000..93aa89626 --- /dev/null +++ b/.github/workflows/pr_triage.yml @@ -0,0 +1,47 @@ +name: "Pull Request Triage" +on: + pull_request_target: + types: [opened, ready_for_review] + +jobs: + triage: + permissions: + contents: read + pull-requests: write + + runs-on: ubuntu-latest + + steps: + # Grab sources to get latest labeler.yml + - name: Fetch sources + uses: actions/checkout@v4 + with: + # Ensure we pin the source origin as pull_request_target run under forks. + fetch-depth: 0 + repository: Ryujinx/Ryujinx + ref: master + + - name: Checkout Ryujinx-Mako + uses: actions/checkout@v4 + with: + repository: Ryujinx/Ryujinx-Mako + ref: master + path: '.ryujinx-mako' + + - name: Setup Ryujinx-Mako + uses: ./.ryujinx-mako/.github/actions/setup-mako + + - name: Update labels based on changes + uses: actions/labeler@v5 + with: + sync-labels: true + dot: true + + - name: Assign reviewers + run: | + poetry -n -C .ryujinx-mako run ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml + shell: bash + env: + MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }} + MAKO_PRIVATE_KEY: ${{ secrets.MAKO_PRIVATE_KEY }} + MAKO_INSTALLATION_ID: ${{ secrets.MAKO_INSTALLATION_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..1e4addb6e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,218 @@ +name: Release job + +on: + workflow_dispatch: + inputs: {} + push: + branches: [ master ] + paths-ignore: + - '.github/**' + - '*.yml' + - '*.json' + - '*.config' + - 'README.md' + +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-20.04 + 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@v7 + 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: + name: Release ${{ matrix.OS_NAME }} + runs-on: ${{ matrix.os }} + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} + 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: win-x64 + RELEASE_ZIP_OS_NAME: win_x64 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Overwrite csc problem matcher + run: echo "::add-matcher::.github/csc.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;' 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 + run: | + 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_gtk + 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish + popd + + pushd publish_sdl2_headless + 7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish + popd + + pushd publish_ava + 7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish + popd + shell: bash + + - name: Packing Linux builds + if: matrix.os == 'ubuntu-latest' + run: | + 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_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_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 + + - name: Pushing new release + uses: ncipollo/release-action@v1 + with: + 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 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 }} + + macos_release: + name: Release MacOS universal + runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Setup LLVM 15 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 15 + + - 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 Ryujinx.Ava + run: | + ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release + + - name: Publish macOS Ryujinx.Headless.SDL2 + run: | + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./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, publish_headless/*.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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d8bfbc0c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,177 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates +.vs +.vscode + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Rider is a Visual Studio alternative +.idea/* + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings +packages/* +*.config + +# Include nuget.config +!nuget.config + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store + +# VS Launch Settings +launchSettings.json + +# NetCore Publishing Profiles +PublishProfiles/ + +# Glade backup files +*.glade~ + +*.xcuserstate +xcdebugger diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..366eb8435 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,147 @@ +# Contribution to Ryujinx + +You can contribute to Ryujinx with PRs, testing of PRs and issues. Contributing code and other implementations is greatly appreciated alongside simply filing issues for problems you encounter. +Please read the entire document before continuing as it can potentially save everyone involved a significant amount of time. + +# Quick Links + +* [Code Style Documentation](docs/coding-guidelines/coding-style.md) +* [Pull Request Guidelines](docs/workflow/pr-guide.md) + +## Reporting Issues + +We always welcome bug reports, feature proposals and overall feedback. Here are a few tips on how you can make reporting your issue as effective as possible. + +### Identify Where to Report + +The Ryujinx codebase is distributed across multiple repositories in the [Ryujinx organization](https://github.com/Ryujinx). Depending on the feedback you might want to file the issue on a different repo. Here are a few common repos: + +* [Ryujinx/Ryujinx](https://github.com/Ryujinx/Ryujinx) Ryujinx core project files. +* [Ryujinx/Ryujinx-Games-List](https://github.com/Ryujinx/Ryujinx-Games-List) Ryujinx game compatibility list. +* [Ryujinx/Ryujinx-Website](https://github.com/Ryujinx/Ryujinx-Website) Ryujinx website source code. +* [Ryujinx/Ryujinx-Ldn-Website](https://github.com/Ryujinx/Ryujinx-Ldn-Website) Ryujinx LDN website source code. + +### Finding Existing Issues + +Before filing a new issue, please search our [open issues](https://github.com/Ryujinx/Ryujinx/issues) to check if it already exists. + +If you do find an existing issue, please include your own feedback in the discussion. Do consider upvoting (👍 reaction) the original post, as this helps us prioritize popular issues in our backlog. + +### Writing a Good Feature Request + +Please review any feature requests already opened to both check it has not already been suggested, and to familiarize yourself with the format. When ready to submit a proposal, please use the [Feature Request issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=&projects=&template=feature_request.yml&title=%5BFeature+Request%5D). + +### Writing a Good Bug Report + +Good bug reports make it easier for maintainers to verify and root cause the underlying problem. The better a bug report, the faster the problem will be resolved. +Ideally, a bug report should contain the following information: + +* A high-level description of the problem. +* A _minimal reproduction_, i.e. the smallest time commitment/configuration required to reproduce the wrong behavior. This can be in the form of a small homebrew application, or by providing a save file and reproduction steps for a specific game. +* A description of the _expected behavior_, contrasted with the _actual behavior_ observed. +* Information on the environment: OS/distro, CPU, GPU (including driver), RAM etc. +* A Ryujinx log file of the run instance where the issue occurred. Log files can be found in `[Executable Folder]/Logs` and are named chronologically. +* Additional information, e.g. is it a regression from previous versions? Are there any known workarounds? + +When ready to submit a bug report, please use the [Bug Report issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=%5BBug%5D). + +## Contributing Changes + +Project maintainers will merge changes that both improve the project and meet our standards for code quality. + +The [Pull Request Guide](docs/workflow/pr-guide.md) and [License](https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt) docs define additional guidance. + +### DOs and DON'Ts + +Please do: + +* **DO** follow our [coding style](docs/coding-guidelines/coding-style.md) (C# code-specific). +* **DO** give priority to the current style of the project or file you're changing even if it diverges from the general guidelines. +* **DO** keep the discussions focused. When a new or related topic comes up + it's often better to create new issue than to side track the discussion. +* **DO** clearly state on an issue that you are going to take on implementing it. +* **DO** blog and tweet (or whatever) about your contributions, frequently! + +Please do not: + +* **DON'T** make PRs for style changes. +* **DON'T** surprise us with big pull requests. Instead, file an issue and talk with us on Discord to start + a discussion so we can agree on a direction before you invest a large amount + of time. +* **DON'T** commit code that you didn't write. If you find code that you think is a good fit to add to Ryujinx, file an issue or talk to us on Discord to start a discussion before proceeding. +* **DON'T** submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it. + +### Suggested Workflow + +We use and recommend the following workflow: + +1. Create or find an issue for your work. + - You can skip this step for trivial changes. + - Get agreement from the team and the community that your proposed change is a good one if it is of significant size or changes core functionality. + - Clearly state that you are going to take on implementing it, if that's the case. You can request that the issue be assigned to you. Note: The issue filer and the implementer don't have to be the same person. +2. Create a personal fork of the repository on GitHub (if you don't already have one). +3. In your fork, create a branch off of main (`git checkout -b mybranch`). + - Branches are useful since they isolate your changes from incoming changes from upstream. They also enable you to create multiple PRs from the same fork. +4. Make and commit your changes to your branch. + - [Build Instructions](https://github.com/Ryujinx/Ryujinx#building) explains how to build and test. + - Commit messages should be clear statements of action and intent. +6. Build the repository with your changes. + - Make sure that the builds are clean. + - Make sure that `dotnet format` has been run and any corrections tested and committed. +7. Create a pull request (PR) against the Ryujinx/Ryujinx repository's **main** branch. + - State in the description what issue or improvement your change is addressing. + - Check if all the Continuous Integration checks are passing. Refer to [Actions](https://github.com/Ryujinx/Ryujinx/actions) to check for outstanding errors. +8. Wait for feedback or approval of your changes from the [core development team](https://github.com/orgs/Ryujinx/teams/developers) + - Details about the pull request [review procedure](docs/workflow/ci/pr-guide.md). +9. When the team members have signed off, and all checks are green, your PR will be merged. + - The next official build will automatically include your change. + - You can delete the branch you used for making the change. + +### Good First Issues + +The team marks the most straightforward issues as [good first issues](https://github.com/Ryujinx/Ryujinx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). This set of issues is the place to start if you are interested in contributing but new to the codebase. + +### Commit Messages + +Please format commit messages as follows (based on [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)): + +``` +Summarize change in 50 characters or less + +Provide more detail after the first line. Leave one blank line below the +summary and wrap all lines at 72 characters or less. + +If the change fixes an issue, leave another blank line after the final +paragraph and indicate which issue is fixed in the specific format +below. + +Fix #42 +``` + +Also do your best to factor commits appropriately, not too large with unrelated things in the same commit, and not too small with the same small change applied N times in N different commits. + +### PR - CI Process + +The [Ryujinx continuous integration](https://github.com/Ryujinx/Ryujinx/actions) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean or have bugs properly filed against flaky/unexpected failures that are unrelated to your change. + +If the CI build fails for any reason, the PR actions tab should be consulted for further information on the failure. There are a few usual suspects for such a failure: +* `dotnet format` has not been run on the PR and has outstanding stylistic issues. +* There is an error within the PR that fails a test or errors the compiler. +* Random failure of the workflow can occasionally result in a CI failure. In this scenario a maintainer will manually restart the job. + +### PR Feedback + +Ryujinx team and community members will provide feedback on your change. Community feedback is highly valued. You may see the absence of team feedback if the community has already provided good review feedback. + +Two Ryujinx team members must review and approve every PR prior to merge. They will often reply with "LGTM, see nit". That means that the PR will be merged once the feedback is resolved. "LGTM" == "looks good to me". + +There are lots of thoughts and [approaches](https://github.com/antlr/antlr4-cpp/blob/master/CONTRIBUTING.md#emoji) for how to efficiently discuss changes. It is best to be clear and explicit with your feedback. Please be patient with people who might not understand the finer details about your approach to feedback. + +#### Copying Changes from Other Projects + +Ryujinx uses some implementations and frameworks from other projects. The following rules must be followed for PRs that include changes from another project: + +- The license of the file is [permissive](https://en.wikipedia.org/wiki/Permissive_free_software_licence). +- The license of the file is left in-tact. +- The contribution is correctly attributed in the [3rd party notices](https://github.com/Ryujinx/Ryujinx/blob/master/distribution/legal/THIRDPARTY.md) file in the repository, as needed. + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..7c9337feb --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,56 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..818ddd760 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Ryujinx Team and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..b2f95cc1f --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ + +

+
+ Ryujinx +
+ Ryujinx +
+ (REE-YOU-JINX) +
+ +

+ +

+ Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#. + This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds. + It was written from scratch and development on the project began in September 2017. Ryujinx is available on Github under the MIT license.
+ +

+

+ + + + + + + + Discord + +
+
+ +

+ +
+ +
+ +## Compatibility + +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 + +To run this emulator, your PC must be equipped with at least 8GiB of RAM; failing to meet this requirement may result in a poor gameplay experience or unexpected crashes. + +See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator. + +For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide +](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide). + +Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information. + +## Latest build + +These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.** + +If you want to see details on updates to the emulator, you can visit our [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog). + +The latest automatic build for Windows, macOS, and Linux can be found on the [Official Website](https://ryujinx.org/download). + + +## Building + +If you wish to build the emulator yourself, follow these steps: + +### Step 1 +Install the X64 version of [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0). + +### Step 2 +Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files. + +### Step 3 + +To build Ryujinx, open a command prompt inside the project directory. You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`. Then type the following command: +`dotnet build -c Release -o build` +the built files will be found in the newly created build directory. + +Ryujinx system files are stored in the `Ryujinx` folder. This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. + + +## Features + + - **Audio** + + Audio output is entirely supported, audio input (microphone) isn't supported. We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks. + +- **CPU** + + The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support. It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code. + There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster). The fastest option (host, unchecked) is set by default. + Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads. The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game. NOTE: this feature is enabled by default in the Options menu > System tab. You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch! These improvements are permanent and do not require any extra launches going forward. + +- **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 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** + + We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers. Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required. + In all scenarios, you can set up everything inside the input configuration menu. + +- **DLC & Modifications** + + Ryujinx is able to manage add-on content/downloadable content through the GUI. Mods (romfs, exefs, and runtime mods such as cheats) are also supported; the GUI contains a shortcut to open the respective mods folder for a particular game. + +- **Configuration** + + The emulator has settings for enabling or disabling some logging, remapping controllers, and more. You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. + + +## Contact + +If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx). You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions). + +## Donations + +If you'd like to support the project financially, Ryujinx has an active Patreon campaign. + + + + + +All developers working on the project do so in their free time, but the project has several expenses: +* Hackable Nintendo Switch consoles to reverse-engineer the hardware +* Additional computer hardware for testing purposes (e.g. GPUs to diagnose graphical bugs, etc.) +* Licenses for various software development tools (e.g. Jetbrains, IDA) +* Web hosting and infrastructure maintenance (e.g. LDN servers) + +All funds received through Patreon are considered a donation to support the project. Patrons receive early access to progress reports and exclusive access to developer interviews. + +## License + +This software is licensed under the terms of the MIT license.
+This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3. +See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details. +## Credits + +- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system. +- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation. +- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes. +- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation. diff --git a/Ryujinx.sln b/Ryujinx.sln new file mode 100644 index 000000000..8ef4cba80 --- /dev/null +++ b/Ryujinx.sln @@ -0,0 +1,277 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Unicorn", "src\Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj", "{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE", "src\Ryujinx.HLE\Ryujinx.HLE.csproj", "{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.ShaderTools", "src\Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "src\Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "src\ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Gpu", "src\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj", "{ADA7EA87-0D63-4D97-9433-922A2124401F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.GAL", "src\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj", "{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.OpenGL", "src\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj", "{9558FB96-075D-4219-8FFF-401979DC0B69}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Texture", "src\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj", "{E1B1AD28-289D-47B7-A106-326972240207}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "src\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec", "src\Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "src\Ryujinx.Audio\Ryujinx.Audio.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + Directory.Packages.props = Directory.Packages.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "src\Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Memory", "src\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj", "{D1CC5322-7325-4F6B-9625-194B30BE1296}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Cpu", "src\Ryujinx.Cpu\Ryujinx.Cpu.csproj", "{3DF35E3D-D844-4399-A9A1-A9E923264C17}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Device", "src\Ryujinx.Graphics.Device\Ryujinx.Graphics.Device.csproj", "{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Host1x", "src\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj", "{C35F1536-7DE5-4F9D-9604-B5B4E1561947}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.Vp9", "src\Ryujinx.Graphics.Nvdec.Vp9\Ryujinx.Graphics.Nvdec.Vp9.csproj", "{B9AECA11-E248-4886-A10B-81B631CAAF29}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vic", "src\Ryujinx.Graphics.Vic\Ryujinx.Graphics.Vic.csproj", "{81BB2C11-9408-4EA3-822E-42987AF54429}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "src\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "src\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SoundIo", "src\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj", "{716364DE-B988-41A6-BAB4-327964266ECC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input", "src\Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL2", "src\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "src\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj", "{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL2", "src\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "src\Ryujinx.Headless.SDL2\Ryujinx.Headless.SDL2.csproj", "{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "src\Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.Common", "src\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "src\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj", "{D4D09B08-D580-4D69-B886-C35D2853F6C8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.LocaleGenerator", "src\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryujinx.Horizon\Ryujinx.Horizon.csproj", "{AF34127A-3A92-43E5-8496-14960A50B1F1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibRyujinx.Shared", "src\LibRyujinx\LibRyujinx.csproj", "{5BBF478C-A520-41E7-9B88-890AD26766B8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{C75176EB-C44E-449A-8077-A48AD94534EE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibRyujinx.NativeSample", "src\LibRyujinx.NativeSample\LibRyujinx.NativeSample.csproj", "{63D2C96B-5194-4592-BC91-30BEB11C06BD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.Build.0 = Release|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.Build.0 = Release|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.Build.0 = Release|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.Build.0 = Release|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.Build.0 = Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.Build.0 = Release|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.Build.0 = Release|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.Build.0 = Release|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.Build.0 = Release|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.Build.0 = Release|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.Build.0 = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.Build.0 = Debug|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.ActiveCfg = Release|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.Build.0 = Release|Any CPU + {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.Build.0 = Release|Any CPU + {D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|Any CPU.Build.0 = Release|Any CPU + {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|Any CPU.Build.0 = Release|Any CPU + {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|Any CPU.Build.0 = Release|Any CPU + {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|Any CPU.Build.0 = Release|Any CPU + {B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|Any CPU.Build.0 = Release|Any CPU + {81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.Build.0 = Release|Any CPU + {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.Build.0 = Release|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.Build.0 = Release|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.Build.0 = Release|Any CPU + {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.Build.0 = Release|Any CPU + {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.Build.0 = Release|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.Build.0 = Release|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU + {390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.Build.0 = Release|Any CPU + {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.Build.0 = Release|Any CPU + {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.Build.0 = Release|Any CPU + {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.Build.0 = Release|Any CPU + {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.Build.0 = Release|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.Build.0 = Release|Any CPU + {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|Any CPU.Build.0 = Release|Any CPU + {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|Any CPU.Build.0 = Release|Any CPU + {77F96ECE-4952-42DB-A528-DED25572A573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77F96ECE-4952-42DB-A528-DED25572A573}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77F96ECE-4952-42DB-A528-DED25572A573}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77F96ECE-4952-42DB-A528-DED25572A573}.Release|Any CPU.Build.0 = Release|Any CPU + {AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|Any CPU.Build.0 = Release|Any CPU + {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU + {5BBF478C-A520-41E7-9B88-890AD26766B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BBF478C-A520-41E7-9B88-890AD26766B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BBF478C-A520-41E7-9B88-890AD26766B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BBF478C-A520-41E7-9B88-890AD26766B8}.Release|Any CPU.Build.0 = Release|Any CPU + {C75176EB-C44E-449A-8077-A48AD94534EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C75176EB-C44E-449A-8077-A48AD94534EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C75176EB-C44E-449A-8077-A48AD94534EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C75176EB-C44E-449A-8077-A48AD94534EE}.Release|Any CPU.Build.0 = Release|Any CPU + {63D2C96B-5194-4592-BC91-30BEB11C06BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63D2C96B-5194-4592-BC91-30BEB11C06BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63D2C96B-5194-4592-BC91-30BEB11C06BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63D2C96B-5194-4592-BC91-30BEB11C06BD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {110169B3-3328-4730-8AB0-BA05BEF75C1A} + EndGlobalSection +EndGlobal diff --git a/Ryujinx.sln.DotSettings b/Ryujinx.sln.DotSettings new file mode 100644 index 000000000..049bdaf69 --- /dev/null +++ b/Ryujinx.sln.DotSettings @@ -0,0 +1,21 @@ + + WARNING + WARNING + UseExplicitType + UseExplicitType + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy> + True + True + True + True + True + True + True + True + True + True + True + True + True + True + \ No newline at end of file diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..279cd2162 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /**/Assets/Locales/en_US.json + translation: /**/Assets/Locales/%locale_with_underscore%.json diff --git a/distribution/legal/THIRDPARTY.md b/distribution/legal/THIRDPARTY.md new file mode 100644 index 000000000..5caa03771 --- /dev/null +++ b/distribution/legal/THIRDPARTY.md @@ -0,0 +1,713 @@ +# ffmpeg (LGPLv3) +
+ See License + + ``` + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates + the terms and conditions of version 3 of the GNU General Public + License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser + General Public License, and the "GNU GPL" refers to version 3 of the GNU + General Public License. + + "The Library" refers to a covered work governed by this License, + other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided + by the Library, but which is not otherwise based on the Library. + Defining a subclass of a class defined by the Library is deemed a mode + of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an + Application with the Library. The particular version of the Library + with which the Combined Work was made is also called the "Linked + Version". + + The "Minimal Corresponding Source" for a Combined Work means the + Corresponding Source for the Combined Work, excluding any source code + for portions of the Combined Work that, considered in isolation, are + based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the + object code and/or source code for the Application, including any data + and utility programs needed for reproducing the Combined Work from the + Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License + without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a + facility refers to a function or data to be supplied by an Application + that uses the facility (other than as an argument passed when the + facility is invoked), then you may convey a copy of the modified + version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from + a header file that is part of the Library. You may convey such object + code under terms of your choice, provided that, if the incorporated + material is not limited to numerical parameters, data structure + layouts and accessors, or small macros, inline functions and templates + (ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, + taken together, effectively do not restrict modification of the + portions of the Library contained in the Combined Work and reverse + engineering for debugging such modifications, if you also do each of + the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the + Library side by side in a single library together with other library + facilities that are not Applications and are not covered by this + License, and convey such a combined library under terms of your + choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions + of the GNU Lesser General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the + Library as you received it specifies that a certain numbered version + of the GNU Lesser General Public License "or any later version" + applies to it, you have the option of following the terms and + conditions either of that published version or of any later version + published by the Free Software Foundation. If the Library as you + received it does not specify a version number of the GNU Lesser + General Public License, you may choose any version of the GNU Lesser + General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide + whether future versions of the GNU Lesser General Public License shall + apply, that proxy's public statement of acceptance of any version is + permanent authorization for you to choose that version for the + Library. + ``` +
+ +# libvpx (BSD) +
+ See License + + ``` + Copyright (c) 2010, The WebM Project authors. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google, nor the WebM Project, nor the names + of its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ``` +
+ +# Atmosphère (MIT) +
+ See License + + ``` + MIT License + + Copyright (c) 2018-2020 Atmosphère-NX + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + ``` +
+ +# OpenAL Soft (LGPLv2) +
+ See License + + ``` + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + [This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + Licenses are intended to guarantee your freedom to share and change + free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some + specially designated Free Software Foundation software, and to any + other libraries whose authors decide to use it. You can use it for + your libraries, too. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + this service if you wish), that you receive source code or can get it + if you want it, that you can change the software or use pieces of it + in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid + anyone to deny you these rights or to ask you to surrender the rights. + These restrictions translate to certain responsibilities for you if + you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis + or for a fee, you must give the recipients all the rights that we gave + you. You must make sure that they, too, receive or can get the source + code. If you link a program with the library, you must provide + complete object files to the recipients so that they can relink them + with the library, after making changes to the library and recompiling + it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright + the library, and (2) offer you this license which gives you legal + permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain + that everyone understands that there is no warranty for this free + library. If the library is modified by someone else and passed on, we + want its recipients to know that what they have is not the original + version, so that any problems introduced by others will not reflect on + the original authors' reputations. + + Finally, any free program is threatened constantly by software + patents. We wish to avoid the danger that companies distributing free + software will individually obtain patent licenses, thus in effect + transforming the program into proprietary software. To prevent this, + we have made it clear that any patent must be licensed for everyone's + free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary + GNU General Public License, which was designed for utility programs. This + license, the GNU Library General Public License, applies to certain + designated libraries. This license is quite different from the ordinary + one; be sure to read it in full, and don't assume that anything in it is + the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that + they blur the distinction we usually make between modifying or adding to a + program and simply using it. Linking a program with a library, without + changing the library, is in some sense simply using the library, and is + analogous to running a utility program or application program. However, in + a textual and legal sense, the linked executable is a combined work, a + derivative of the original library, and the ordinary General Public License + treats it as such. + + Because of this blurred distinction, using the ordinary General + Public License for libraries did not effectively promote software + sharing, because most developers did not use the libraries. We + concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the + users of those programs of all benefit from the free status of the + libraries themselves. This Library General Public License is intended to + permit developers of non-free programs to use free libraries, while + preserving your freedom as a user of such programs to change the free + libraries that are incorporated in them. (We have not seen how to achieve + this as regards changes in header files, but we have achieved it as regards + changes in the actual functions of the Library.) The hope is that this + will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and + modification follow. Pay close attention to the difference between a + "work based on the library" and a "work that uses the library". The + former contains code derived from the library, while the latter only + works together with the library. + + Note that it is possible for a library to be covered by the ordinary + General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which + contains a notice placed by the copyright holder or other authorized + party saying it may be distributed under the terms of this Library + General Public License (also called "this License"). Each licensee is + addressed as "you". + + A "library" means a collection of software functions and/or data + prepared so as to be conveniently linked with application programs + (which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work + which has been distributed under these terms. A "work based on the + Library" means either the Library or any derivative work under + copyright law: that is to say, a work containing the Library or a + portion of it, either verbatim or with modifications and/or translated + straightforwardly into another language. (Hereinafter, translation is + included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for + making modifications to it. For a library, complete source code means + all the source code for all modules it contains, plus any associated + interface definition files, plus the scripts used to control compilation + and installation of the library. + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running a program using the Library is not restricted, and output from + such a program is covered only if its contents constitute a work based + on the Library (independent of the use of the Library in a tool for + writing it). Whether that is true depends on what the Library does + and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's + complete source code as you receive it, in any medium, provided that + you conspicuously and appropriately publish on each copy an + appropriate copyright notice and disclaimer of warranty; keep intact + all the notices that refer to this License and to the absence of any + warranty; and distribute a copy of this License along with the + Library. + + You may charge a fee for the physical act of transferring a copy, + and you may at your option offer warranty protection in exchange for a + fee. + + 2. You may modify your copy or copies of the Library or any portion + of it, thus forming a work based on the Library, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Library, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Library, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote + it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Library. + + In addition, mere aggregation of another work not based on the Library + with the Library (or with a work based on the Library) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public + License instead of this License to a given copy of the Library. To do + this, you must alter all the notices that refer to this License, so + that they refer to the ordinary GNU General Public License, version 2, + instead of to this License. (If a newer version than version 2 of the + ordinary GNU General Public License has appeared, then you can specify + that version instead if you wish.) Do not make any other change in + these notices. + + Once this change is made in a given copy, it is irreversible for + that copy, so the ordinary GNU General Public License applies to all + subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of + the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or + derivative of it, under Section 2) in object code or executable form + under the terms of Sections 1 and 2 above provided that you accompany + it with the complete corresponding machine-readable source code, which + must be distributed under the terms of Sections 1 and 2 above on a + medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy + from a designated place, then offering equivalent access to copy the + source code from the same place satisfies the requirement to + distribute the source code, even though third parties are not + compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the + Library, but is designed to work with the Library by being compiled or + linked with it, is called a "work that uses the Library". Such a + work, in isolation, is not a derivative work of the Library, and + therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library + creates an executable that is a derivative of the Library (because it + contains portions of the Library), rather than a "work that uses the + library". The executable is therefore covered by this License. + Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file + that is part of the Library, the object code for the work may be a + derivative work of the Library even though the source code is not. + Whether this is true is especially significant if the work can be + linked without the Library, or if the work is itself a library. The + threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data + structure layouts and accessors, and small macros and small inline + functions (ten lines or less in length), then the use of the object + file is unrestricted, regardless of whether it is legally a derivative + work. (Executables containing this object code plus portions of the + Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may + distribute the object code for the work under the terms of Section 6. + Any executables containing that work also fall under Section 6, + whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or + link a "work that uses the Library" with the Library to produce a + work containing portions of the Library, and distribute that work + under terms of your choice, provided that the terms permit + modification of the work for the customer's own use and reverse + engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the + Library is used in it and that the Library and its use are covered by + this License. You must supply a copy of this License. If the work + during execution displays copyright notices, you must include the + copyright notice for the Library among them, as well as a reference + directing the user to the copy of this License. Also, you must do one + of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the + Library" must include any data and utility programs needed for + reproducing the executable from it. However, as a special exception, + the source code distributed need not include anything that is normally + distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies + the executable. + + It may happen that this requirement contradicts the license + restrictions of other proprietary libraries that do not normally + accompany the operating system. Such a contradiction means you cannot + use both them and the Library together in an executable that you + distribute. + + 7. You may place library facilities that are a work based on the + Library side-by-side in a single library together with other library + facilities not covered by this License, and distribute such a combined + library, provided that the separate distribution of the work based on + the Library and of the other library facilities is otherwise + permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute + the Library except as expressly provided under this License. Any + attempt otherwise to copy, modify, sublicense, link with, or + distribute the Library is void, and will automatically terminate your + rights under this License. However, parties who have received copies, + or rights, from you under this License will not have their licenses + terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Library or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Library (or any work based on the + Library), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the + Library), the recipient automatically receives a license from the + original licensor to copy, distribute, link with or modify the Library + subject to these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties to + this License. + + 11. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Library at all. For example, if a patent + license would not permit royalty-free redistribution of the Library by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Library. + + If any portion of this section is held invalid or unenforceable under any + particular circumstance, the balance of the section is intended to apply, + and the section as a whole is intended to apply in other circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Library under this License may add + an explicit geographical distribution limitation excluding those countries, + so that distribution is permitted only in or among countries not thus + excluded. In such case, this License incorporates the limitation as if + written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new + versions of the Library General Public License from time to time. + Such new versions will be similar in spirit to the present version, + but may differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Library + specifies a version number of this License which applies to it and + "any later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Library does not specify a + license version number, you may choose any version ever published by + the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free + programs whose distribution conditions are incompatible with these, + write to the author to ask for permission. For software which is + copyrighted by the Free Software Foundation, write to the Free + Software Foundation; we sometimes make exceptions for this. Our + decision will be guided by the two goals of preserving the free status + of all derivatives of our free software and of promoting the sharing + and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE + LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME + THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU + FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE + LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING + RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A + FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF + SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + DAMAGES. + + END OF TERMS AND CONDITIONS + ``` +
+ +# ShellLink (MIT) +
+ See License + + ``` + MIT License + + Copyright (c) 2017 Yorick Koster, Securify B.V. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + ``` +
diff --git a/distribution/linux/Ryujinx.desktop b/distribution/linux/Ryujinx.desktop new file mode 100644 index 000000000..a4550d104 --- /dev/null +++ b/distribution/linux/Ryujinx.desktop @@ -0,0 +1,14 @@ +[Desktop Entry] +Version=1.0 +Name=Ryujinx +Type=Application +Icon=Ryujinx +Exec=Ryujinx.sh %f +Comment=Plays Nintendo Switch applications +GenericName=Nintendo Switch Emulator +Terminal=false +Categories=Game;Emulator; +MimeType=application/x-nx-nca;application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci; +Keywords=Switch;Nintendo;Emulator; +StartupWMClass=Ryujinx +PrefersNonDefaultGPU=true diff --git a/distribution/linux/Ryujinx.sh b/distribution/linux/Ryujinx.sh new file mode 100644 index 000000000..f356cad01 --- /dev/null +++ b/distribution/linux/Ryujinx.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +SCRIPT_DIR=$(dirname "$(realpath "$0")") +RYUJINX_BIN="Ryujinx" + +if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then + RYUJINX_BIN="Ryujinx.Ava" +fi + +if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then + RYUJINX_BIN="Ryujinx.Headless.SDL2" +fi + +COMMAND="env DOTNET_EnableAlternateStackCheck=1" + +if command -v gamemoderun > /dev/null 2>&1; then + COMMAND="$COMMAND gamemoderun" +fi + +$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@" \ No newline at end of file diff --git a/distribution/linux/mime/Ryujinx.xml b/distribution/linux/mime/Ryujinx.xml new file mode 100644 index 000000000..bd9df0edf --- /dev/null +++ b/distribution/linux/mime/Ryujinx.xml @@ -0,0 +1,33 @@ + + + + Nintendo Content Archive + NCA + + + + + Nintendo Relocatable Object + NRO + + + + + Nintendo Shared Object + NSO + + + + + Nintendo Submission Package + NSP + + + + + Nintendo Switch Cartridge + XCI + + + + diff --git a/distribution/linux/shortcut-template.desktop b/distribution/linux/shortcut-template.desktop new file mode 100644 index 000000000..6bee0f8d1 --- /dev/null +++ b/distribution/linux/shortcut-template.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Version=1.0 +Name={0} +Type=Application +Icon={1} +Exec={2} %f +Comment=Nintendo Switch application +GenericName=Nintendo Switch Emulator +Terminal=false +Categories=Game;Emulator; +Keywords=Switch;Nintendo;Emulator; +StartupWMClass=Ryujinx +PrefersNonDefaultGPU=true diff --git a/distribution/macos/Info.plist b/distribution/macos/Info.plist new file mode 100644 index 000000000..51c71eaa1 --- /dev/null +++ b/distribution/macos/Info.plist @@ -0,0 +1,158 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + Ryujinx + CFBundleGetInfoString + Ryujinx + CFBundleIconFile + Ryujinx.icns + CFBundleTypeExtensions + + nca + nro + nso + nsp + xci + + CFBundleIdentifier + org.ryujinx.Ryujinx + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + %%RYUJINX_BUILD_VERSION%%-%%RYUJINX_BUILD_GIT_HASH%%" + CFBundleName + Ryujinx + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.1 + CFBundleSignature + ???? + CFBundleVersion + 1.1.0 + NSHighResolutionCapable + + CSResourcesFileMapped + + NSHumanReadableCopyright + Copyright © 2018 - 2023 Ryujinx Team and Contributors. + LSApplicationCategoryType + public.app-category.games + LSMinimumSystemVersion + 12.0 + UTExportedTypeDeclarations + + + UTTypeDescription + Extensible Application Markup Language + UTTypeConformsTo + + public.xml + + UTTypeIdentifier + com.ryujinx.xaml + UTTypeTagSpecification + + public.filename-extension + + xaml + + + + + UTTypeDescription + Nintendo Submission Package + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.nsp + UTTypeTagSpecification + + public.filename-extension + + nsp + + + + + UTTypeDescription + Nintendo Switch Cartridge + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.xci + UTTypeTagSpecification + + public.filename-extension + + xci + + + + + UTTypeDescription + Nintendo Content Archive + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.nca + UTTypeTagSpecification + + public.filename-extension + + nca + + + + + UTTypeDescription + Nintendo Relocatable Object + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.nro + UTTypeTagSpecification + + public.filename-extension + + nro + + + + + UTTypeDescription + Nintendo Shared Object + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.nso + UTTypeTagSpecification + + public.filename-extension + + nso + + + + + LSEnvironment + + DOTNET_DefaultStackSize + 200000 + + + diff --git a/distribution/macos/Ryujinx.icns b/distribution/macos/Ryujinx.icns new file mode 100644 index 000000000..f54a9aeb7 Binary files /dev/null and b/distribution/macos/Ryujinx.icns differ diff --git a/distribution/macos/bundle_fix_up.py b/distribution/macos/bundle_fix_up.py new file mode 100644 index 000000000..a8e3ac760 --- /dev/null +++ b/distribution/macos/bundle_fix_up.py @@ -0,0 +1,609 @@ +import argparse +import hashlib +import os +from pathlib import Path +import platform +import shutil +import struct +import subprocess +from typing import List, Optional, Tuple + +parser = argparse.ArgumentParser(description="Fixup for MacOS application bundle") +parser.add_argument("input_directory", help="Input directory (Application path)") +parser.add_argument("executable_sub_path", help="Main executable sub path") + +# Use Apple LLVM on Darwin, otherwise standard LLVM. +if platform.system() == "Darwin": + OTOOL = "otool" + INSTALL_NAME_TOOL = "install_name_tool" +else: + OTOOL = shutil.which("llvm-otool") + if OTOOL is None: + for llvm_ver in [15, 14, 13]: + otool_path = shutil.which(f"llvm-otool-{llvm_ver}") + if otool_path is not None: + OTOOL = otool_path + INSTALL_NAME_TOOL = shutil.which(f"llvm-install-name-tool-{llvm_ver}") + break + else: + INSTALL_NAME_TOOL = shutil.which("llvm-install-name-tool") + + +args = parser.parse_args() + + +def get_dylib_id(dylib_path: Path) -> str: + res = subprocess.check_output([OTOOL, "-D", str(dylib_path.absolute())]).decode( + "utf-8" + ) + + return res.split("\n")[1] + + +def get_dylib_dependencies(dylib_path: Path) -> List[str]: + output = ( + subprocess.check_output([OTOOL, "-L", str(dylib_path.absolute())]) + .decode("utf-8") + .split("\n")[1:] + ) + + res = [] + + for line in output: + line = line.strip() + index = line.find(" (compatibility version ") + if index == -1: + continue + + line = line[:index] + + res.append(line) + + return res + + +def replace_dylib_id(dylib_path: Path, new_id: str): + subprocess.check_call( + [INSTALL_NAME_TOOL, "-id", new_id, str(dylib_path.absolute())] + ) + + +def change_dylib_link(dylib_path: Path, old: str, new: str): + subprocess.check_call( + [INSTALL_NAME_TOOL, "-change", old, new, str(dylib_path.absolute())] + ) + + +def add_dylib_rpath(dylib_path: Path, rpath: str): + subprocess.check_call( + [INSTALL_NAME_TOOL, "-add_rpath", rpath, str(dylib_path.absolute())] + ) + + +def fixup_dylib( + dylib_path: Path, + replacement_path: str, + search_path: List[str], + content_directory: Path, +): + dylib_id = get_dylib_id(dylib_path) + new_dylib_id = replacement_path + "/" + os.path.basename(dylib_id) + replace_dylib_id(dylib_path, new_dylib_id) + + dylib_dependencies = get_dylib_dependencies(dylib_path) + dylib_new_mapping = {} + + for dylib_dependency in dylib_dependencies: + if ( + not dylib_dependency.startswith("@executable_path") + and not dylib_dependency.startswith("/usr/lib") + and not dylib_dependency.startswith("/System/Library") + ): + dylib_dependency_name = os.path.basename(dylib_dependency) + library_found = False + for library_base_path in search_path: + lib_path = Path(os.path.join(library_base_path, dylib_dependency_name)) + + if lib_path.exists(): + target_replacement_path = get_path_related_to_target_exec( + content_directory, lib_path + ) + + dylib_new_mapping[dylib_dependency] = ( + target_replacement_path + + "/" + + os.path.basename(dylib_dependency) + ) + library_found = True + + if not library_found: + raise Exception( + f"{dylib_id}: Cannot find dependency {dylib_dependency_name} for fixup" + ) + + for key in dylib_new_mapping: + change_dylib_link(dylib_path, key, dylib_new_mapping[key]) + + +FILE_TYPE_ASSEMBLY = 1 + +ALIGN_REQUIREMENTS = 4096 + + +def parse_embedded_string(data: bytes) -> Tuple[bytes, str]: + first_byte = data[0] + + if (first_byte & 0x80) == 0: + size = first_byte + data = data[1:] + else: + second_byte = data[1] + + assert (second_byte & 0x80) == 0 + + size = (second_byte << 7) | (first_byte & 0x7F) + + data = data[2:] + + res = data[:size].decode("utf-8") + data = data[size:] + + return (data, res) + + +def write_embedded_string(file, string: str): + raw_str = string.encode("utf-8") + raw_str_len = len(raw_str) + + assert raw_str_len < 0x7FFF + + if raw_str_len > 0x7F: + file.write(struct.pack("b", raw_str_len & 0x7F | 0x80)) + file.write(struct.pack("b", raw_str_len >> 7)) + else: + file.write(struct.pack("b", raw_str_len)) + + file.write(raw_str) + + +class BundleFileEntry(object): + offset: int + size: int + compressed_size: int + file_type: int + relative_path: str + data: bytes + + def __init__( + self, + offset: int, + size: int, + compressed_size: int, + file_type: int, + relative_path: str, + data: bytes, + ) -> None: + self.offset = offset + self.size = size + self.compressed_size = compressed_size + self.file_type = file_type + self.relative_path = relative_path + self.data = data + + def write(self, file): + self.offset = file.tell() + + if ( + self.file_type == FILE_TYPE_ASSEMBLY + and (self.offset % ALIGN_REQUIREMENTS) != 0 + ): + padding_size = ALIGN_REQUIREMENTS - (self.offset % ALIGN_REQUIREMENTS) + file.write(b"\0" * padding_size) + self.offset += padding_size + + file.write(self.data) + + def write_header(self, file): + file.write( + struct.pack( + "QQQb", self.offset, self.size, self.compressed_size, self.file_type + ) + ) + write_embedded_string(file, self.relative_path) + + +class BundleManifest(object): + major: int + minor: int + bundle_id: str + deps_json: BundleFileEntry + runtimeconfig_json: BundleFileEntry + flags: int + files: List[BundleFileEntry] + + def __init__( + self, + major: int, + minor: int, + bundle_id: str, + deps_json: BundleFileEntry, + runtimeconfig_json: BundleFileEntry, + flags: int, + files: List[BundleFileEntry], + ) -> None: + self.major = major + self.minor = minor + self.bundle_id = bundle_id + self.deps_json = deps_json + self.runtimeconfig_json = runtimeconfig_json + self.flags = flags + self.files = files + + def write(self, file) -> int: + for bundle_file in self.files: + bundle_file.write(file) + + bundle_header_offset = file.tell() + file.write(struct.pack("iiI", self.major, self.minor, len(self.files))) + write_embedded_string(file, self.bundle_id) + + if self.deps_json is not None: + deps_json_location_offset = self.deps_json.offset + deps_json_location_size = self.deps_json.size + else: + deps_json_location_offset = 0 + deps_json_location_size = 0 + + if self.runtimeconfig_json is not None: + runtimeconfig_json_location_offset = self.runtimeconfig_json.offset + runtimeconfig_json_location_size = self.runtimeconfig_json.size + else: + runtimeconfig_json_location_offset = 0 + runtimeconfig_json_location_size = 0 + + file.write( + struct.pack("qq", deps_json_location_offset, deps_json_location_size) + ) + file.write( + struct.pack( + "qq", + runtimeconfig_json_location_offset, + runtimeconfig_json_location_size, + ) + ) + file.write(struct.pack("q", self.flags)) + + for bundle_file in self.files: + bundle_file.write_header(file) + + return bundle_header_offset + + +def read_file_entry( + raw_data: bytes, header_bytes: bytes +) -> Tuple[bytes, BundleFileEntry]: + ( + offset, + size, + compressed_size, + file_type, + ) = struct.unpack("QQQb", header_bytes[:0x19]) + (header_bytes, relative_path) = parse_embedded_string(header_bytes[0x19:]) + + target_size = compressed_size + + if target_size == 0: + target_size = size + + return ( + header_bytes, + BundleFileEntry( + offset, + size, + compressed_size, + file_type, + relative_path, + raw_data[offset : offset + target_size], + ), + ) + + +def get_dotnet_bundle_data(data: bytes) -> Optional[Tuple[int, int, BundleManifest]]: + offset = data.find(hashlib.sha256(b".net core bundle\n").digest()) + + if offset == -1: + return None + + raw_header_offset = data[offset - 8 : offset] + (header_offset,) = struct.unpack("q", raw_header_offset) + header_bytes = data[header_offset:] + + ( + major, + minor, + files_count, + ) = struct.unpack("iiI", header_bytes[:0xC]) + header_bytes = header_bytes[0xC:] + + (header_bytes, bundle_id) = parse_embedded_string(header_bytes) + + # v2 header + ( + deps_json_location_offset, + deps_json_location_size, + ) = struct.unpack("qq", header_bytes[:0x10]) + ( + runtimeconfig_json_location_offset, + runtimeconfig_json_location_size, + ) = struct.unpack("qq", header_bytes[0x10:0x20]) + (flags,) = struct.unpack("q", header_bytes[0x20:0x28]) + header_bytes = header_bytes[0x28:] + + files = [] + + deps_json = None + runtimeconfig_json = None + + for _ in range(files_count): + (header_bytes, file_entry) = read_file_entry(data, header_bytes) + + files.append(file_entry) + + if file_entry.offset == deps_json_location_offset: + deps_json = file_entry + elif file_entry.offset == runtimeconfig_json_location_offset: + runtimeconfig_json = file_entry + + file_entry = files[0] + + return ( + file_entry.offset, + header_offset, + BundleManifest( + major, minor, bundle_id, deps_json, runtimeconfig_json, flags, files + ), + ) + + +LC_SYMTAB = 0x2 +LC_SEGMENT_64 = 0x19 +LC_CODE_SIGNATURE = 0x1D + + +def fixup_linkedit(file, data: bytes, new_size: int): + offset = 0 + + ( + macho_magic, + macho_cputype, + macho_cpusubtype, + macho_filetype, + macho_ncmds, + macho_sizeofcmds, + macho_flags, + macho_reserved, + ) = struct.unpack("IiiIIIII", data[offset : offset + 0x20]) + + offset += 0x20 + + linkedit_offset = None + symtab_offset = None + codesign_offset = None + + for _ in range(macho_ncmds): + (cmd, cmdsize) = struct.unpack("II", data[offset : offset + 8]) + + if cmd == LC_SEGMENT_64: + ( + cmd, + cmdsize, + segname_raw, + vmaddr, + vmsize, + fileoff, + filesize, + maxprot, + initprot, + nsects, + flags, + ) = struct.unpack("II16sQQQQiiII", data[offset : offset + 72]) + segname = segname_raw.decode("utf-8").split("\0")[0] + + if segname == "__LINKEDIT": + linkedit_offset = offset + elif cmd == LC_SYMTAB: + symtab_offset = offset + elif cmd == LC_CODE_SIGNATURE: + codesign_offset = offset + + offset += cmdsize + pass + + assert linkedit_offset is not None and symtab_offset is not None + + # If there is a codesign section, clean it up. + if codesign_offset is not None: + ( + codesign_cmd, + codesign_cmdsize, + codesign_dataoff, + codesign_datasize, + ) = struct.unpack("IIII", data[codesign_offset : codesign_offset + 16]) + file.seek(codesign_offset) + file.write(b"\0" * codesign_cmdsize) + + macho_ncmds -= 1 + macho_sizeofcmds -= codesign_cmdsize + file.seek(0) + file.write( + struct.pack( + "IiiIIIII", + macho_magic, + macho_cputype, + macho_cpusubtype, + macho_filetype, + macho_ncmds, + macho_sizeofcmds, + macho_flags, + macho_reserved, + ) + ) + + file.seek(codesign_dataoff) + file.write(b"\0" * codesign_datasize) + + ( + symtab_cmd, + symtab_cmdsize, + symtab_symoff, + symtab_nsyms, + symtab_stroff, + symtab_strsize, + ) = struct.unpack("IIIIII", data[symtab_offset : symtab_offset + 24]) + + symtab_strsize = new_size - symtab_stroff + + new_symtab = struct.pack( + "IIIIII", + symtab_cmd, + symtab_cmdsize, + symtab_symoff, + symtab_nsyms, + symtab_stroff, + symtab_strsize, + ) + + file.seek(symtab_offset) + file.write(new_symtab) + + ( + linkedit_cmd, + linkedit_cmdsize, + linkedit_segname_raw, + linkedit_vmaddr, + linkedit_vmsize, + linkedit_fileoff, + linkedit_filesize, + linkedit_maxprot, + linkedit_initprot, + linkedit_nsects, + linkedit_flags, + ) = struct.unpack("II16sQQQQiiII", data[linkedit_offset : linkedit_offset + 72]) + + linkedit_filesize = new_size - linkedit_fileoff + linkedit_vmsize = linkedit_filesize + + new_linkedit = struct.pack( + "II16sQQQQiiII", + linkedit_cmd, + linkedit_cmdsize, + linkedit_segname_raw, + linkedit_vmaddr, + linkedit_vmsize, + linkedit_fileoff, + linkedit_filesize, + linkedit_maxprot, + linkedit_initprot, + linkedit_nsects, + linkedit_flags, + ) + file.seek(linkedit_offset) + file.write(new_linkedit) + + +def write_bundle_data( + output, + old_bundle_base_offset: int, + new_bundle_base_offset: int, + bundle: BundleManifest, +) -> int: + # Write bundle data + bundle_header_offset = bundle.write(output) + total_size = output.tell() + + # Patch the header position + offset = file_data.find(hashlib.sha256(b".net core bundle\n").digest()) + output.seek(offset - 8) + output.write(struct.pack("q", bundle_header_offset)) + + return total_size - new_bundle_base_offset + + +input_directory: Path = Path(args.input_directory) +content_directory: Path = Path(os.path.join(args.input_directory, "Contents")) +executable_path: Path = Path(os.path.join(content_directory, args.executable_sub_path)) + + +def get_path_related_to_other_path(a: Path, b: Path) -> str: + temp = b + + parts = [] + + while temp != a: + temp = temp.parent + parts.append(temp.name) + + parts.remove(parts[-1]) + parts.reverse() + + return "/".join(parts) + + +def get_path_related_to_target_exec(input_directory: Path, path: Path): + return "@executable_path/../" + get_path_related_to_other_path( + input_directory, path + ) + + +search_path = [ + Path(os.path.join(content_directory, "Frameworks")), + Path(os.path.join(content_directory, "Resources/lib")), +] + + +for path in content_directory.rglob("**/*.dylib"): + current_search_path = [path.parent] + current_search_path.extend(search_path) + + fixup_dylib( + path, + get_path_related_to_target_exec(content_directory, path), + current_search_path, + content_directory, + ) + +for path in content_directory.rglob("**/*.so"): + current_search_path = [path.parent] + current_search_path.extend(search_path) + + fixup_dylib( + path, + get_path_related_to_target_exec(content_directory, path), + current_search_path, + content_directory, + ) + + +with open(executable_path, "rb") as input: + file_data = input.read() + + +(bundle_base_offset, bundle_header_offset, bundle) = get_dotnet_bundle_data(file_data) + +add_dylib_rpath(executable_path, "@executable_path/../Frameworks/") + +# Recent "vanilla" version of LLVM (LLVM 13 and upper) seems to really dislike how .NET package its assemblies. +# As a result, after execution of install_name_tool it will have "fixed" the symtab resulting in a missing .NET bundle... +# To mitigate that, we check if the bundle offset inside the binary is valid after install_name_tool and readd .NET bundle if not. +output_file_size = os.stat(executable_path).st_size +if output_file_size < bundle_header_offset: + print("LLVM broke the .NET bundle, readding bundle data...") + with open(executable_path, "r+b") as output: + file_data = output.read() + bundle_data_size = write_bundle_data( + output, bundle_base_offset, output_file_size, bundle + ) + + # Now patch the __LINKEDIT section + new_size = output_file_size + bundle_data_size + fixup_linkedit(output, file_data, new_size) diff --git a/distribution/macos/construct_universal_dylib.py b/distribution/macos/construct_universal_dylib.py new file mode 100644 index 000000000..b6c3770c6 --- /dev/null +++ b/distribution/macos/construct_universal_dylib.py @@ -0,0 +1,95 @@ +import argparse +import os +from pathlib import Path +import platform +import shutil +import subprocess + +parser = argparse.ArgumentParser( + description="Construct Universal dylibs for nuget package" +) +parser.add_argument( + "arm64_input_directory", help="ARM64 Input directory containing dylibs" +) +parser.add_argument( + "x86_64_input_directory", help="x86_64 Input directory containing dylibs" +) +parser.add_argument("output_directory", help="Output directory") +parser.add_argument("rglob", help="rglob") + +args = parser.parse_args() + +# Use Apple LLVM on Darwin, otherwise standard LLVM. +if platform.system() == "Darwin": + LIPO = "lipo" +else: + LIPO = shutil.which("llvm-lipo") + + if LIPO is None: + for llvm_ver in [15, 14, 13]: + lipo_path = shutil.which(f"llvm-lipo-{llvm_ver}") + if lipo_path is not None: + LIPO = lipo_path + break + +if LIPO is None: + raise Exception("Cannot find a valid location for LLVM lipo!") + +arm64_input_directory: Path = Path(args.arm64_input_directory) +x86_64_input_directory: Path = Path(args.x86_64_input_directory) +output_directory: Path = Path(args.output_directory) +rglob = args.rglob + + +def get_new_name( + input_directory: Path, output_directory: str, input_dylib_path: Path +) -> Path: + input_component = str(input_dylib_path).replace(str(input_directory), "")[1:] + return Path(os.path.join(output_directory, input_component)) + + +def is_fat_file(dylib_path: Path) -> str: + res = subprocess.check_output([LIPO, "-info", str(dylib_path.absolute())]).decode( + "utf-8" + ) + + return not res.split("\n")[0].startswith("Non-fat file") + + +def construct_universal_dylib( + arm64_input_dylib_path: Path, x86_64_input_dylib_path: Path, output_dylib_path: Path +): + if output_dylib_path.exists() or output_dylib_path.is_symlink(): + os.remove(output_dylib_path) + + os.makedirs(output_dylib_path.parent, exist_ok=True) + + if arm64_input_dylib_path.is_symlink(): + os.symlink( + os.path.basename(arm64_input_dylib_path.resolve()), output_dylib_path + ) + else: + if is_fat_file(arm64_input_dylib_path) or not x86_64_input_dylib_path.exists(): + with open(output_dylib_path, "wb") as dst: + with open(arm64_input_dylib_path, "rb") as src: + dst.write(src.read()) + else: + subprocess.check_call( + [ + LIPO, + str(arm64_input_dylib_path.absolute()), + str(x86_64_input_dylib_path.absolute()), + "-output", + str(output_dylib_path.absolute()), + "-create", + ] + ) + + +print(rglob) +for path in arm64_input_directory.rglob("**/*.dylib"): + construct_universal_dylib( + path, + get_new_name(arm64_input_directory, x86_64_input_directory, path), + get_new_name(arm64_input_directory, output_directory, path), + ) diff --git a/distribution/macos/create_app_bundle.sh b/distribution/macos/create_app_bundle.sh new file mode 100755 index 000000000..858c06f59 --- /dev/null +++ b/distribution/macos/create_app_bundle.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -e + +PUBLISH_DIRECTORY=$1 +OUTPUT_DIRECTORY=$2 +ENTITLEMENTS_FILE_PATH=$3 + +APP_BUNDLE_DIRECTORY="$OUTPUT_DIRECTORY/Ryujinx.app" + +rm -rf "$APP_BUNDLE_DIRECTORY" +mkdir -p "$APP_BUNDLE_DIRECTORY/Contents" +mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" +mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS" +mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources" + +# Copy executables first +cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" +chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" + +# Then all libraries +cp "$PUBLISH_DIRECTORY"/*.dylib "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" + +# Then resources +cp Info.plist "$APP_BUNDLE_DIRECTORY/Contents" +cp Ryujinx.icns "$APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns" +cp updater.sh "$APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh" +cp -r "$PUBLISH_DIRECTORY/THIRDPARTY.md" "$APP_BUNDLE_DIRECTORY/Contents/Resources" + +echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo" + +# Fixup libraries and executable +python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx + +# Now sign it +if ! [ -x "$(command -v codesign)" ]; +then + if ! [ -x "$(command -v rcodesign)" ]; + then + echo "Cannot find rcodesign on your system, please install rcodesign." + exit 1 + fi + + # cargo install apple-codesign + echo "Usign rcodesign for ad-hoc signing" + rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY" +else + echo "Usign codesign for ad-hoc signing" + codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$APP_BUNDLE_DIRECTORY" +fi \ No newline at end of file diff --git a/distribution/macos/create_macos_build_ava.sh b/distribution/macos/create_macos_build_ava.sh new file mode 100755 index 000000000..80594a40a --- /dev/null +++ b/distribution/macos/create_macos_build_ava.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +set -e + +if [ "$#" -lt 7 ]; then + echo "usage " + exit 1 +fi + +mkdir -p "$1" +mkdir -p "$2" +mkdir -p "$3" + +BASE_DIR=$(readlink -f "$1") +TEMP_DIRECTORY=$(readlink -f "$2") +OUTPUT_DIRECTORY=$(readlink -f "$3") +ENTITLEMENTS_FILE_PATH=$(readlink -f "$4") +VERSION=$5 +SOURCE_REVISION_ID=$6 +CONFIGURATION=$7 +EXTRA_ARGS=$8 + +if [ "$VERSION" == "1.1.0" ]; +then + RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar +else + RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar +fi + +ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app" +X64_APP_BUNDLE="$TEMP_DIRECTORY/output_x64/Ryujinx.app" +UNIVERSAL_APP_BUNDLE="$OUTPUT_DIRECTORY/Ryujinx.app" +EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx + +rm -rf "$TEMP_DIRECTORY" +mkdir -p "$TEMP_DIRECTORY" + +DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS) + +dotnet restore +dotnet build -c "$CONFIGURATION" src/Ryujinx.Ava +dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava +dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava + +# Get rid of the support library for ARMeilleure for x64 (that's only for arm64) +rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" + +# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant +# TODO: remove this once done +rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib" + +pushd "$BASE_DIR/distribution/macos" +./create_app_bundle.sh "$TEMP_DIRECTORY/publish_x64" "$TEMP_DIRECTORY/output_x64" "$ENTITLEMENTS_FILE_PATH" +./create_app_bundle.sh "$TEMP_DIRECTORY/publish_arm64" "$TEMP_DIRECTORY/output_arm64" "$ENTITLEMENTS_FILE_PATH" +popd + +rm -rf "$UNIVERSAL_APP_BUNDLE" +mkdir -p "$OUTPUT_DIRECTORY" + +# Let's copy one of the two different app bundle and remove the executable +cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" +rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" + +# Make it libraries universal +python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib" + +if ! [ -x "$(command -v lipo)" ]; +then + if ! [ -x "$(command -v llvm-lipo-14)" ]; + then + LIPO=llvm-lipo + else + LIPO=llvm-lipo-14 + fi +else + LIPO=lipo +fi + +# Make the executable universal +$LIPO "$ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" "$X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -create + +# Patch up the Info.plist to have appropriate version +sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist" +sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist" +rm "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck" + +# Now sign it +if ! [ -x "$(command -v codesign)" ]; +then + if ! [ -x "$(command -v rcodesign)" ]; + then + echo "Cannot find rcodesign on your system, please install rcodesign." + exit 1 + fi + + # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes. + # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign" + echo "Using rcodesign for ad-hoc signing" + rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE" +else + echo "Using codesign for ad-hoc signing" + codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$UNIVERSAL_APP_BUNDLE" +fi + +echo "Creating archive" +pushd "$OUTPUT_DIRECTORY" +tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME" Ryujinx.app 1> /dev/null +python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx" +gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" +rm "$RELEASE_TAR_FILE_NAME" +popd + +echo "Done" \ No newline at end of file diff --git a/distribution/macos/create_macos_build_headless.sh b/distribution/macos/create_macos_build_headless.sh new file mode 100755 index 000000000..a439aef45 --- /dev/null +++ b/distribution/macos/create_macos_build_headless.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +set -e + +if [ "$#" -lt 7 ]; then + echo "usage " + exit 1 +fi + +mkdir -p "$1" +mkdir -p "$2" +mkdir -p "$3" + +BASE_DIR=$(readlink -f "$1") +TEMP_DIRECTORY=$(readlink -f "$2") +OUTPUT_DIRECTORY=$(readlink -f "$3") +ENTITLEMENTS_FILE_PATH=$(readlink -f "$4") +VERSION=$5 +SOURCE_REVISION_ID=$6 +CONFIGURATION=$7 +EXTRA_ARGS=$8 + +if [ "$VERSION" == "1.1.0" ]; +then + RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar +else + RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar +fi + +ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64" +X64_OUTPUT="$TEMP_DIRECTORY/publish_x64" +UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish" +EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL2 + +rm -rf "$TEMP_DIRECTORY" +mkdir -p "$TEMP_DIRECTORY" + +DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS) + +dotnet restore +dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL2 +dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2 +dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2 + +# Get rid of the support library for ARMeilleure for x64 (that's only for arm64) +rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" + +# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant +# TODO: remove this once done +rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib" + +rm -rf "$OUTPUT_DIRECTORY" +mkdir -p "$OUTPUT_DIRECTORY" + +# Let's copy one of the two different outputs and remove the executable +cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT" +rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" + +# Make it libraries universal +python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib" + +if ! [ -x "$(command -v lipo)" ]; +then + if ! [ -x "$(command -v llvm-lipo-14)" ]; + then + LIPO=llvm-lipo + else + LIPO=llvm-lipo-14 + fi +else + LIPO=lipo +fi + +# Make the executable universal +$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create + +# Now sign it +if ! [ -x "$(command -v codesign)" ]; +then + if ! [ -x "$(command -v rcodesign)" ]; + then + echo "Cannot find rcodesign on your system, please install rcodesign." + exit 1 + fi + + # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes. + # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign" + echo "Using rcodesign for ad-hoc signing" + for FILE in "$UNIVERSAL_OUTPUT"/*; do + if [[ $(file "$FILE") == *"Mach-O"* ]]; then + rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE" + fi + done +else + echo "Using codesign for ad-hoc signing" + for FILE in "$UNIVERSAL_OUTPUT"/*; do + if [[ $(file "$FILE") == *"Mach-O"* ]]; then + codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$FILE" + fi + done +fi + +echo "Creating archive" +pushd "$OUTPUT_DIRECTORY" +tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null +python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2" +gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" +rm "$RELEASE_TAR_FILE_NAME" +popd + +echo "Done" \ No newline at end of file diff --git a/distribution/macos/entitlements.xml b/distribution/macos/entitlements.xml new file mode 100644 index 000000000..bf3185071 --- /dev/null +++ b/distribution/macos/entitlements.xml @@ -0,0 +1,23 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.debugger + + com.apple.security.get-task-allow + + com.apple.security.hypervisor + + + diff --git a/distribution/macos/shortcut-template.plist b/distribution/macos/shortcut-template.plist new file mode 100644 index 000000000..27a9e46a9 --- /dev/null +++ b/distribution/macos/shortcut-template.plist @@ -0,0 +1,35 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + {0} + CFBundleGetInfoString + {1} + CFBundleIconFile + {2} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleVersion + 1.0 + NSHighResolutionCapable + + CSResourcesFileMapped + + NSHumanReadableCopyright + Copyright © 2018 - 2023 Ryujinx Team and Contributors. + LSApplicationCategoryType + public.app-category.games + LSMinimumSystemVersion + 11.0 + UIPrerenderedIcon + + LSEnvironment + + DOTNET_DefaultStackSize + 200000 + + + diff --git a/distribution/macos/updater.sh b/distribution/macos/updater.sh new file mode 100755 index 000000000..12e4c3aa1 --- /dev/null +++ b/distribution/macos/updater.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -e + +INSTALL_DIRECTORY=$1 +NEW_APP_DIRECTORY=$2 +APP_PID=$3 +APP_ARGUMENTS=("${@:4}") + +error_handler() { + local lineno="$1" + + script=""" + set alertTitle to \"Ryujinx - Updater error\" + set alertMessage to \"An error occurred during Ryujinx update (updater.sh:$lineno)\n\nPlease download the update manually from our website if the problem persists.\" + display dialog alertMessage with icon caution with title alertTitle buttons {\"Open Download Page\", \"Exit\"} + set the button_pressed to the button returned of the result + + if the button_pressed is \"Open Download Page\" then + open location \"https://ryujinx.org/download\" + end if + """ + + osascript -e "$script" + exit 1 +} + +trap 'error_handler ${LINENO}' ERR + +# Wait for Ryujinx to exit. +# If the main process is still acitve, we wait for 1 second and check it again. +# After the fifth time checking, this script exits with status 1. + +attempt=0 +while true; do + if lsof -p "$APP_PID" +r 1 &>/dev/null || ps -p "$APP_PID" &>/dev/null; then + if [ "$attempt" -eq 4 ]; then + exit 1 + fi + sleep 1 + else + break + fi + (( attempt++ )) +done + +sleep 1 + +# Now replace and reopen. +rm -rf "$INSTALL_DIRECTORY" +mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY" + +if [ "$#" -le 3 ]; then + open -a "$INSTALL_DIRECTORY" +else + open -a "$INSTALL_DIRECTORY" --args "${APP_ARGUMENTS[@]}" +fi \ No newline at end of file diff --git a/distribution/misc/Logo.svg b/distribution/misc/Logo.svg new file mode 100644 index 000000000..d6a76312a --- /dev/null +++ b/distribution/misc/Logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/distribution/misc/add_tar_exec.py b/distribution/misc/add_tar_exec.py new file mode 100644 index 000000000..fe659c161 --- /dev/null +++ b/distribution/misc/add_tar_exec.py @@ -0,0 +1,24 @@ +import argparse +from io import BytesIO +import tarfile + +parser = argparse.ArgumentParser( + description="Add the main binary to a tar and force it to be executable" +) +parser.add_argument("input_tar_file", help="input tar file") +parser.add_argument("main_binary_path", help="Main executable path") +parser.add_argument("main_binary_tar_path", help="Main executable tar path") + +args = parser.parse_args() +input_tar_file = args.input_tar_file +main_binary_path = args.main_binary_path +main_binary_tar_path = args.main_binary_tar_path + +with open(main_binary_path, "rb") as f: + with tarfile.open(input_tar_file, "a") as tar: + data = f.read() + tar_info = tarfile.TarInfo(main_binary_tar_path) + tar_info.mode = 0o755 + tar_info.size = len(data) + + tar.addfile(tar_info, BytesIO(data)) diff --git a/distribution/windows/alsoft.ini b/distribution/windows/alsoft.ini new file mode 100644 index 000000000..a142619a3 --- /dev/null +++ b/distribution/windows/alsoft.ini @@ -0,0 +1,2 @@ +[General] +stereo-mode=speakers diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..2213086f6 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,40 @@ +# Documents Index + +This repo includes several documents that explain both high-level and low-level concepts about Ryujinx and its functions. These are very useful for contributors, to get context that can be very difficult to acquire from just reading code. + +Intro to Ryujinx +================== + +Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#. +* The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions. +* 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. +* Audio output is entirely supported via C# wrappers for SDL2, with OpenAL & libsoundio as fallbacks. + +Getting Started +=============== + +- [Installing the .NET SDK](https://dotnet.microsoft.com/download) +- [Official .NET Docs](https://docs.microsoft.com/dotnet/core/) + +Contributing (Building, testing, benchmarking, profiling, etc.) +=============== + +If you want to contribute a code change to this repo, start here. + +- [Contributor Guide](../CONTRIBUTING.md) + +Coding Guidelines +================= + +- [C# coding style](coding-guidelines/coding-style.md) +- [Service Implementation Guidelines - WIP](https://gist.github.com/gdkchan/84ba88cd50efbe58d1babfaa7cd7c455) + +Project Docs +================= + +To be added. Many project files will contain basic XML docs for key functions and classes in the meantime. + +Other Information +================= + +- N/A diff --git a/docs/coding-guidelines/coding-style.md b/docs/coding-guidelines/coding-style.md new file mode 100644 index 000000000..9c84055d6 --- /dev/null +++ b/docs/coding-guidelines/coding-style.md @@ -0,0 +1,116 @@ +# C# Coding Style + +The general rule we follow is "use Visual Studio defaults". +Using an IDE that supports the `.editorconfig` standard will make this much simpler. + +1. We use [Allman style](http://en.wikipedia.org/wiki/Indent_style#Allman_style) braces, where each brace begins on a new line. A single line statement block can go without braces but the block must be properly indented on its own line and must not be nested in other statement blocks that use braces (See rule 18 for more details). One exception is that a `using` statement is permitted to be nested within another `using` statement by starting on the following line at the same indentation level, even if the nested `using` contains a controlled block. +2. We use four spaces of indentation (no tabs). +3. We use `_camelCase` for internal and private fields and use `readonly` where possible. Prefix internal and private instance fields with `_`, static fields with `s_` and thread static fields with `t_`. When used on static fields, `readonly` should come after `static` (e.g. `static readonly` not `readonly static`). Public fields should be used sparingly and should use PascalCasing with no prefix when used. +4. We avoid `this.` unless absolutely necessary. +5. We always specify the visibility, even if it's the default (e.g. + `private string _foo` not `string _foo`). Visibility should be the first modifier (e.g. + `public abstract` not `abstract public`). +6. Namespace imports should be specified at the top of the file, *outside* of `namespace` declarations. +7. Avoid more than one empty line at any time. For example, do not have two + blank lines between members of a type. +8. Avoid spurious free spaces. + For example avoid `if (someVar == 0)...`, where the dots mark the spurious free spaces. + Consider enabling "View White Space (Ctrl+R, Ctrl+W)" or "Edit -> Advanced -> View White Space" if using Visual Studio to aid detection. +9. If a file happens to differ in style from these guidelines (e.g. private members are named `m_member` + rather than `_member`), the existing style in that file takes precedence. +10. We only use `var` when the type is explicitly named on the right-hand side, typically due to either `new` or an explicit cast, e.g. `var stream = new FileStream(...)` not `var stream = OpenStandardInput()`. + - Similarly, target-typed `new()` can only be used when the type is explicitly named on the left-hand side, in a variable definition statement or a field definition statement. e.g. `FileStream stream = new(...);`, but not `stream = new(...);` (where the type was specified on a previous line). +11. We use language keywords instead of BCL types (e.g. `int, string, float` instead of `Int32, String, Single`, etc) for both type references as well as method calls (e.g. `int.Parse` instead of `Int32.Parse`). See issue [#13976](https://github.com/dotnet/runtime/issues/13976) for examples. +12. We use PascalCasing to name all our constant local variables and fields. The only exception is for interop code where the constant value should exactly match the name and value of the code you are calling via interop. +13. We use PascalCasing for all method names, including local functions. +14. We use ```nameof(...)``` instead of ```"..."``` whenever possible and relevant. +15. Fields should be specified at the top within type declarations. +16. When including non-ASCII characters in the source code use Unicode escape sequences (\uXXXX) instead of literal characters. Literal non-ASCII characters occasionally get garbled by a tool or editor. +17. When using labels (for goto), indent the label one less than the current indentation. +18. When using a single-statement if, we follow these conventions: + - Never use single-line form (for example: `if (source == null) throw new ArgumentNullException("source");`) + - Using braces is always accepted, and required if any block of an `if`/`else if`/.../`else` compound statement uses braces or if a single statement body spans multiple lines. + - Braces may be omitted only if the body of *every* block associated with an `if`/`else if`/.../`else` compound statement is placed on a single line. +19. Make all internal and private types static or sealed unless derivation from them is required. As with any implementation detail, they can be changed if/when derivation is required in the future. +20. XML docs should be used when writing interfaces or when a class/method is deemed sufficient in scope or complexity. +21. So-called [Magic Numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) should be defined as named constants before use (for example `for (int i = 56; i < 68; i++)` could read `for (int i = _currentAge; i < _retireAge; i++)`). + This may be ignored for trivial or syntactically common statements. + +An [EditorConfig](https://editorconfig.org "EditorConfig homepage") file (`.editorconfig`) has been provided at the root of the runtime repository, enabling C# auto-formatting conforming to the above guidelines. + +### Example File: + +``ShaderCache.cs:`` + +```C# +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader.DiskCache; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Memory cache of shader code. + /// + class ShaderCache : IDisposable + { + /// + /// Default flags used on the shader translation process. + /// + public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode; + + private readonly struct TranslatedShader + { + public readonly CachedShaderStage Shader; + public readonly ShaderProgram Program; + + public TranslatedShader(CachedShaderStage shader, ShaderProgram program) + { + Shader = shader; + Program = program; + } + } + ... + + /// + /// Processes the queue of shaders that must save their binaries to the disk cache. + /// + public void ProcessShaderCacheQueue() + { + // Check to see if the binaries for previously compiled shaders are ready, and save them out. + + while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave)) + { + ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false); + + if (result != ProgramLinkStatus.Incomplete) + { + if (result == ProgramLinkStatus.Success) + { + _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary()); + } + + _programsToSaveQueue.Dequeue(); + } + else + { + break; + } + } + } + } +} +``` + +For other languages, our current best guidance is consistency. When editing files, keep new code and changes consistent with the style in the files. For new files, it should conform to the style for that component. If there is a completely new component, anything that is reasonably broadly accepted is fine. diff --git a/docs/workflow/pr-guide.md b/docs/workflow/pr-guide.md new file mode 100644 index 000000000..cc2c5900b --- /dev/null +++ b/docs/workflow/pr-guide.md @@ -0,0 +1,56 @@ +# Pull Request Guide + +## Contributing Rules + +All contributions to Ryujinx/Ryujinx repository are made via pull requests (PRs) rather than through direct commits. The pull requests are reviewed and merged by the maintainers after a review and at least two approvals from the core development team. + +To merge pull requests, you must have write permissions in the repository. + +## Quick Code Review Rules + +* Do not mix unrelated changes in one pull request. For example, a code style change should never be mixed with a bug fix. +* All changes should follow the existing code style. You can read more about our code style at [docs/coding-guidelines](../coding-guidelines/coding-style.md). +* Adding external dependencies is to be avoided unless not doing so would introduce _significant_ complexity. Any dependency addition should be justified and discussed before merge. +* Use Draft pull requests for changes you are still working on but want early CI loop feedback. When you think your changes are ready for review, [change the status](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request) of your pull request. +* Rebase your changes when required or directly requested. Changes should always be commited on top of the upstream branch, not the other way around. +* If you are asked to make changes during the review process do them as a new commit. +* Only resolve GitHub conversations with reviewers once they have been addressed with a commit, or via a mutual agreement. + +## Pull Request Ownership + +Every pull request will have automatically have labels and reviewers assigned. The label not only indicates the code segment which the change touches but also the area reviewers to be assigned. + +If during the code review process a merge conflict occurs, the PR author is responsible for its resolution. Help will be provided if necessary although GitHub makes this easier by allowing simple conflict resolution using the [conflict-editor](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github). + +## Pull Request Builds + +When submitting a PR to the `Ryujinx/Ryujinx` repository, various builds will run validating many areas to ensure we keep developer productivity and product quality high. These various workflows can be tracked in the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of the repository. If the job continues to completion, the build artifacts will be uploaded and posted as a comment in the PR discussion. + +## Review Turnaround Times + +Ryujinx is a project that is maintained by volunteers on a completely free-time basis. As such we cannot guarantee any particular timeframe for pull request review and approval. Weeks to months are common for larger (>500 line) PRs but there are some additional best practises to avoid review purgatory. + +* Make the reviewers life easier wherever possible. Make use of descriptive commit names, code comments and XML docs where applicable. +* If there is disagreement on feedback then always lean on the side of the development team and community over any personal opinion. +* We're human. We miss things. We forget things. If there has been radio silence on your changes for a substantial period of time then do not hesitate to reach out directly either with something simple like "bump" on GitHub or a directly on Discord. + +To re-iterate, make the review as easy for us as possible, respond promptly and be comfortable to interact directly with us for anything else. + +## Merging Pull Requests + +Anyone with write access can merge a pull request manually when the following conditions have been met: + +* The PR has been approved by two reviewers and any other objections are addressed. + * You can request follow up reviews from the original reviewers if they requested changes. +* The PR successfully builds and passes all tests in the Continuous Integration (CI) system. In case of failures, refer to the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of your PR. + +Typically, PRs are merged as one commit (squash merges). It creates a simpler history than a Merge Commit. "Special circumstances" are rare, and typically mean that there are a series of cleanly separated changes that will be too hard to understand if squashed together, or for some reason we want to preserve the ability to dissect them. + +## Blocking Pull Request Merging + +If for whatever reason you would like to move your pull request back to an in-progress status to avoid merging it in the current form, you can turn the PR into a draft PR by selecting the option under the reviewers section. Alternatively, you can do that by adding [WIP] prefix to the pull request title. + +## Old Pull Request Policy + +From time to time we will review older PRs and check them for relevance. If we find the PR is inactive or no longer applies, we will close it. As the PR owner, you can simply reopen it if you feel your closed PR needs our attention. + diff --git a/global.json b/global.json new file mode 100644 index 000000000..391ba3c2a --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.100", + "rollForward": "latestFeature" + } +} diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..ec6c6444d --- /dev/null +++ b/nuget.config @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/ARMeilleure/ARMeilleure.csproj b/src/ARMeilleure/ARMeilleure.csproj new file mode 100644 index 000000000..550e50c26 --- /dev/null +++ b/src/ARMeilleure/ARMeilleure.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + true + + + + + + + + + + PreserveNewest + libarmeilleure-jitsupport.dylib + + + + + + <_Parameter1>Ryujinx.Tests + + + + diff --git a/src/ARMeilleure/Allocators.cs b/src/ARMeilleure/Allocators.cs new file mode 100644 index 000000000..fba302657 --- /dev/null +++ b/src/ARMeilleure/Allocators.cs @@ -0,0 +1,39 @@ +using ARMeilleure.Common; +using System; +using System.Runtime.CompilerServices; + +namespace ARMeilleure +{ + static class Allocators + { + [ThreadStatic] private static ArenaAllocator _default; + [ThreadStatic] private static ArenaAllocator _operands; + [ThreadStatic] private static ArenaAllocator _operations; + [ThreadStatic] private static ArenaAllocator _references; + [ThreadStatic] private static ArenaAllocator _liveRanges; + [ThreadStatic] private static ArenaAllocator _liveIntervals; + + public static ArenaAllocator Default => GetAllocator(ref _default, 256 * 1024, 4); + public static ArenaAllocator Operands => GetAllocator(ref _operands, 64 * 1024, 8); + public static ArenaAllocator Operations => GetAllocator(ref _operations, 64 * 1024, 8); + public static ArenaAllocator References => GetAllocator(ref _references, 64 * 1024, 8); + public static ArenaAllocator LiveRanges => GetAllocator(ref _liveRanges, 64 * 1024, 8); + public static ArenaAllocator LiveIntervals => GetAllocator(ref _liveIntervals, 64 * 1024, 8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ArenaAllocator GetAllocator(ref ArenaAllocator alloc, uint pageSize, uint pageCount) + { + alloc ??= new ArenaAllocator(pageSize, pageCount); + + return alloc; + } + + public static void ResetAll() + { + Default.Reset(); + Operands.Reset(); + Operations.Reset(); + References.Reset(); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs b/src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs new file mode 100644 index 000000000..00ffd1958 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs @@ -0,0 +1,270 @@ +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class Arm64Optimizer + { + private const int MaxConstantUses = 10000; + + public static void RunPass(ControlFlowGraph cfg) + { + var constants = new Dictionary(); + + Operand GetConstantCopy(BasicBlock block, Operation operation, Operand source) + { + // If the constant has many uses, we also force a new constant mov to be added, in order + // to avoid overflow of the counts field (that is limited to 16 bits). + if (!constants.TryGetValue(source.Value, out var constant) || constant.UsesCount > MaxConstantUses) + { + constant = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, constant, source); + + block.Operations.AddBefore(operation, copyOp); + + constants[source.Value] = constant; + } + + return constant; + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + constants.Clear(); + + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + // Insert copies for constants that can't fit on a 32-bit immediate. + // Doing this early unblocks a few optimizations. + if (node.Instruction == Instruction.Add) + { + Operand src1 = node.GetSource(0); + Operand src2 = node.GetSource(1); + + if (src1.Kind == OperandKind.Constant && (src1.Relocatable || ConstTooLong(src1, OperandType.I32))) + { + node.SetSource(0, GetConstantCopy(block, node, src1)); + } + + if (src2.Kind == OperandKind.Constant && (src2.Relocatable || ConstTooLong(src2, OperandType.I32))) + { + node.SetSource(1, GetConstantCopy(block, node, src2)); + } + } + + // Try to fold something like: + // lsl x1, x1, #2 + // add x0, x0, x1 + // ldr x0, [x0] + // add x2, x2, #16 + // ldr x2, [x2] + // Into: + // ldr x0, [x0, x1, lsl #2] + // ldr x2, [x2, #16] + if (IsMemoryLoadOrStore(node.Instruction)) + { + OperandType type; + + if (node.Destination != default) + { + type = node.Destination.Type; + } + else + { + type = node.GetSource(1).Type; + } + + Operand memOp = GetMemoryOperandOrNull(node.GetSource(0), type); + + if (memOp != default) + { + node.SetSource(0, memOp); + } + } + } + } + + Optimizer.RemoveUnusedNodes(cfg); + } + + private static Operand GetMemoryOperandOrNull(Operand addr, OperandType type) + { + Operand baseOp = addr; + + // First we check if the address is the result of a local X with immediate + // addition. If that is the case, then the baseOp is X, and the memory operand immediate + // becomes the addition immediate. Otherwise baseOp keeps being the address. + int imm = GetConstOp(ref baseOp, type); + if (imm != 0) + { + return MemoryOp(type, baseOp, default, Multiplier.x1, imm); + } + + // Now we check if the baseOp is the result of a local Y with a local Z addition. + // If that is the case, we now set baseOp to Y and indexOp to Z. We further check + // if Z is the result of a left shift of local W by a value == 0 or == Log2(AccessSize), + // if that is the case, we set indexOp to W and adjust the scale value of the memory operand + // to match that of the left shift. + // There is one missed case, which is the address being a shift result, but this is + // probably not worth optimizing as it should never happen. + (Operand indexOp, Multiplier scale) = GetIndexOp(ref baseOp, type); + + // If baseOp is still equal to address, then there's nothing that can be optimized. + if (baseOp == addr) + { + return default; + } + + return MemoryOp(type, baseOp, indexOp, scale, 0); + } + + private static int GetConstOp(ref Operand baseOp, OperandType accessType) + { + Operation operation = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (operation == default) + { + return 0; + } + + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + Operand constOp; + Operand otherOp; + + if (src1.Kind == OperandKind.Constant && src2.Kind == OperandKind.LocalVariable) + { + constOp = src1; + otherOp = src2; + } + else if (src1.Kind == OperandKind.LocalVariable && src2.Kind == OperandKind.Constant) + { + constOp = src2; + otherOp = src1; + } + else + { + return 0; + } + + // If we have addition by a constant that we can't encode on the instruction, + // then we can't optimize it further. + if (ConstTooLong(constOp, accessType)) + { + return 0; + } + + baseOp = otherOp; + + return constOp.AsInt32(); + } + + private static (Operand, Multiplier) GetIndexOp(ref Operand baseOp, OperandType accessType) + { + Operand indexOp = default; + + Multiplier scale = Multiplier.x1; + + Operation addOp = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (addOp == default) + { + return (indexOp, scale); + } + + Operand src1 = addOp.GetSource(0); + Operand src2 = addOp.GetSource(1); + + if (src1.Kind != OperandKind.LocalVariable || src2.Kind != OperandKind.LocalVariable) + { + return (indexOp, scale); + } + + baseOp = src1; + indexOp = src2; + + Operation shlOp = GetAsgOpWithInst(src1, Instruction.ShiftLeft); + + bool indexOnSrc2 = false; + + if (shlOp == default) + { + shlOp = GetAsgOpWithInst(src2, Instruction.ShiftLeft); + + indexOnSrc2 = true; + } + + if (shlOp != default) + { + Operand shSrc = shlOp.GetSource(0); + Operand shift = shlOp.GetSource(1); + + int maxShift = Assembler.GetScaleForType(accessType); + + if (shSrc.Kind == OperandKind.LocalVariable && + shift.Kind == OperandKind.Constant && + (shift.Value == 0 || shift.Value == (ulong)maxShift)) + { + scale = shift.Value switch + { + 1 => Multiplier.x2, + 2 => Multiplier.x4, + 3 => Multiplier.x8, + 4 => Multiplier.x16, + _ => Multiplier.x1, + }; + + baseOp = indexOnSrc2 ? src1 : src2; + indexOp = shSrc; + } + } + + return (indexOp, scale); + } + + private static Operation GetAsgOpWithInst(Operand op, Instruction inst) + { + // If we have multiple assignments, folding is not safe + // as the value may be different depending on the + // control flow path. + if (op.AssignmentsCount != 1) + { + return default; + } + + Operation asgOp = op.Assignments[0]; + + if (asgOp.Instruction != inst) + { + return default; + } + + return asgOp; + } + + private static bool IsMemoryLoadOrStore(Instruction inst) + { + return inst == Instruction.Load || inst == Instruction.Store; + } + + private static bool ConstTooLong(Operand constOp, OperandType accessType) + { + if ((uint)constOp.Value != constOp.Value) + { + return true; + } + + return !CodeGenCommon.ConstFitsOnUImm12(constOp.AsInt32(), accessType); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs b/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs new file mode 100644 index 000000000..5db898591 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs @@ -0,0 +1,49 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.CodeGen.Arm64 +{ + enum ArmCondition + { + Eq = 0, + Ne = 1, + GeUn = 2, + LtUn = 3, + Mi = 4, + Pl = 5, + Vs = 6, + Vc = 7, + GtUn = 8, + LeUn = 9, + Ge = 10, + Lt = 11, + Gt = 12, + Le = 13, + Al = 14, + Nv = 15, + } + + static class ComparisonArm64Extensions + { + public static ArmCondition ToArmCondition(this Comparison comp) + { + return comp switch + { +#pragma warning disable IDE0055 // Disable formatting + Comparison.Equal => ArmCondition.Eq, + Comparison.NotEqual => ArmCondition.Ne, + Comparison.Greater => ArmCondition.Gt, + Comparison.LessOrEqual => ArmCondition.Le, + Comparison.GreaterUI => ArmCondition.GtUn, + Comparison.LessOrEqualUI => ArmCondition.LeUn, + Comparison.GreaterOrEqual => ArmCondition.Ge, + Comparison.Less => ArmCondition.Lt, + Comparison.GreaterOrEqualUI => ArmCondition.GeUn, + Comparison.LessUI => ArmCondition.LtUn, +#pragma warning restore IDE0055 + + _ => throw new ArgumentException(null, nameof(comp)), + }; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs b/src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs new file mode 100644 index 000000000..20ccfd4ba --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.Arm64 +{ + enum ArmExtensionType + { + Uxtb = 0, + Uxth = 1, + Uxtw = 2, + Uxtx = 3, + Sxtb = 4, + Sxth = 5, + Sxtw = 6, + Sxtx = 7, + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs b/src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs new file mode 100644 index 000000000..f32407c43 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs @@ -0,0 +1,11 @@ + +namespace ARMeilleure.CodeGen.Arm64 +{ + enum ArmShiftType + { + Lsl = 0, + Lsr = 1, + Asr = 2, + Ror = 3, + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/Assembler.cs b/src/ARMeilleure/CodeGen/Arm64/Assembler.cs new file mode 100644 index 000000000..41684faf2 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/Assembler.cs @@ -0,0 +1,1162 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Diagnostics; +using System.IO; +using static ARMeilleure.IntermediateRepresentation.Operand; + +namespace ARMeilleure.CodeGen.Arm64 +{ + class Assembler + { + public const uint SfFlag = 1u << 31; + + private const int SpRegister = 31; + private const int ZrRegister = 31; + + private readonly Stream _stream; + + public Assembler(Stream stream) + { + _stream = stream; + } + + public void Add(Operand rd, Operand rn, Operand rm, ArmExtensionType extensionType, int shiftAmount = 0) + { + WriteInstructionAuto(0x0b200000u, rd, rn, rm, extensionType, shiftAmount); + } + + public void Add(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0, bool immForm = false) + { + WriteInstructionAuto(0x11000000u, 0x0b000000u, rd, rn, rm, shiftType, shiftAmount, immForm); + } + + public void And(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x12000000u, 0x0a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Ands(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x72000000u, 0x6a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Asr(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Sbfm(rd, rn, shift, mask); + } + else + { + Asrv(rd, rn, rm); + } + } + + public void Asrv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02800u, rd, rn, rm); + } + + public void B(int imm) + { + WriteUInt32(0x14000000u | EncodeSImm26_2(imm)); + } + + public void B(ArmCondition condition, int imm) + { + WriteUInt32(0x54000000u | (uint)condition | (EncodeSImm19_2(imm) << 5)); + } + + public void Blr(Operand rn) + { + WriteUInt32(0xd63f0000u | (EncodeReg(rn) << 5)); + } + + public void Br(Operand rn) + { + WriteUInt32(0xd61f0000u | (EncodeReg(rn) << 5)); + } + + public void Brk() + { + WriteUInt32(0xd4200000u); + } + + public void Cbz(Operand rt, int imm) + { + WriteInstructionAuto(0x34000000u | (EncodeSImm19_2(imm) << 5), rt); + } + + public void Cbnz(Operand rt, int imm) + { + WriteInstructionAuto(0x35000000u | (EncodeSImm19_2(imm) << 5), rt); + } + + public void Clrex(int crm = 15) + { + WriteUInt32(0xd503305fu | (EncodeUImm4(crm) << 8)); + } + + public void Clz(Operand rd, Operand rn) + { + WriteInstructionAuto(0x5ac01000u, rd, rn); + } + + public void CmeqVector(Operand rd, Operand rn, Operand rm, int size, bool q = true) + { + Debug.Assert((uint)size < 4); + WriteSimdInstruction(0x2e208c00u | ((uint)size << 22), rd, rn, rm, q); + } + + public void Cmp(Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Subs(Factory.Register(ZrRegister, RegisterType.Integer, rn.Type), rn, rm, shiftType, shiftAmount); + } + + public void Csel(Operand rd, Operand rn, Operand rm, ArmCondition condition) + { + WriteInstructionBitwiseAuto(0x1a800000u | ((uint)condition << 12), rd, rn, rm); + } + + public void Cset(Operand rd, ArmCondition condition) + { + var zr = Factory.Register(ZrRegister, RegisterType.Integer, rd.Type); + Csinc(rd, zr, zr, (ArmCondition)((int)condition ^ 1)); + } + + public void Csinc(Operand rd, Operand rn, Operand rm, ArmCondition condition) + { + WriteInstructionBitwiseAuto(0x1a800400u | ((uint)condition << 12), rd, rn, rm); + } + + public void Dmb(uint option) + { + WriteUInt32(0xd50330bfu | (option << 8)); + } + + public void DupScalar(Operand rd, Operand rn, int index, int size) + { + WriteInstruction(0x5e000400u | (EncodeIndexSizeImm5(index, size) << 16), rd, rn); + } + + public void Eor(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x52000000u, 0x4a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void EorVector(Operand rd, Operand rn, Operand rm, bool q = true) + { + WriteSimdInstruction(0x2e201c00u, rd, rn, rm, q); + } + + public void Extr(Operand rd, Operand rn, Operand rm, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionBitwiseAuto(0x13800000u | n | (EncodeUImm6(imms) << 10), rd, rn, rm); + } + + public void FaddScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e202800u, rd, rn, rm); + } + + public void FcvtScalar(Operand rd, Operand rn) + { + uint instruction = 0x1e224000u | (rd.Type == OperandType.FP64 ? 1u << 15 : 1u << 22); + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public void FdivScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e201800u, rd, rn, rm); + } + + public void Fmov(Operand rd, Operand rn) + { + WriteFPInstructionAuto(0x1e204000u, rd, rn); + } + + public void Fmov(Operand rd, Operand rn, bool topHalf) + { + Debug.Assert(rd.Type.IsInteger() != rn.Type.IsInteger()); + Debug.Assert(rd.Type == OperandType.I64 || rn.Type == OperandType.I64 || !topHalf); + + uint opcode = rd.Type.IsInteger() ? 0b110u : 0b111u; + + uint rmode = topHalf ? 1u << 19 : 0u; + uint ftype = rd.Type == OperandType.FP64 || rn.Type == OperandType.FP64 ? 1u << 22 : 0u; + uint sf = rd.Type == OperandType.I64 || rn.Type == OperandType.I64 ? SfFlag : 0u; + + WriteUInt32(0x1e260000u | (opcode << 16) | rmode | ftype | sf | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public void FmulScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e200800u, rd, rn, rm); + } + + public void FnegScalar(Operand rd, Operand rn) + { + WriteFPInstructionAuto(0x1e214000u, rd, rn); + } + + public void FsubScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e203800u, rd, rn, rm); + } + + public void Ins(Operand rd, Operand rn, int index, int size) + { + WriteInstruction(0x4e001c00u | (EncodeIndexSizeImm5(index, size) << 16), rd, rn); + } + + public void Ins(Operand rd, Operand rn, int srcIndex, int dstIndex, int size) + { + uint imm4 = (uint)srcIndex << size; + Debug.Assert((uint)srcIndex < (16u >> size)); + WriteInstruction(0x6e000400u | (imm4 << 11) | (EncodeIndexSizeImm5(dstIndex, size) << 16), rd, rn); + } + + public void Ldaxp(Operand rt, Operand rt2, Operand rn) + { + WriteInstruction(0x887f8000u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rt2); + } + + public void Ldaxr(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn); + } + + public void Ldaxrb(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u, rt, rn); + } + + public void Ldaxrh(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u | (1u << 30), rt, rn); + } + + public void LdpRiPost(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x28c00000u, 0x2cc00000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void LdpRiPre(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29c00000u, 0x2dc00000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void LdpRiUn(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29400000u, 0x2d400000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void Ldr(Operand rt, Operand rn) + { + if (rn.Kind == OperandKind.Memory) + { + MemoryOperand memOp = rn.GetMemory(); + + if (memOp.Index != default) + { + Debug.Assert(memOp.Displacement == 0); + Debug.Assert(memOp.Scale == Multiplier.x1 || (int)memOp.Scale == GetScaleForType(rt.Type)); + LdrRr(rt, memOp.BaseAddress, memOp.Index, ArmExtensionType.Uxtx, memOp.Scale != Multiplier.x1); + } + else + { + LdrRiUn(rt, memOp.BaseAddress, memOp.Displacement); + } + } + else + { + LdrRiUn(rt, rn, 0); + } + } + + public void LdrLit(Operand rt, int offset) + { + uint instruction = 0x18000000u | (EncodeSImm19_2(offset) << 5); + + if (rt.Type == OperandType.I64) + { + instruction |= 1u << 30; + } + + WriteInstruction(instruction, rt); + } + + public void LdrRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400400u, 0x3c400400u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400c00u, 0x3c400c00u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb9400000u, 0x3d400000u, rt.Type) | (EncodeUImm12(imm, rt.Type) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void LdrRr(Operand rt, Operand rn, Operand rm, ArmExtensionType extensionType, bool shift) + { + uint instruction = GetLdrStrInstruction(0xb8600800u, 0x3ce00800u, rt.Type); + WriteInstructionLdrStrAuto(instruction, rt, rn, rm, extensionType, shift); + } + + public void LdrbRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38400400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrbRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38400c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrbRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x39400000u | (EncodeUImm12(imm, 0) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void LdrhRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78400400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrhRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78400c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrhRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x79400000u | (EncodeUImm12(imm, 1) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void Ldur(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400000u, 0x3c400000u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void Lsl(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Ubfm(rd, rn, -shift & mask, mask - shift); + } + else + { + Lslv(rd, rn, rm); + } + } + + public void Lslv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02000u, rd, rn, rm); + } + + public void Lsr(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Ubfm(rd, rn, shift, mask); + } + else + { + Lsrv(rd, rn, rm); + } + } + + public void Lsrv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02400u, rd, rn, rm); + } + + public void Madd(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstructionAuto(0x1b000000u, rd, rn, rm, ra); + } + + public void Mul(Operand rd, Operand rn, Operand rm) + { + Madd(rd, rn, rm, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type)); + } + + public void Mov(Operand rd, Operand rn) + { + if (rd.Type.IsInteger()) + { + Orr(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn); + } + else + { + OrrVector(rd, rn, rn); + } + } + + public void MovSp(Operand rd, Operand rn) + { + if (rd.GetRegister().Index == SpRegister || + rn.GetRegister().Index == SpRegister) + { + Add(rd, rn, Factory.Const(rd.Type, 0), immForm: true); + } + else + { + Mov(rd, rn); + } + } + + public void Mov(Operand rd, int imm) + { + Movz(rd, imm, 0); + } + + public void Movz(Operand rd, int imm, int hw) + { + Debug.Assert((hw & (rd.Type == OperandType.I64 ? 3 : 1)) == hw); + WriteInstructionAuto(0x52800000u | (EncodeUImm16(imm) << 5) | ((uint)hw << 21), rd); + } + + public void Movk(Operand rd, int imm, int hw) + { + Debug.Assert((hw & (rd.Type == OperandType.I64 ? 3 : 1)) == hw); + WriteInstructionAuto(0x72800000u | (EncodeUImm16(imm) << 5) | ((uint)hw << 21), rd); + } + + public void Mrs(Operand rt, uint o0, uint op1, uint crn, uint crm, uint op2) + { + uint instruction = 0xd5300000u; + + instruction |= (op2 & 7) << 5; + instruction |= (crm & 15) << 8; + instruction |= (crn & 15) << 12; + instruction |= (op1 & 7) << 16; + instruction |= (o0 & 1) << 19; + + WriteInstruction(instruction, rt); + } + + public void Mvn(Operand rd, Operand rn, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Orn(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn, shiftType, shiftAmount); + } + + public void Neg(Operand rd, Operand rn, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Sub(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn, shiftType, shiftAmount); + } + + public void Orn(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x2a200000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Orr(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x32000000u, 0x2a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void OrrVector(Operand rd, Operand rn, Operand rm, bool q = true) + { + WriteSimdInstruction(0x0ea01c00u, rd, rn, rm, q); + } + + public void Ret(Operand rn) + { + WriteUInt32(0xd65f0000u | (EncodeReg(rn) << 5)); + } + + public void Rev(Operand rd, Operand rn) + { + uint opc0 = rd.Type == OperandType.I64 ? 1u << 10 : 0u; + WriteInstructionAuto(0x5ac00800u | opc0, rd, rn); + } + + public void Ror(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Extr(rd, rn, rn, shift); + } + else + { + Rorv(rd, rn, rm); + } + } + + public void Rorv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02c00u, rd, rn, rm); + } + + public void Sbfm(Operand rd, Operand rn, int immr, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionAuto(0x13000000u | n | (EncodeUImm6(imms) << 10) | (EncodeUImm6(immr) << 16), rd, rn); + } + + public void ScvtfScalar(Operand rd, Operand rn) + { + uint instruction = 0x1e220000u; + + if (rn.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteFPInstructionAuto(instruction, rd, rn); + } + + public void Sdiv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16Auto(0x1ac00c00u, rd, rn, rm); + } + + public void Smulh(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x9b407c00u, rd, rn, rm); + } + + public void Stlxp(Operand rt, Operand rt2, Operand rn, Operand rs) + { + WriteInstruction(0x88208000u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rs, rt2); + } + + public void Stlxr(Operand rt, Operand rn, Operand rs) + { + WriteInstructionRm16(0x0800fc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rs); + } + + public void Stlxrb(Operand rt, Operand rn, Operand rs) + { + WriteInstructionRm16(0x0800fc00u, rt, rn, rs); + } + + public void Stlxrh(Operand rt, Operand rn, Operand rs) + { + WriteInstructionRm16(0x0800fc00u | (1u << 30), rt, rn, rs); + } + + public void StpRiPost(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x28800000u, 0x2c800000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void StpRiPre(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29800000u, 0x2d800000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void StpRiUn(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29000000u, 0x2d000000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void Str(Operand rt, Operand rn) + { + if (rn.Kind == OperandKind.Memory) + { + MemoryOperand memOp = rn.GetMemory(); + + if (memOp.Index != default) + { + Debug.Assert(memOp.Displacement == 0); + Debug.Assert(memOp.Scale == Multiplier.x1 || (int)memOp.Scale == GetScaleForType(rt.Type)); + StrRr(rt, memOp.BaseAddress, memOp.Index, ArmExtensionType.Uxtx, memOp.Scale != Multiplier.x1); + } + else + { + StrRiUn(rt, memOp.BaseAddress, memOp.Displacement); + } + } + else + { + StrRiUn(rt, rn, 0); + } + } + + public void StrRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000400u, 0x3c000400u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000c00u, 0x3c000c00u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb9000000u, 0x3d000000u, rt.Type) | (EncodeUImm12(imm, rt.Type) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void StrRr(Operand rt, Operand rn, Operand rm, ArmExtensionType extensionType, bool shift) + { + uint instruction = GetLdrStrInstruction(0xb8200800u, 0x3ca00800u, rt.Type); + WriteInstructionLdrStrAuto(instruction, rt, rn, rm, extensionType, shift); + } + + public void StrbRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38000400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrbRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38000c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrbRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x39000000u | (EncodeUImm12(imm, 0) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void StrhRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78000400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrhRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78000c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrhRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x79000000u | (EncodeUImm12(imm, 1) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void Stur(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000000u, 0x3c000000u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void Sub(Operand rd, Operand rn, Operand rm, ArmExtensionType extensionType, int shiftAmount = 0) + { + WriteInstructionAuto(0x4b200000u, rd, rn, rm, extensionType, shiftAmount); + } + + public void Sub(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionAuto(0x51000000u, 0x4b000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Subs(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionAuto(0x71000000u, 0x6b000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Sxtb(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 7); + } + + public void Sxth(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 15); + } + + public void Sxtw(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 31); + } + + public void Tst(Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Ands(Factory.Register(ZrRegister, RegisterType.Integer, rn.Type), rn, rm, shiftType, shiftAmount); + } + + public void Ubfm(Operand rd, Operand rn, int immr, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionAuto(0x53000000u | n | (EncodeUImm6(imms) << 10) | (EncodeUImm6(immr) << 16), rd, rn); + } + + public void UcvtfScalar(Operand rd, Operand rn) + { + uint instruction = 0x1e230000u; + + if (rn.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteFPInstructionAuto(instruction, rd, rn); + } + + public void Udiv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16Auto(0x1ac00800u, rd, rn, rm); + } + + public void Umov(Operand rd, Operand rn, int index, int size) + { + uint q = size == 3 ? 1u << 30 : 0u; + WriteInstruction(0x0e003c00u | (EncodeIndexSizeImm5(index, size) << 16) | q, rd, rn); + } + + public void Umulh(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x9bc07c00u, rd, rn, rm); + } + + public void Uxtb(Operand rd, Operand rn) + { + Ubfm(rd, rn, 0, 7); + } + + public void Uxth(Operand rd, Operand rn) + { + Ubfm(rd, rn, 0, 15); + } + + private void WriteInstructionAuto( + uint instI, + uint instR, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0, + bool immForm = false) + { + if (rm.Kind == OperandKind.Constant && (rm.Value != 0 || immForm)) + { + Debug.Assert(shiftAmount == 0); + int imm = rm.AsInt32(); + Debug.Assert((uint)imm == rm.Value); + if (imm != 0 && (imm & 0xfff) == 0) + { + instI |= 1 << 22; // sh flag + imm >>= 12; + } + WriteInstructionAuto(instI | (EncodeUImm12(imm, 0) << 10), rd, rn); + } + else + { + instR |= EncodeUImm6(shiftAmount) << 10; + instR |= (uint)shiftType << 22; + + WriteInstructionRm16Auto(instR, rd, rn, rm); + } + } + + private void WriteInstructionAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmExtensionType extensionType, + int shiftAmount = 0) + { + Debug.Assert((uint)shiftAmount <= 4); + + instruction |= (uint)shiftAmount << 10; + instruction |= (uint)extensionType << 13; + + WriteInstructionRm16Auto(instruction, rd, rn, rm); + } + + private void WriteInstructionBitwiseAuto( + uint instI, + uint instR, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0) + { + if (rm.Kind == OperandKind.Constant && rm.Value != 0) + { + Debug.Assert(shiftAmount == 0); + bool canEncode = CodeGenCommon.TryEncodeBitMask(rm, out int immN, out int immS, out int immR); + Debug.Assert(canEncode); + uint instruction = instI | ((uint)immS << 10) | ((uint)immR << 16) | ((uint)immN << 22); + + WriteInstructionAuto(instruction, rd, rn); + } + else + { + WriteInstructionBitwiseAuto(instR, rd, rn, rm, shiftType, shiftAmount); + } + } + + private void WriteInstructionBitwiseAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + instruction |= EncodeUImm6(shiftAmount) << 10; + instruction |= (uint)shiftType << 22; + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteInstructionLdrStrAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmExtensionType extensionType, + bool shift) + { + if (shift) + { + instruction |= 1u << 12; + } + + instruction |= (uint)extensionType << 13; + + if (rd.Type == OperandType.I64) + { + instruction |= 1u << 30; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteInstructionAuto(uint instruction, Operand rd) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd); + } + + public void WriteInstructionAuto(uint instruction, Operand rd, Operand rn) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd, rn); + } + + private void WriteInstructionAuto(uint instruction, Operand rd, Operand rn, Operand rm, Operand ra) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd, rn, rm, ra); + } + + public void WriteInstruction(uint instruction, Operand rd) + { + WriteUInt32(instruction | EncodeReg(rd)); + } + + public void WriteInstruction(uint instruction, Operand rd, Operand rn) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public void WriteInstruction(uint instruction, Operand rd, Operand rn, Operand rm) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 10)); + } + + public void WriteInstruction(uint instruction, Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(ra) << 10) | (EncodeReg(rm) << 16)); + } + + private void WriteFPInstructionAuto(uint instruction, Operand rd, Operand rn) + { + if (rd.Type == OperandType.FP64) + { + instruction |= 1u << 22; + } + + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + private void WriteFPInstructionAuto(uint instruction, Operand rd, Operand rn, Operand rm) + { + if (rd.Type == OperandType.FP64) + { + instruction |= 1u << 22; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteSimdInstruction(uint instruction, Operand rd, Operand rn, Operand rm, bool q = true) + { + if (q) + { + instruction |= 1u << 30; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteInstructionRm16Auto(uint instruction, Operand rd, Operand rn, Operand rm) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + public void WriteInstructionRm16(uint instruction, Operand rd, Operand rn, Operand rm) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 16)); + } + + public void WriteInstructionRm16NoRet(uint instruction, Operand rn, Operand rm) + { + WriteUInt32(instruction | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 16)); + } + + private static uint GetLdpStpInstruction(uint intInst, uint vecInst, int imm, OperandType type) + { + uint instruction; + int scale; + + if (type.IsInteger()) + { + instruction = intInst; + + if (type == OperandType.I64) + { + instruction |= SfFlag; + scale = 3; + } + else + { + scale = 2; + } + } + else + { + int opc = type switch + { + OperandType.FP32 => 0, + OperandType.FP64 => 1, + _ => 2, + }; + + instruction = vecInst | ((uint)opc << 30); + scale = 2 + opc; + } + + instruction |= (EncodeSImm7(imm, scale) << 15); + + return instruction; + } + + private static uint GetLdrStrInstruction(uint intInst, uint vecInst, OperandType type) + { + uint instruction; + + if (type.IsInteger()) + { + instruction = intInst; + + if (type == OperandType.I64) + { + instruction |= 1 << 30; + } + } + else + { + instruction = vecInst; + + if (type == OperandType.V128) + { + instruction |= 1u << 23; + } + else + { + instruction |= type == OperandType.FP32 ? 2u << 30 : 3u << 30; + } + } + + return instruction; + } + + private static uint EncodeIndexSizeImm5(int index, int size) + { + Debug.Assert((uint)size < 4); + Debug.Assert((uint)index < (16u >> size), $"Invalid index {index} and size {size} combination."); + return ((uint)index << (size + 1)) | (1u << size); + } + + private static uint EncodeSImm7(int value, int scale) + { + uint imm = (uint)(value >> scale) & 0x7f; + Debug.Assert(((int)imm << 25) >> (25 - scale) == value, $"Failed to encode constant 0x{value:X} with scale {scale}."); + return imm; + } + + private static uint EncodeSImm9(int value) + { + uint imm = (uint)value & 0x1ff; + Debug.Assert(((int)imm << 23) >> 23 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeSImm19_2(int value) + { + uint imm = (uint)(value >> 2) & 0x7ffff; + Debug.Assert(((int)imm << 13) >> 11 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeSImm26_2(int value) + { + uint imm = (uint)(value >> 2) & 0x3ffffff; + Debug.Assert(((int)imm << 6) >> 4 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm4(int value) + { + uint imm = (uint)value & 0xf; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm6(int value) + { + uint imm = (uint)value & 0x3f; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm12(int value, OperandType type) + { + return EncodeUImm12(value, GetScaleForType(type)); + } + + private static uint EncodeUImm12(int value, int scale) + { + uint imm = (uint)(value >> scale) & 0xfff; + Debug.Assert((int)imm << scale == value, $"Failed to encode constant 0x{value:X} with scale {scale}."); + return imm; + } + + private static uint EncodeUImm16(int value) + { + uint imm = (uint)value & 0xffff; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeReg(Operand reg) + { + if (reg.Kind == OperandKind.Constant && reg.Value == 0) + { + return ZrRegister; + } + + uint regIndex = (uint)reg.GetRegister().Index; + Debug.Assert(reg.Kind == OperandKind.Register); + Debug.Assert(regIndex < 32); + return regIndex; + } + + public static int GetScaleForType(OperandType type) + { + return type switch + { + OperandType.I32 => 2, + OperandType.I64 => 3, + OperandType.FP32 => 2, + OperandType.FP64 => 3, + OperandType.V128 => 4, + _ => throw new ArgumentException($"Invalid type {type}."), + }; + } + +#pragma warning disable IDE0051 // Remove unused private member + private void WriteInt16(short value) + { + WriteUInt16((ushort)value); + } + + private void WriteInt32(int value) + { + WriteUInt32((uint)value); + } + + private void WriteByte(byte value) + { + _stream.WriteByte(value); + } +#pragma warning restore IDE0051 + + private void WriteUInt16(ushort value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + } + + private void WriteUInt32(uint value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs b/src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs new file mode 100644 index 000000000..a487c2ed3 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs @@ -0,0 +1,96 @@ +using System; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CallingConvention + { + private const int RegistersMask = unchecked((int)0xffffffff); + + // Some of those register have specific roles and can't be used as general purpose registers. + // X18 - Reserved for platform specific usage. + // X29 - Frame pointer. + // X30 - Return address. + // X31 - Not an actual register, in some cases maps to SP, and in others to ZR. + private const int ReservedRegsMask = (1 << CodeGenCommon.ReservedRegister) | (1 << 18) | (1 << 29) | (1 << 30) | (1 << 31); + + public static int GetIntAvailableRegisters() + { + return RegistersMask & ~ReservedRegsMask; + } + + public static int GetVecAvailableRegisters() + { + return RegistersMask; + } + + public static int GetIntCallerSavedRegisters() + { + return (GetIntCalleeSavedRegisters() ^ RegistersMask) & ~ReservedRegsMask; + } + + public static int GetFpCallerSavedRegisters() + { + return GetFpCalleeSavedRegisters() ^ RegistersMask; + } + + public static int GetVecCallerSavedRegisters() + { + return GetVecCalleeSavedRegisters() ^ RegistersMask; + } + + public static int GetIntCalleeSavedRegisters() + { + return 0x1ff80000; // X19 to X28 + } + + public static int GetFpCalleeSavedRegisters() + { + return 0xff00; // D8 to D15 + } + + public static int GetVecCalleeSavedRegisters() + { + return 0; + } + + public static int GetArgumentsOnRegsCount() + { + return 8; + } + + public static int GetIntArgumentRegister(int index) + { + if ((uint)index < (uint)GetArgumentsOnRegsCount()) + { + return index; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static int GetVecArgumentRegister(int index) + { + if ((uint)index < (uint)GetArgumentsOnRegsCount()) + { + return index; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static int GetIntReturnRegister() + { + return 0; + } + + public static int GetIntReturnRegisterHigh() + { + return 1; + } + + public static int GetVecReturnRegister() + { + return 0; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs new file mode 100644 index 000000000..1f0148d5e --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs @@ -0,0 +1,91 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Numerics; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CodeGenCommon + { + public const int TcAddressRegister = 8; + public const int ReservedRegister = 17; + + public static bool ConstFitsOnSImm7(int value, int scale) + { + return (((value >> scale) << 25) >> (25 - scale)) == value; + } + + public static bool ConstFitsOnSImm9(int value) + { + return ((value << 23) >> 23) == value; + } + + public static bool ConstFitsOnUImm12(int value) + { + return (value & 0xfff) == value; + } + + public static bool ConstFitsOnUImm12(int value, OperandType type) + { + int scale = Assembler.GetScaleForType(type); + return (((value >> scale) & 0xfff) << scale) == value; + } + + public static bool TryEncodeBitMask(Operand operand, out int immN, out int immS, out int immR) + { + return TryEncodeBitMask(operand.Type, operand.Value, out immN, out immS, out immR); + } + + public static bool TryEncodeBitMask(OperandType type, ulong value, out int immN, out int immS, out int immR) + { + if (type == OperandType.I32) + { + value |= value << 32; + } + + return TryEncodeBitMask(value, out immN, out immS, out immR); + } + + public static bool TryEncodeBitMask(ulong value, out int immN, out int immS, out int immR) + { + // Some special values also can't be encoded: + // 0 can't be encoded because we need to subtract 1 from onesCount (which would became negative if 0). + // A value with all bits set can't be encoded because it is reserved according to the spec, because: + // Any value AND all ones will be equal itself, so it's effectively a no-op. + // Any value OR all ones will be equal all ones, so one can just use MOV. + // Any value XOR all ones will be equal its inverse, so one can just use MVN. + if (value == 0 || value == ulong.MaxValue) + { + immN = 0; + immS = 0; + immR = 0; + + return false; + } + + // Normalize value, rotating it such that the LSB is 1: Ensures we get a complete element that has not + // been cut-in-half across the word boundary. + int rotation = BitOperations.TrailingZeroCount(value & (value + 1)); + ulong rotatedValue = ulong.RotateRight(value, rotation); + + // Now that we have a complete element in the LSB with the LSB = 1, determine size and number of ones + // in element. + int elementSize = BitOperations.TrailingZeroCount(rotatedValue & (rotatedValue + 1)); + int onesInElement = BitOperations.TrailingZeroCount(~rotatedValue); + + // Check the value is repeating; also ensures element size is a power of two. + if (ulong.RotateRight(value, elementSize) != value) + { + immN = 0; + immS = 0; + immR = 0; + + return false; + } + + immN = (elementSize >> 6) & 1; + immS = (((~elementSize + 1) << 1) | (onesInElement - 1)) & 0x3f; + immR = (elementSize - rotation) & (elementSize - 1); + + return true; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs new file mode 100644 index 000000000..12ebabddd --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs @@ -0,0 +1,287 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; +using System; +using System.Collections.Generic; +using System.IO; + +namespace ARMeilleure.CodeGen.Arm64 +{ + class CodeGenContext + { + private const int BccInstLength = 4; + private const int CbnzInstLength = 4; + private const int LdrLitInstLength = 4; + + private readonly Stream _stream; + + public int StreamOffset => (int)_stream.Length; + + public AllocationResult AllocResult { get; } + + public Assembler Assembler { get; } + + public BasicBlock CurrBlock { get; private set; } + + public bool HasCall { get; } + + public int CallArgsRegionSize { get; } + public int FpLrSaveRegionSize { get; } + + private readonly Dictionary _visitedBlocks; + private readonly Dictionary> _pendingBranches; + + private readonly struct ConstantPoolEntry + { + public readonly int Offset; + public readonly Symbol Symbol; + public readonly List<(Operand, int)> LdrOffsets; + + public ConstantPoolEntry(int offset, Symbol symbol) + { + Offset = offset; + Symbol = symbol; + LdrOffsets = new List<(Operand, int)>(); + } + } + + private readonly Dictionary _constantPool; + + private bool _constantPoolWritten; + private long _constantPoolOffset; + + private ArmCondition _jNearCondition; + private Operand _jNearValue; + + private long _jNearPosition; + + private readonly bool _relocatable; + + public CodeGenContext(AllocationResult allocResult, int maxCallArgs, bool relocatable) + { + _stream = MemoryStreamManager.Shared.GetStream(); + + AllocResult = allocResult; + + Assembler = new Assembler(_stream); + + bool hasCall = maxCallArgs >= 0; + + HasCall = hasCall; + + if (maxCallArgs < 0) + { + maxCallArgs = 0; + } + + CallArgsRegionSize = maxCallArgs * 16; + FpLrSaveRegionSize = hasCall ? 16 : 0; + + _visitedBlocks = new Dictionary(); + _pendingBranches = new Dictionary>(); + _constantPool = new Dictionary(); + + _relocatable = relocatable; + } + + public void EnterBlock(BasicBlock block) + { + CurrBlock = block; + + long target = _stream.Position; + + if (_pendingBranches.TryGetValue(block, out var list)) + { + foreach ((ArmCondition condition, long branchPos) in list) + { + _stream.Seek(branchPos, SeekOrigin.Begin); + WriteBranch(condition, target); + } + + _stream.Seek(target, SeekOrigin.Begin); + _pendingBranches.Remove(block); + } + + _visitedBlocks.Add(block, target); + } + + public void JumpTo(BasicBlock target) + { + JumpTo(ArmCondition.Al, target); + } + + public void JumpTo(ArmCondition condition, BasicBlock target) + { + if (_visitedBlocks.TryGetValue(target, out long offset)) + { + WriteBranch(condition, offset); + } + else + { + if (!_pendingBranches.TryGetValue(target, out var list)) + { + list = new List<(ArmCondition, long)>(); + _pendingBranches.Add(target, list); + } + + list.Add((condition, _stream.Position)); + + _stream.Seek(BccInstLength, SeekOrigin.Current); + } + } + + private void WriteBranch(ArmCondition condition, long to) + { + int imm = checked((int)(to - _stream.Position)); + + if (condition != ArmCondition.Al) + { + Assembler.B(condition, imm); + } + else + { + Assembler.B(imm); + } + } + + public void JumpToNear(ArmCondition condition) + { + _jNearCondition = condition; + _jNearPosition = _stream.Position; + + _stream.Seek(BccInstLength, SeekOrigin.Current); + } + + public void JumpToNearIfNotZero(Operand value) + { + _jNearValue = value; + _jNearPosition = _stream.Position; + + _stream.Seek(CbnzInstLength, SeekOrigin.Current); + } + + public void JumpHere() + { + long currentPosition = _stream.Position; + long offset = currentPosition - _jNearPosition; + + _stream.Seek(_jNearPosition, SeekOrigin.Begin); + + if (_jNearValue != default) + { + Assembler.Cbnz(_jNearValue, checked((int)offset)); + _jNearValue = default; + } + else + { + Assembler.B(_jNearCondition, checked((int)offset)); + } + + _stream.Seek(currentPosition, SeekOrigin.Begin); + } + + public void ReserveRelocatableConstant(Operand rt, Symbol symbol, ulong value) + { + if (!_constantPool.TryGetValue(value, out ConstantPoolEntry cpe)) + { + cpe = new ConstantPoolEntry(_constantPool.Count * sizeof(ulong), symbol); + _constantPool.Add(value, cpe); + } + + cpe.LdrOffsets.Add((rt, (int)_stream.Position)); + _stream.Seek(LdrLitInstLength, SeekOrigin.Current); + } + + private long WriteConstantPool() + { + if (_constantPoolWritten) + { + return _constantPoolOffset; + } + + long constantPoolBaseOffset = _stream.Position; + + foreach (ulong value in _constantPool.Keys) + { + WriteUInt64(value); + } + + foreach (ConstantPoolEntry cpe in _constantPool.Values) + { + foreach ((Operand rt, int ldrOffset) in cpe.LdrOffsets) + { + _stream.Seek(ldrOffset, SeekOrigin.Begin); + + int absoluteOffset = checked((int)(constantPoolBaseOffset + cpe.Offset)); + int pcRelativeOffset = absoluteOffset - ldrOffset; + + Assembler.LdrLit(rt, pcRelativeOffset); + } + } + + _stream.Seek(constantPoolBaseOffset + _constantPool.Count * sizeof(ulong), SeekOrigin.Begin); + + _constantPoolOffset = constantPoolBaseOffset; + _constantPoolWritten = true; + + return constantPoolBaseOffset; + } + + public (byte[], RelocInfo) GetCode() + { + long constantPoolBaseOffset = WriteConstantPool(); + + byte[] code = new byte[_stream.Length]; + + long originalPosition = _stream.Position; + + _stream.Seek(0, SeekOrigin.Begin); + _stream.Read(code, 0, code.Length); + _stream.Seek(originalPosition, SeekOrigin.Begin); + + RelocInfo relocInfo; + + if (_relocatable) + { + RelocEntry[] relocs = new RelocEntry[_constantPool.Count]; + + int index = 0; + + foreach (ConstantPoolEntry cpe in _constantPool.Values) + { + if (cpe.Symbol.Type != SymbolType.None) + { + int absoluteOffset = checked((int)(constantPoolBaseOffset + cpe.Offset)); + relocs[index++] = new RelocEntry(absoluteOffset, cpe.Symbol); + } + } + + if (index != relocs.Length) + { + Array.Resize(ref relocs, index); + } + + relocInfo = new RelocInfo(relocs); + } + else + { + relocInfo = new RelocInfo(Array.Empty()); + } + + return (code, relocInfo); + } + + private void WriteUInt64(ulong value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + _stream.WriteByte((byte)(value >> 32)); + _stream.WriteByte((byte)(value >> 40)); + _stream.WriteByte((byte)(value >> 48)); + _stream.WriteByte((byte)(value >> 56)); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs new file mode 100644 index 000000000..2df86671a --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs @@ -0,0 +1,1581 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Common; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; +using static ARMeilleure.IntermediateRepresentation.Operand; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CodeGenerator + { + private const int DWordScale = 3; + + private const int RegistersCount = 32; + + private const int FpRegister = 29; + private const int LrRegister = 30; + private const int SpRegister = 31; + private const int ZrRegister = 31; + + private enum AccessSize + { + Byte, + Hword, + Auto, + } + + private static readonly Action[] _instTable; + + static CodeGenerator() + { + _instTable = new Action[EnumUtils.GetCount(typeof(Instruction))]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Instruction.Add, GenerateAdd); + Add(Instruction.BitwiseAnd, GenerateBitwiseAnd); + Add(Instruction.BitwiseExclusiveOr, GenerateBitwiseExclusiveOr); + Add(Instruction.BitwiseNot, GenerateBitwiseNot); + Add(Instruction.BitwiseOr, GenerateBitwiseOr); + Add(Instruction.BranchIf, GenerateBranchIf); + Add(Instruction.ByteSwap, GenerateByteSwap); + Add(Instruction.Call, GenerateCall); + // Add(Instruction.Clobber, GenerateClobber); + Add(Instruction.Compare, GenerateCompare); + Add(Instruction.CompareAndSwap, GenerateCompareAndSwap); + Add(Instruction.CompareAndSwap16, GenerateCompareAndSwap16); + Add(Instruction.CompareAndSwap8, GenerateCompareAndSwap8); + Add(Instruction.ConditionalSelect, GenerateConditionalSelect); + Add(Instruction.ConvertI64ToI32, GenerateConvertI64ToI32); + Add(Instruction.ConvertToFP, GenerateConvertToFP); + Add(Instruction.ConvertToFPUI, GenerateConvertToFPUI); + Add(Instruction.Copy, GenerateCopy); + Add(Instruction.CountLeadingZeros, GenerateCountLeadingZeros); + Add(Instruction.Divide, GenerateDivide); + Add(Instruction.DivideUI, GenerateDivideUI); + Add(Instruction.Fill, GenerateFill); + Add(Instruction.Load, GenerateLoad); + Add(Instruction.Load16, GenerateLoad16); + Add(Instruction.Load8, GenerateLoad8); + Add(Instruction.MemoryBarrier, GenerateMemoryBarrier); + Add(Instruction.Multiply, GenerateMultiply); + Add(Instruction.Multiply64HighSI, GenerateMultiply64HighSI); + Add(Instruction.Multiply64HighUI, GenerateMultiply64HighUI); + Add(Instruction.Negate, GenerateNegate); + Add(Instruction.Return, GenerateReturn); + Add(Instruction.RotateRight, GenerateRotateRight); + Add(Instruction.ShiftLeft, GenerateShiftLeft); + Add(Instruction.ShiftRightSI, GenerateShiftRightSI); + Add(Instruction.ShiftRightUI, GenerateShiftRightUI); + Add(Instruction.SignExtend16, GenerateSignExtend16); + Add(Instruction.SignExtend32, GenerateSignExtend32); + Add(Instruction.SignExtend8, GenerateSignExtend8); + Add(Instruction.Spill, GenerateSpill); + Add(Instruction.SpillArg, GenerateSpillArg); + Add(Instruction.StackAlloc, GenerateStackAlloc); + Add(Instruction.Store, GenerateStore); + Add(Instruction.Store16, GenerateStore16); + Add(Instruction.Store8, GenerateStore8); + Add(Instruction.Subtract, GenerateSubtract); + Add(Instruction.Tailcall, GenerateTailcall); + Add(Instruction.VectorCreateScalar, GenerateVectorCreateScalar); + Add(Instruction.VectorExtract, GenerateVectorExtract); + Add(Instruction.VectorExtract16, GenerateVectorExtract16); + Add(Instruction.VectorExtract8, GenerateVectorExtract8); + Add(Instruction.VectorInsert, GenerateVectorInsert); + Add(Instruction.VectorInsert16, GenerateVectorInsert16); + Add(Instruction.VectorInsert8, GenerateVectorInsert8); + Add(Instruction.VectorOne, GenerateVectorOne); + Add(Instruction.VectorZero, GenerateVectorZero); + Add(Instruction.VectorZeroUpper64, GenerateVectorZeroUpper64); + Add(Instruction.VectorZeroUpper96, GenerateVectorZeroUpper96); + Add(Instruction.ZeroExtend16, GenerateZeroExtend16); + Add(Instruction.ZeroExtend32, GenerateZeroExtend32); + Add(Instruction.ZeroExtend8, GenerateZeroExtend8); +#pragma warning restore IDE0055 + + static void Add(Instruction inst, Action func) + { + _instTable[(int)inst] = func; + } + } + + public static CompiledFunction Generate(CompilerContext cctx) + { + ControlFlowGraph cfg = cctx.Cfg; + + Logger.StartPass(PassName.Optimization); + + if (cctx.Options.HasFlag(CompilerOptions.Optimize)) + { + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Optimizer.RunPass(cfg); + } + + BlockPlacement.RunPass(cfg); + } + + Arm64Optimizer.RunPass(cfg); + + Logger.EndPass(PassName.Optimization, cfg); + + Logger.StartPass(PassName.PreAllocation); + + StackAllocator stackAlloc = new(); + + PreAllocator.RunPass(cctx, out int maxCallArgs); + + Logger.EndPass(PassName.PreAllocation, cfg); + + Logger.StartPass(PassName.RegisterAllocation); + + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Ssa.Deconstruct(cfg); + } + + IRegisterAllocator regAlloc; + + if (cctx.Options.HasFlag(CompilerOptions.Lsra)) + { + regAlloc = new LinearScanAllocator(); + } + else + { + regAlloc = new HybridAllocator(); + } + + RegisterMasks regMasks = new( + CallingConvention.GetIntAvailableRegisters(), + CallingConvention.GetVecAvailableRegisters(), + CallingConvention.GetIntCallerSavedRegisters(), + CallingConvention.GetVecCallerSavedRegisters(), + CallingConvention.GetIntCalleeSavedRegisters(), + CallingConvention.GetVecCalleeSavedRegisters(), + RegistersCount); + + AllocationResult allocResult = regAlloc.RunPass(cfg, stackAlloc, regMasks); + + Logger.EndPass(PassName.RegisterAllocation, cfg); + + Logger.StartPass(PassName.CodeGeneration); + + bool relocatable = (cctx.Options & CompilerOptions.Relocatable) != 0; + + CodeGenContext context = new(allocResult, maxCallArgs, relocatable); + + UnwindInfo unwindInfo = WritePrologue(context); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + context.EnterBlock(block); + + for (Operation node = block.Operations.First; node != default;) + { + node = GenerateOperation(context, node); + } + + if (block.SuccessorsCount == 0) + { + // The only blocks which can have 0 successors are exit blocks. + Operation last = block.Operations.Last; + + Debug.Assert(last.Instruction == Instruction.Tailcall || + last.Instruction == Instruction.Return); + } + else + { + BasicBlock succ = block.GetSuccessor(0); + + if (succ != block.ListNext) + { + context.JumpTo(succ); + } + } + } + + (byte[] code, RelocInfo relocInfo) = context.GetCode(); + + Logger.EndPass(PassName.CodeGeneration); + + return new CompiledFunction(code, unwindInfo, relocInfo); + } + + private static Operation GenerateOperation(CodeGenContext context, Operation operation) + { + if (operation.Instruction == Instruction.Extended) + { + CodeGeneratorIntrinsic.GenerateOperation(context, operation); + } + else + { + if (IsLoadOrStore(operation) && + operation.ListNext != default && + operation.ListNext.Instruction == operation.Instruction && + TryPairMemoryOp(context, operation, operation.ListNext)) + { + // Skip next operation if we managed to pair them. + return operation.ListNext.ListNext; + } + + Action func = _instTable[(int)operation.Instruction]; + + if (func != null) + { + func(context, operation); + } + else + { + throw new ArgumentException($"Invalid instruction \"{operation.Instruction}\"."); + } + } + + return operation.ListNext; + } + + private static void GenerateAdd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + // ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Add(dest, src1, src2); + } + else + { + context.Assembler.FaddScalar(dest, src1, src2); + } + } + + private static void GenerateBitwiseAnd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.And(dest, src1, src2); + } + + private static void GenerateBitwiseExclusiveOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Eor(dest, src1, src2); + } + else + { + context.Assembler.EorVector(dest, src1, src2); + } + } + + private static void GenerateBitwiseNot(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Mvn(dest, source); + } + + private static void GenerateBitwiseOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Orr(dest, src1, src2); + } + + private static void GenerateBranchIf(CodeGenContext context, Operation operation) + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToArmCondition(); + + GenerateCompareCommon(context, operation); + + context.JumpTo(cond, context.CurrBlock.GetSuccessor(1)); + } + + private static void GenerateByteSwap(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Rev(dest, source); + } + + private static void GenerateCall(CodeGenContext context, Operation operation) + { + context.Assembler.Blr(operation.GetSource(0)); + } + + private static void GenerateCompare(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand comp = operation.GetSource(2); + + Debug.Assert(dest.Type == OperandType.I32); + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToArmCondition(); + + GenerateCompareCommon(context, operation); + + context.Assembler.Cset(dest, cond); + } + + private static void GenerateCompareAndSwap(CodeGenContext context, Operation operation) + { + if (operation.SourcesCount == 5) // CompareAndSwap128 has 5 sources, compared to CompareAndSwap64/32's 3. + { + Operand actualLow = operation.GetDestination(0); + Operand actualHigh = operation.GetDestination(1); + Operand temp0 = operation.GetDestination(2); + Operand temp1 = operation.GetDestination(3); + Operand address = operation.GetSource(0); + Operand expectedLow = operation.GetSource(1); + Operand expectedHigh = operation.GetSource(2); + Operand desiredLow = operation.GetSource(3); + Operand desiredHigh = operation.GetSource(4); + + GenerateAtomicDcas( + context, + address, + expectedLow, + expectedHigh, + desiredLow, + desiredHigh, + actualLow, + actualHigh, + temp0, + temp1); + } + else + { + Operand actual = operation.GetDestination(0); + Operand result = operation.GetDestination(1); + Operand address = operation.GetSource(0); + Operand expected = operation.GetSource(1); + Operand desired = operation.GetSource(2); + + GenerateAtomicCas(context, address, expected, desired, actual, result, AccessSize.Auto); + } + } + + private static void GenerateCompareAndSwap16(CodeGenContext context, Operation operation) + { + Operand actual = operation.GetDestination(0); + Operand result = operation.GetDestination(1); + Operand address = operation.GetSource(0); + Operand expected = operation.GetSource(1); + Operand desired = operation.GetSource(2); + + GenerateAtomicCas(context, address, expected, desired, actual, result, AccessSize.Hword); + } + + private static void GenerateCompareAndSwap8(CodeGenContext context, Operation operation) + { + Operand actual = operation.GetDestination(0); + Operand result = operation.GetDestination(1); + Operand address = operation.GetSource(0); + Operand expected = operation.GetSource(1); + Operand desired = operation.GetSource(2); + + GenerateAtomicCas(context, address, expected, desired, actual, result, AccessSize.Byte); + } + + private static void GenerateCompareCommon(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(src1, src2); + + Debug.Assert(src1.Type.IsInteger()); + + context.Assembler.Cmp(src1, src2); + } + + private static void GenerateConditionalSelect(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(dest, src2, src3); + + Debug.Assert(dest.Type.IsInteger()); + Debug.Assert(src1.Type == OperandType.I32); + + context.Assembler.Cmp(src1, Const(src1.Type, 0)); + context.Assembler.Csel(dest, src2, src3, ArmCondition.Ne); + } + + private static void GenerateConvertI64ToI32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.I32 && source.Type == OperandType.I64); + + context.Assembler.Mov(dest, Register(source, OperandType.I32)); + } + + private static void GenerateConvertToFP(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); + Debug.Assert(dest.Type != source.Type); + Debug.Assert(source.Type != OperandType.V128); + + if (source.Type.IsInteger()) + { + context.Assembler.ScvtfScalar(dest, source); + } + else + { + context.Assembler.FcvtScalar(dest, source); + } + } + + private static void GenerateConvertToFPUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); + Debug.Assert(dest.Type != source.Type); + Debug.Assert(source.Type.IsInteger()); + + context.Assembler.UcvtfScalar(dest, source); + } + + private static void GenerateCopy(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger() || source.Kind != OperandKind.Constant); + + // Moves to the same register are useless. + if (dest.Kind == source.Kind && dest.Value == source.Value) + { + return; + } + + if (dest.Kind == OperandKind.Register && source.Kind == OperandKind.Constant) + { + if (source.Relocatable) + { + context.ReserveRelocatableConstant(dest, source.Symbol, source.Value); + } + else + { + GenerateConstantCopy(context, dest, source.Value); + } + } + else + { + context.Assembler.Mov(dest, source); + } + } + + private static void GenerateCountLeadingZeros(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Clz(dest, source); + } + + private static void GenerateDivide(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand dividend = operation.GetSource(0); + Operand divisor = operation.GetSource(1); + + ValidateBinOp(dest, dividend, divisor); + + if (dest.Type.IsInteger()) + { + context.Assembler.Sdiv(dest, dividend, divisor); + } + else + { + context.Assembler.FdivScalar(dest, dividend, divisor); + } + } + + private static void GenerateDivideUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand dividend = operation.GetSource(0); + Operand divisor = operation.GetSource(1); + + ValidateBinOp(dest, dividend, divisor); + + context.Assembler.Udiv(dest, dividend, divisor); + } + + private static void GenerateLoad(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = operation.GetSource(0); + + context.Assembler.Ldr(value, address); + } + + private static void GenerateLoad16(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.LdrhRiUn(value, address, 0); + } + + private static void GenerateLoad8(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.LdrbRiUn(value, address, 0); + } + + private static void GenerateMemoryBarrier(CodeGenContext context, Operation operation) + { + context.Assembler.Dmb(0xf); + } + + private static void GenerateMultiply(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Mul(dest, src1, src2); + } + else + { + context.Assembler.FmulScalar(dest, src1, src2); + } + } + + private static void GenerateMultiply64HighSI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1, src2); + + Debug.Assert(dest.Type == OperandType.I64); + + context.Assembler.Smulh(dest, src1, src2); + } + + private static void GenerateMultiply64HighUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1, src2); + + Debug.Assert(dest.Type == OperandType.I64); + + context.Assembler.Umulh(dest, src1, src2); + } + + private static void GenerateNegate(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + if (dest.Type.IsInteger()) + { + context.Assembler.Neg(dest, source); + } + else + { + context.Assembler.FnegScalar(dest, source); + } + } + + private static void GenerateLoad(CodeGenContext context, Operand value, Operand address, int offset) + { + if (CodeGenCommon.ConstFitsOnUImm12(offset, value.Type)) + { + context.Assembler.LdrRiUn(value, address, offset); + } + else if (CodeGenCommon.ConstFitsOnSImm9(offset)) + { + context.Assembler.Ldur(value, address, offset); + } + else + { + Operand tempAddress = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempAddress, (ulong)offset); + context.Assembler.Add(tempAddress, address, tempAddress, ArmExtensionType.Uxtx); // Address might be SP and must be the first input. + context.Assembler.LdrRiUn(value, tempAddress, 0); + } + } + + private static void GenerateReturn(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Ret(Register(LrRegister)); + } + + private static void GenerateRotateRight(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Ror(dest, src1, src2); + } + + private static void GenerateShiftLeft(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Lsl(dest, src1, src2); + } + + private static void GenerateShiftRightSI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Asr(dest, src1, src2); + } + + private static void GenerateShiftRightUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Lsr(dest, src1, src2); + } + + private static void GenerateSignExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Sxth(dest, source); + } + + private static void GenerateSignExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Sxtw(dest, source); + } + + private static void GenerateSignExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Sxtb(dest, source); + } + + private static void GenerateFill(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize + context.FpLrSaveRegionSize; + + GenerateLoad(context, dest, Register(SpRegister), offs); + } + + private static void GenerateStore(CodeGenContext context, Operand value, Operand address, int offset) + { + if (CodeGenCommon.ConstFitsOnUImm12(offset, value.Type)) + { + context.Assembler.StrRiUn(value, address, offset); + } + else if (CodeGenCommon.ConstFitsOnSImm9(offset)) + { + context.Assembler.Stur(value, address, offset); + } + else + { + Operand tempAddress = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempAddress, (ulong)offset); + context.Assembler.Add(tempAddress, address, tempAddress, ArmExtensionType.Uxtx); // Address might be SP and must be the first input. + context.Assembler.StrRiUn(value, tempAddress, 0); + } + } + + private static void GenerateSpill(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, context.CallArgsRegionSize + context.FpLrSaveRegionSize); + } + + private static void GenerateSpillArg(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, 0); + } + + private static void GenerateStackAlloc(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize + context.FpLrSaveRegionSize; + + context.Assembler.Add(dest, Register(SpRegister), Const(dest.Type, offs)); + } + + private static void GenerateStore(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = operation.GetSource(0); + + context.Assembler.Str(value, address); + } + + private static void GenerateStore16(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.StrhRiUn(value, address, 0); + } + + private static void GenerateStore8(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.StrbRiUn(value, address, 0); + } + + private static void GenerateSpill(CodeGenContext context, Operation operation, int baseOffset) + { + Operand offset = operation.GetSource(0); + Operand source = operation.GetSource(1); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + baseOffset; + + GenerateStore(context, source, Register(SpRegister), offs); + } + + private static void GenerateSubtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + // ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Sub(dest, src1, src2); + } + else + { + context.Assembler.FsubScalar(dest, src1, src2); + } + } + + private static void GenerateTailcall(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Br(operation.GetSource(0)); + } + + private static void GenerateVectorCreateScalar(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + if (dest != default) + { + Debug.Assert(!dest.Type.IsInteger() && source.Type.IsInteger()); + + OperandType destType = source.Type == OperandType.I64 ? OperandType.FP64 : OperandType.FP32; + + context.Assembler.Fmov(Register(dest, destType), source, topHalf: false); + } + } + + private static void GenerateVectorExtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; // Value + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < OperandType.V128.GetSizeInBytes() / dest.Type.GetSizeInBytes()); + + if (dest.Type.IsInteger()) + { + context.Assembler.Umov(dest, src1, index, dest.Type == OperandType.I64 ? 3 : 2); + } + else + { + context.Assembler.DupScalar(dest, src1, index, dest.Type == OperandType.FP64 ? 3 : 2); + } + } + + private static void GenerateVectorExtract16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; // Value + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 8); + + context.Assembler.Umov(dest, src1, index, 1); + } + + private static void GenerateVectorExtract8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; // Value + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 16); + + context.Assembler.Umov(dest, src1, index, 0); + } + + private static void GenerateVectorInsert(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + EnsureSameReg(dest, src1); + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + if (src2.Type.IsInteger()) + { + context.Assembler.Ins(dest, src2, index, src2.Type == OperandType.I64 ? 3 : 2); + } + else + { + context.Assembler.Ins(dest, src2, 0, index, src2.Type == OperandType.FP64 ? 3 : 2); + } + } + + private static void GenerateVectorInsert16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + EnsureSameReg(dest, src1); + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Ins(dest, src2, index, 1); + } + + private static void GenerateVectorInsert8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + EnsureSameReg(dest, src1); + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Ins(dest, src2, index, 0); + } + + private static void GenerateVectorOne(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.CmeqVector(dest, dest, dest, 2); + } + + private static void GenerateVectorZero(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.EorVector(dest, dest, dest); + } + + private static void GenerateVectorZeroUpper64(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + context.Assembler.Fmov(Register(dest, OperandType.FP64), Register(source, OperandType.FP64)); + } + + private static void GenerateVectorZeroUpper96(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + context.Assembler.Fmov(Register(dest, OperandType.FP32), Register(source, OperandType.FP32)); + } + + private static void GenerateZeroExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Uxth(dest, source); + } + + private static void GenerateZeroExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + // We can eliminate the move if source is already 32-bit and the registers are the same. + if (dest.Value == source.Value && source.Type == OperandType.I32) + { + return; + } + + context.Assembler.Mov(Register(dest.GetRegister().Index, OperandType.I32), source); + } + + private static void GenerateZeroExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Uxtb(dest, source); + } + + private static UnwindInfo WritePrologue(CodeGenContext context) + { + List pushEntries = new(); + + Operand rsp = Register(SpRegister); + + int intMask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + int vecMask = CallingConvention.GetFpCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + int intCalleeSavedRegsCount = BitOperations.PopCount((uint)intMask); + int vecCalleeSavedRegsCount = BitOperations.PopCount((uint)vecMask); + + int calleeSaveRegionSize = Align16(intCalleeSavedRegsCount * 8 + vecCalleeSavedRegsCount * 8); + + int offset = 0; + + WritePrologueCalleeSavesPreIndexed(context, pushEntries, ref intMask, ref offset, calleeSaveRegionSize, OperandType.I64); + WritePrologueCalleeSavesPreIndexed(context, pushEntries, ref vecMask, ref offset, calleeSaveRegionSize, OperandType.FP64); + + int localSize = Align16(context.AllocResult.SpillRegionSize + context.FpLrSaveRegionSize); + int outArgsSize = context.CallArgsRegionSize; + + if (CodeGenCommon.ConstFitsOnSImm7(localSize, DWordScale)) + { + if (context.HasCall) + { + context.Assembler.StpRiPre(Register(FpRegister), Register(LrRegister), rsp, -localSize); + context.Assembler.MovSp(Register(FpRegister), rsp); + } + + if (outArgsSize != 0) + { + context.Assembler.Sub(rsp, rsp, Const(OperandType.I64, outArgsSize)); + } + } + else + { + int frameSize = localSize + outArgsSize; + if (frameSize != 0) + { + if (CodeGenCommon.ConstFitsOnUImm12(frameSize)) + { + context.Assembler.Sub(rsp, rsp, Const(OperandType.I64, frameSize)); + } + else + { + Operand tempSize = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempSize, (ulong)frameSize); + context.Assembler.Sub(rsp, rsp, tempSize, ArmExtensionType.Uxtx); + } + } + + context.Assembler.StpRiUn(Register(FpRegister), Register(LrRegister), rsp, outArgsSize); + + if (outArgsSize != 0) + { + context.Assembler.Add(Register(FpRegister), Register(SpRegister), Const(OperandType.I64, outArgsSize)); + } + else + { + context.Assembler.MovSp(Register(FpRegister), Register(SpRegister)); + } + } + + return new UnwindInfo(pushEntries.ToArray(), context.StreamOffset); + } + + private static void WritePrologueCalleeSavesPreIndexed( + CodeGenContext context, + List pushEntries, + ref int mask, + ref int offset, + int calleeSaveRegionSize, + OperandType type) + { + if ((BitOperations.PopCount((uint)mask) & 1) != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: reg)); + + mask &= ~(1 << reg); + + if (offset != 0) + { + context.Assembler.StrRiUn(Register(reg, type), Register(SpRegister), offset); + } + else + { + context.Assembler.StrRiPre(Register(reg, type), Register(SpRegister), -calleeSaveRegionSize); + } + + offset += type.GetSizeInBytes(); + } + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: reg)); + + mask &= ~(1 << reg); + + int reg2 = BitOperations.TrailingZeroCount(mask); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: reg2)); + + mask &= ~(1 << reg2); + + if (offset != 0) + { + context.Assembler.StpRiUn(Register(reg, type), Register(reg2, type), Register(SpRegister), offset); + } + else + { + context.Assembler.StpRiPre(Register(reg, type), Register(reg2, type), Register(SpRegister), -calleeSaveRegionSize); + } + + offset += type.GetSizeInBytes() * 2; + } + } + + private static void WriteEpilogue(CodeGenContext context) + { + Operand rsp = Register(SpRegister); + + int localSize = Align16(context.AllocResult.SpillRegionSize + context.FpLrSaveRegionSize); + int outArgsSize = context.CallArgsRegionSize; + + if (CodeGenCommon.ConstFitsOnSImm7(localSize, DWordScale)) + { + if (outArgsSize != 0) + { + context.Assembler.Add(rsp, rsp, Const(OperandType.I64, outArgsSize)); + } + + if (context.HasCall) + { + context.Assembler.LdpRiPost(Register(FpRegister), Register(LrRegister), rsp, localSize); + } + } + else + { + if (context.HasCall) + { + context.Assembler.LdpRiUn(Register(FpRegister), Register(LrRegister), rsp, outArgsSize); + } + + int frameSize = localSize + outArgsSize; + if (frameSize != 0) + { + if (CodeGenCommon.ConstFitsOnUImm12(frameSize)) + { + context.Assembler.Add(rsp, rsp, Const(OperandType.I64, frameSize)); + } + else + { + Operand tempSize = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempSize, (ulong)frameSize); + context.Assembler.Add(rsp, rsp, tempSize, ArmExtensionType.Uxtx); + } + } + } + + int intMask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + int vecMask = CallingConvention.GetFpCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + int intCalleeSavedRegsCount = BitOperations.PopCount((uint)intMask); + int vecCalleeSavedRegsCount = BitOperations.PopCount((uint)vecMask); + + int offset = intCalleeSavedRegsCount * 8 + vecCalleeSavedRegsCount * 8; + int calleeSaveRegionSize = Align16(offset); + + WriteEpilogueCalleeSavesPostIndexed(context, ref vecMask, ref offset, calleeSaveRegionSize, OperandType.FP64); + WriteEpilogueCalleeSavesPostIndexed(context, ref intMask, ref offset, calleeSaveRegionSize, OperandType.I64); + } + + private static void WriteEpilogueCalleeSavesPostIndexed( + CodeGenContext context, + ref int mask, + ref int offset, + int calleeSaveRegionSize, + OperandType type) + { + while (mask != 0) + { + int reg = BitUtils.HighestBitSet(mask); + + mask &= ~(1 << reg); + + if (mask != 0) + { + int reg2 = BitUtils.HighestBitSet(mask); + + mask &= ~(1 << reg2); + + offset -= type.GetSizeInBytes() * 2; + + if (offset != 0) + { + context.Assembler.LdpRiUn(Register(reg2, type), Register(reg, type), Register(SpRegister), offset); + } + else + { + context.Assembler.LdpRiPost(Register(reg2, type), Register(reg, type), Register(SpRegister), calleeSaveRegionSize); + } + } + else + { + offset -= type.GetSizeInBytes(); + + if (offset != 0) + { + context.Assembler.LdrRiUn(Register(reg, type), Register(SpRegister), offset); + } + else + { + context.Assembler.LdrRiPost(Register(reg, type), Register(SpRegister), calleeSaveRegionSize); + } + } + } + } + + private static void GenerateConstantCopy(CodeGenContext context, Operand dest, ulong value) + { + if (value == 0) + { + context.Assembler.Mov(dest, Register(ZrRegister, dest.Type)); + } + else if (CodeGenCommon.TryEncodeBitMask(dest.Type, value, out _, out _, out _)) + { + context.Assembler.Orr(dest, Register(ZrRegister, dest.Type), Const(dest.Type, (long)value)); + } + else + { + int hw = 0; + bool first = true; + + while (value != 0) + { + int valueLow = (ushort)value; + if (valueLow != 0) + { + if (first) + { + context.Assembler.Movz(dest, valueLow, hw); + first = false; + } + else + { + context.Assembler.Movk(dest, valueLow, hw); + } + } + + hw++; + value >>= 16; + } + } + } + + private static void GenerateAtomicCas( + CodeGenContext context, + Operand address, + Operand expected, + Operand desired, + Operand actual, + Operand result, + AccessSize accessSize) + { + int startOffset = context.StreamOffset; + + switch (accessSize) + { + case AccessSize.Byte: + context.Assembler.Ldaxrb(actual, address); + break; + case AccessSize.Hword: + context.Assembler.Ldaxrh(actual, address); + break; + default: + context.Assembler.Ldaxr(actual, address); + break; + } + + context.Assembler.Cmp(actual, expected); + + context.JumpToNear(ArmCondition.Ne); + + switch (accessSize) + { + case AccessSize.Byte: + context.Assembler.Stlxrb(desired, address, result); + break; + case AccessSize.Hword: + context.Assembler.Stlxrh(desired, address, result); + break; + default: + context.Assembler.Stlxr(desired, address, result); + break; + } + + context.Assembler.Cbnz(result, startOffset - context.StreamOffset); // Retry if store failed. + + context.JumpHere(); + + context.Assembler.Clrex(); + } + + private static void GenerateAtomicDcas( + CodeGenContext context, + Operand address, + Operand expectedLow, + Operand expectedHigh, + Operand desiredLow, + Operand desiredHigh, + Operand actualLow, + Operand actualHigh, + Operand temp0, + Operand temp1) + { + int startOffset = context.StreamOffset; + + context.Assembler.Ldaxp(actualLow, actualHigh, address); + context.Assembler.Eor(temp0, actualHigh, expectedHigh); + context.Assembler.Eor(temp1, actualLow, expectedLow); + context.Assembler.Orr(temp0, temp1, temp0); + + context.JumpToNearIfNotZero(temp0); + + Operand result = Register(temp0, OperandType.I32); + + context.Assembler.Stlxp(desiredLow, desiredHigh, address, result); + context.Assembler.Cbnz(result, startOffset - context.StreamOffset); // Retry if store failed. + + context.JumpHere(); + + context.Assembler.Clrex(); + } + + private static bool TryPairMemoryOp(CodeGenContext context, Operation currentOp, Operation nextOp) + { + if (!TryGetMemOpBaseAndOffset(currentOp, out Operand op1Base, out int op1Offset)) + { + return false; + } + + if (!TryGetMemOpBaseAndOffset(nextOp, out Operand op2Base, out int op2Offset)) + { + return false; + } + + if (op1Base != op2Base) + { + return false; + } + + OperandType valueType = GetMemOpValueType(currentOp); + + if (valueType != GetMemOpValueType(nextOp) || op1Offset + valueType.GetSizeInBytes() != op2Offset) + { + return false; + } + + if (!CodeGenCommon.ConstFitsOnSImm7(op1Offset, valueType.GetSizeInBytesLog2())) + { + return false; + } + + if (currentOp.Instruction == Instruction.Load) + { + context.Assembler.LdpRiUn(currentOp.Destination, nextOp.Destination, op1Base, op1Offset); + } + else if (currentOp.Instruction == Instruction.Store) + { + context.Assembler.StpRiUn(currentOp.GetSource(1), nextOp.GetSource(1), op1Base, op1Offset); + } + else + { + return false; + } + + return true; + } + + private static bool IsLoadOrStore(Operation operation) + { + return operation.Instruction == Instruction.Load || operation.Instruction == Instruction.Store; + } + + private static OperandType GetMemOpValueType(Operation operation) + { + if (operation.Destination != default) + { + return operation.Destination.Type; + } + + return operation.GetSource(1).Type; + } + + private static bool TryGetMemOpBaseAndOffset(Operation operation, out Operand baseAddress, out int offset) + { + baseAddress = default; + offset = 0; + Operand address = operation.GetSource(0); + + if (address.Kind != OperandKind.Memory) + { + return false; + } + + MemoryOperand memOp = address.GetMemory(); + Operand baseOp = memOp.BaseAddress; + + if (baseOp == default) + { + baseOp = memOp.Index; + + if (baseOp == default || memOp.Scale != Multiplier.x1) + { + return false; + } + } + if (memOp.Index != default) + { + return false; + } + + baseAddress = memOp.BaseAddress; + offset = memOp.Displacement; + + return true; + } + + private static Operand Register(Operand operand, OperandType type = OperandType.I64) + { + return Register(operand.GetRegister().Index, type); + } + + private static Operand Register(int register, OperandType type = OperandType.I64) + { + return Factory.Register(register, RegisterType.Integer, type); + } + + private static int Align16(int value) + { + return (value + 0xf) & ~0xf; + } + + [Conditional("DEBUG")] + private static void ValidateUnOp(Operand dest, Operand source) + { + // Destination and source aren't forced to be equals + // EnsureSameReg (dest, source); + EnsureSameType(dest, source); + } + + [Conditional("DEBUG")] + private static void ValidateBinOp(Operand dest, Operand src1, Operand src2) + { + // Destination and source aren't forced to be equals + // EnsureSameReg (dest, src1); + EnsureSameType(dest, src1, src2); + } + + [Conditional("DEBUG")] + private static void ValidateShift(Operand dest, Operand src1, Operand src2) + { + // Destination and source aren't forced to be equals + // EnsureSameReg (dest, src1); + EnsureSameType(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src2.Type == OperandType.I32); + } + + private static void EnsureSameReg(Operand op1, Operand op2) + { + Debug.Assert(op1.Kind == OperandKind.Register || op1.Kind == OperandKind.Memory); + Debug.Assert(op1.Kind == op2.Kind); + Debug.Assert(op1.Value == op2.Value); + } + + private static void EnsureSameType(Operand op1, Operand op2) + { + Debug.Assert(op1.Type == op2.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + } + +#pragma warning disable IDE0051 // Remove unused private member + private static void EnsureSameType(Operand op1, Operand op2, Operand op3, Operand op4) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + Debug.Assert(op1.Type == op4.Type); + } +#pragma warning restore IDE0051 + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs new file mode 100644 index 000000000..b87370557 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs @@ -0,0 +1,691 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Diagnostics; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CodeGeneratorIntrinsic + { + public static void GenerateOperation(CodeGenContext context, Operation operation) + { + Intrinsic intrin = operation.Intrinsic; + + IntrinsicInfo info = IntrinsicTable.GetInfo(intrin & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); + + switch (info.Type) + { + case IntrinsicType.ScalarUnary: + GenerateVectorUnary( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.ScalarUnaryByElem: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorUnaryByElem( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(1).AsInt32(), + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.ScalarBinary: + GenerateVectorBinary( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.ScalarBinaryFPByElem: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(2).AsInt32(), + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.ScalarBinaryRd: + GenerateVectorUnary( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1)); + break; + case IntrinsicType.ScalarBinaryShl: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarBinaryShr: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarFPCompare: + GenerateScalarFPCompare( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.ScalarFPConvFixed: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + 0, + ((uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift) + 2u, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarFPConvFixedGpr: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateScalarFPConvGpr( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarFPConvGpr: + GenerateScalarFPConvGpr( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.ScalarTernary: + GenerateScalarTernary( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + operation.GetSource(2), + operation.GetSource(0)); + break; + case IntrinsicType.ScalarTernaryFPRdByElem: + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.ScalarTernaryShlRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + case IntrinsicType.ScalarTernaryShrRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + + case IntrinsicType.Vector128Unary: + GenerateVectorUnary( + context, + 1, + 0, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.Vector128Binary: + GenerateVectorBinary( + context, + 1, + 0, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.Vector128BinaryRd: + GenerateVectorUnary( + context, + 1, + 0, + info.Inst, + operation.Destination, + operation.GetSource(1)); + break; + + case IntrinsicType.VectorUnary: + GenerateVectorUnary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.VectorUnaryByElem: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorUnaryByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(1).AsInt32(), + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.VectorBinary: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryBitwise: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryByElem: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(2).AsInt32(), + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryFPByElem: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(2).AsInt32(), + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryRd: + GenerateVectorUnary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryShl: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.VectorBinaryShr: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.VectorFPConvFixed: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + ((uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift) + 2u, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.VectorInsertByElem: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorInsertByElem( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + (uint)operation.GetSource(1).AsInt32(), + operation.Destination, + operation.GetSource(2)); + break; + case IntrinsicType.VectorLookupTable: + Debug.Assert((uint)(operation.SourcesCount - 2) <= 3); + + for (int i = 1; i < operation.SourcesCount - 1; i++) + { + Register currReg = operation.GetSource(i).GetRegister(); + Register prevReg = operation.GetSource(i - 1).GetRegister(); + + Debug.Assert(prevReg.Index + 1 == currReg.Index && currReg.Type == RegisterType.Vector); + } + + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + info.Inst | ((uint)(operation.SourcesCount - 2) << 13), + operation.Destination, + operation.GetSource(0), + operation.GetSource(operation.SourcesCount - 1)); + break; + case IntrinsicType.VectorTernaryFPRdByElem: + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryRd: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryRdBitwise: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryRdByElem: + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorBinaryByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryShlRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + case IntrinsicType.VectorTernaryShrRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + + case IntrinsicType.GetRegister: + context.Assembler.WriteInstruction(info.Inst, operation.Destination); + break; + case IntrinsicType.SetRegister: + context.Assembler.WriteInstruction(info.Inst, operation.GetSource(0)); + break; + + default: + throw new NotImplementedException(info.Type.ToString()); + } + } + + private static void GenerateScalarFPCompare( + CodeGenContext context, + uint sz, + uint instruction, + Operand dest, + Operand rn, + Operand rm) + { + instruction |= (sz << 22); + + if (rm.Kind == OperandKind.Constant && rm.Value == 0) + { + instruction |= 0b1000; + rm = rn; + } + + context.Assembler.WriteInstructionRm16NoRet(instruction, rn, rm); + context.Assembler.Mrs(dest, 1, 3, 4, 2, 0); + } + + private static void GenerateScalarFPConvGpr( + CodeGenContext context, + uint sz, + uint instruction, + Operand rd, + Operand rn) + { + instruction |= (sz << 22); + + if (rd.Type.IsInteger()) + { + context.Assembler.WriteInstructionAuto(instruction, rd, rn); + } + else + { + if (rn.Type == OperandType.I64) + { + instruction |= Assembler.SfFlag; + } + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + } + + private static void GenerateScalarFPConvGpr( + CodeGenContext context, + uint sz, + uint instruction, + Operand rd, + Operand rn, + uint fBits) + { + Debug.Assert(fBits <= 64); + + instruction |= (sz << 22); + instruction |= (64 - fBits) << 10; + + if (rd.Type.IsInteger()) + { + Debug.Assert(rd.Type != OperandType.I32 || fBits <= 32); + + context.Assembler.WriteInstructionAuto(instruction, rd, rn); + } + else + { + if (rn.Type == OperandType.I64) + { + instruction |= Assembler.SfFlag; + } + else + { + Debug.Assert(fBits <= 32); + } + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + } + + private static void GenerateScalarTernary( + CodeGenContext context, + uint sz, + uint instruction, + Operand rd, + Operand rn, + Operand rm, + Operand ra) + { + instruction |= (sz << 22); + + context.Assembler.WriteInstruction(instruction, rd, rn, rm, ra); + } + + private static void GenerateVectorUnary( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn) + { + instruction |= (q << 30) | (sz << 22); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorUnaryByElem( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + uint srcIndex, + Operand rd, + Operand rn) + { + uint imm5 = (srcIndex << ((int)sz + 1)) | (1u << (int)sz); + + instruction |= (q << 30) | (imm5 << 16); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorBinary( + CodeGenContext context, + uint q, + uint instruction, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30); + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinary( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30) | (sz << 22); + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinaryByElem( + CodeGenContext context, + uint q, + uint size, + uint instruction, + uint srcIndex, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30) | (size << 22); + + if (size == 2) + { + instruction |= ((srcIndex & 1) << 21) | ((srcIndex & 2) << 10); + } + else + { + instruction |= ((srcIndex & 3) << 20) | ((srcIndex & 4) << 9); + } + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinaryFPByElem( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + uint srcIndex, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30) | (sz << 22); + + if (sz != 0) + { + instruction |= (srcIndex & 1) << 11; + } + else + { + instruction |= ((srcIndex & 1) << 21) | ((srcIndex & 2) << 10); + } + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinaryShlImm( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn, + uint shift) + { + instruction |= (q << 30); + + Debug.Assert(shift >= 0 && shift < (8u << (int)sz)); + + uint imm = (8u << (int)sz) | (shift & (0x3fu >> (int)(3 - sz))); + + instruction |= (imm << 16); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorBinaryShrImm( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn, + uint shift) + { + instruction |= (q << 30); + + Debug.Assert(shift > 0 && shift <= (8u << (int)sz)); + + uint imm = (8u << (int)sz) | ((8u << (int)sz) - shift); + + instruction |= (imm << 16); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorInsertByElem( + CodeGenContext context, + uint sz, + uint instruction, + uint srcIndex, + uint dstIndex, + Operand rd, + Operand rn) + { + uint imm4 = srcIndex << (int)sz; + uint imm5 = (dstIndex << ((int)sz + 1)) | (1u << (int)sz); + + instruction |= imm4 << 11; + instruction |= imm5 << 16; + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs b/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs new file mode 100644 index 000000000..e957f6786 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs @@ -0,0 +1,183 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Versioning; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static partial class HardwareCapabilities + { + static HardwareCapabilities() + { + if (!ArmBase.Arm64.IsSupported) + { + return; + } + + if (OperatingSystem.IsLinux()) + { + LinuxFeatureInfoHwCap = (LinuxFeatureFlagsHwCap)getauxval(AT_HWCAP); + LinuxFeatureInfoHwCap2 = (LinuxFeatureFlagsHwCap2)getauxval(AT_HWCAP2); + } + + if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + for (int i = 0; i < _sysctlNames.Length; i++) + { + if (CheckSysctlName(_sysctlNames[i])) + { + MacOsFeatureInfo |= (MacOsFeatureFlags)(1 << i); + } + } + } + } + + #region Linux + + private const ulong AT_HWCAP = 16; + private const ulong AT_HWCAP2 = 26; + + [LibraryImport("libc", SetLastError = true)] + private static partial ulong getauxval(ulong type); + + [Flags] + public enum LinuxFeatureFlagsHwCap : ulong + { + Fp = 1 << 0, + Asimd = 1 << 1, + Evtstrm = 1 << 2, + Aes = 1 << 3, + Pmull = 1 << 4, + Sha1 = 1 << 5, + Sha2 = 1 << 6, + Crc32 = 1 << 7, + Atomics = 1 << 8, + FpHp = 1 << 9, + AsimdHp = 1 << 10, + CpuId = 1 << 11, + AsimdRdm = 1 << 12, + Jscvt = 1 << 13, + Fcma = 1 << 14, + Lrcpc = 1 << 15, + DcpOp = 1 << 16, + Sha3 = 1 << 17, + Sm3 = 1 << 18, + Sm4 = 1 << 19, + AsimdDp = 1 << 20, + Sha512 = 1 << 21, + Sve = 1 << 22, + AsimdFhm = 1 << 23, + Dit = 1 << 24, + Uscat = 1 << 25, + Ilrcpc = 1 << 26, + FlagM = 1 << 27, + Ssbs = 1 << 28, + Sb = 1 << 29, + Paca = 1 << 30, + Pacg = 1UL << 31, + } + + [Flags] + public enum LinuxFeatureFlagsHwCap2 : ulong + { + Dcpodp = 1 << 0, + Sve2 = 1 << 1, + SveAes = 1 << 2, + SvePmull = 1 << 3, + SveBitperm = 1 << 4, + SveSha3 = 1 << 5, + SveSm4 = 1 << 6, + FlagM2 = 1 << 7, + Frint = 1 << 8, + SveI8mm = 1 << 9, + SveF32mm = 1 << 10, + SveF64mm = 1 << 11, + SveBf16 = 1 << 12, + I8mm = 1 << 13, + Bf16 = 1 << 14, + Dgh = 1 << 15, + Rng = 1 << 16, + Bti = 1 << 17, + Mte = 1 << 18, + Ecv = 1 << 19, + Afp = 1 << 20, + Rpres = 1 << 21, + Mte3 = 1 << 22, + Sme = 1 << 23, + Sme_i16i64 = 1 << 24, + Sme_f64f64 = 1 << 25, + Sme_i8i32 = 1 << 26, + Sme_f16f32 = 1 << 27, + Sme_b16f32 = 1 << 28, + Sme_f32f32 = 1 << 29, + Sme_fa64 = 1 << 30, + Wfxt = 1UL << 31, + Ebf16 = 1UL << 32, + Sve_Ebf16 = 1UL << 33, + Cssc = 1UL << 34, + Rprfm = 1UL << 35, + Sve2p1 = 1UL << 36, + } + + public static LinuxFeatureFlagsHwCap LinuxFeatureInfoHwCap { get; } = 0; + public static LinuxFeatureFlagsHwCap2 LinuxFeatureInfoHwCap2 { get; } = 0; + + #endregion + + #region Darwin + + [LibraryImport("libSystem.dylib", SetLastError = true)] + private static unsafe partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, out int oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize); + + [SupportedOSPlatform("macos")] + [SupportedOSPlatform("ios")] + private static bool CheckSysctlName(string name) + { + ulong size = sizeof(int); + if (sysctlbyname(name, out int val, ref size, IntPtr.Zero, 0) == 0 && size == sizeof(int)) + { + return val != 0; + } + return false; + } + + private static readonly string[] _sysctlNames = new string[] + { + "hw.optional.floatingpoint", + "hw.optional.AdvSIMD", + "hw.optional.arm.FEAT_FP16", + "hw.optional.arm.FEAT_AES", + "hw.optional.arm.FEAT_PMULL", + "hw.optional.arm.FEAT_LSE", + "hw.optional.armv8_crc32", + "hw.optional.arm.FEAT_SHA1", + "hw.optional.arm.FEAT_SHA256", + }; + + [Flags] + public enum MacOsFeatureFlags + { + Fp = 1 << 0, + AdvSimd = 1 << 1, + Fp16 = 1 << 2, + Aes = 1 << 3, + Pmull = 1 << 4, + Lse = 1 << 5, + Crc32 = 1 << 6, + Sha1 = 1 << 7, + Sha256 = 1 << 8, + } + + public static MacOsFeatureFlags MacOsFeatureInfo { get; } = 0; + + #endregion + + public static bool SupportsAdvSimd => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Asimd) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.AdvSimd); + public static bool SupportsAes => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Aes) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Aes); + public static bool SupportsPmull => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Pmull) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Pmull); + public static bool SupportsLse => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Atomics) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Lse); + public static bool SupportsCrc32 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Crc32) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Crc32); + public static bool SupportsSha1 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha1) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha1); + public static bool SupportsSha256 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha2) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha256); + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs b/src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs new file mode 100644 index 000000000..956fc778d --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.Arm64 +{ + readonly struct IntrinsicInfo + { + public uint Inst { get; } + public IntrinsicType Type { get; } + + public IntrinsicInfo(uint inst, IntrinsicType type) + { + Inst = inst; + Type = type; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs b/src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs new file mode 100644 index 000000000..dbd5bdd10 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs @@ -0,0 +1,465 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class IntrinsicTable + { + private static readonly IntrinsicInfo[] _intrinTable; + + static IntrinsicTable() + { + _intrinTable = new IntrinsicInfo[EnumUtils.GetCount(typeof(Intrinsic))]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Intrinsic.Arm64AbsS, new IntrinsicInfo(0x5e20b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64AbsV, new IntrinsicInfo(0x0e20b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64AddhnV, new IntrinsicInfo(0x0e204000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64AddpS, new IntrinsicInfo(0x5e31b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64AddpV, new IntrinsicInfo(0x0e20bc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64AddvV, new IntrinsicInfo(0x0e31b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64AddS, new IntrinsicInfo(0x5e208400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64AddV, new IntrinsicInfo(0x0e208400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64AesdV, new IntrinsicInfo(0x4e285800u, IntrinsicType.Vector128BinaryRd)); + Add(Intrinsic.Arm64AeseV, new IntrinsicInfo(0x4e284800u, IntrinsicType.Vector128BinaryRd)); + Add(Intrinsic.Arm64AesimcV, new IntrinsicInfo(0x4e287800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64AesmcV, new IntrinsicInfo(0x4e286800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64AndV, new IntrinsicInfo(0x0e201c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64BicVi, new IntrinsicInfo(0x2f001400u, IntrinsicType.VectorBinaryBitwiseImm)); + Add(Intrinsic.Arm64BicV, new IntrinsicInfo(0x0e601c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64BifV, new IntrinsicInfo(0x2ee01c00u, IntrinsicType.VectorTernaryRdBitwise)); + Add(Intrinsic.Arm64BitV, new IntrinsicInfo(0x2ea01c00u, IntrinsicType.VectorTernaryRdBitwise)); + Add(Intrinsic.Arm64BslV, new IntrinsicInfo(0x2e601c00u, IntrinsicType.VectorTernaryRdBitwise)); + Add(Intrinsic.Arm64ClsV, new IntrinsicInfo(0x0e204800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64ClzV, new IntrinsicInfo(0x2e204800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmeqS, new IntrinsicInfo(0x7e208c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmeqV, new IntrinsicInfo(0x2e208c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmeqSz, new IntrinsicInfo(0x5e209800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmeqVz, new IntrinsicInfo(0x0e209800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmgeS, new IntrinsicInfo(0x5e203c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmgeV, new IntrinsicInfo(0x0e203c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmgeSz, new IntrinsicInfo(0x7e208800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmgeVz, new IntrinsicInfo(0x2e208800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmgtS, new IntrinsicInfo(0x5e203400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmgtV, new IntrinsicInfo(0x0e203400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmgtSz, new IntrinsicInfo(0x5e208800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmgtVz, new IntrinsicInfo(0x0e208800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmhiS, new IntrinsicInfo(0x7e203400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmhiV, new IntrinsicInfo(0x2e203400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmhsS, new IntrinsicInfo(0x7e203c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmhsV, new IntrinsicInfo(0x2e203c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmleSz, new IntrinsicInfo(0x7e209800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmleVz, new IntrinsicInfo(0x2e209800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmltSz, new IntrinsicInfo(0x5e20a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmltVz, new IntrinsicInfo(0x0e20a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmtstS, new IntrinsicInfo(0x5e208c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmtstV, new IntrinsicInfo(0x0e208c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CntV, new IntrinsicInfo(0x0e205800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64DupSe, new IntrinsicInfo(0x5e000400u, IntrinsicType.ScalarUnaryByElem)); + Add(Intrinsic.Arm64DupVe, new IntrinsicInfo(0x0e000400u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64DupGp, new IntrinsicInfo(0x0e000c00u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64EorV, new IntrinsicInfo(0x2e201c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64ExtV, new IntrinsicInfo(0x2e000000u, IntrinsicType.VectorExt)); + Add(Intrinsic.Arm64FabdS, new IntrinsicInfo(0x7ea0d400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FabdV, new IntrinsicInfo(0x2ea0d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FabsV, new IntrinsicInfo(0x0ea0f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FabsS, new IntrinsicInfo(0x1e20c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FacgeS, new IntrinsicInfo(0x7e20ec00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FacgeV, new IntrinsicInfo(0x2e20ec00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FacgtS, new IntrinsicInfo(0x7ea0ec00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FacgtV, new IntrinsicInfo(0x2ea0ec00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FaddpS, new IntrinsicInfo(0x7e30d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FaddpV, new IntrinsicInfo(0x2e20d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FaddV, new IntrinsicInfo(0x0e20d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FaddS, new IntrinsicInfo(0x1e202800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FccmpeS, new IntrinsicInfo(0x1e200410u, IntrinsicType.ScalarFPCompareCond)); + Add(Intrinsic.Arm64FccmpS, new IntrinsicInfo(0x1e200400u, IntrinsicType.ScalarFPCompareCond)); + Add(Intrinsic.Arm64FcmeqS, new IntrinsicInfo(0x5e20e400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FcmeqV, new IntrinsicInfo(0x0e20e400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FcmeqSz, new IntrinsicInfo(0x5ea0d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmeqVz, new IntrinsicInfo(0x0ea0d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmgeS, new IntrinsicInfo(0x7e20e400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FcmgeV, new IntrinsicInfo(0x2e20e400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FcmgeSz, new IntrinsicInfo(0x7ea0c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmgeVz, new IntrinsicInfo(0x2ea0c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmgtS, new IntrinsicInfo(0x7ea0e400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FcmgtV, new IntrinsicInfo(0x2ea0e400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FcmgtSz, new IntrinsicInfo(0x5ea0c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmgtVz, new IntrinsicInfo(0x0ea0c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmleSz, new IntrinsicInfo(0x7ea0d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmleVz, new IntrinsicInfo(0x2ea0d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmltSz, new IntrinsicInfo(0x5ea0e800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmltVz, new IntrinsicInfo(0x0ea0e800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmpeS, new IntrinsicInfo(0x1e202010u, IntrinsicType.ScalarFPCompare)); + Add(Intrinsic.Arm64FcmpS, new IntrinsicInfo(0x1e202000u, IntrinsicType.ScalarFPCompare)); + Add(Intrinsic.Arm64FcselS, new IntrinsicInfo(0x1e200c00u, IntrinsicType.ScalarFcsel)); + Add(Intrinsic.Arm64FcvtasS, new IntrinsicInfo(0x5e21c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtasV, new IntrinsicInfo(0x0e21c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtasGp, new IntrinsicInfo(0x1e240000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtauS, new IntrinsicInfo(0x7e21c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtauV, new IntrinsicInfo(0x2e21c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtauGp, new IntrinsicInfo(0x1e250000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtlV, new IntrinsicInfo(0x0e217800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtmsS, new IntrinsicInfo(0x5e21b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtmsV, new IntrinsicInfo(0x0e21b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtmsGp, new IntrinsicInfo(0x1e300000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtmuS, new IntrinsicInfo(0x7e21b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtmuV, new IntrinsicInfo(0x2e21b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtmuGp, new IntrinsicInfo(0x1e310000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtnsS, new IntrinsicInfo(0x5e21a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtnsV, new IntrinsicInfo(0x0e21a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtnsGp, new IntrinsicInfo(0x1e200000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtnuS, new IntrinsicInfo(0x7e21a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtnuV, new IntrinsicInfo(0x2e21a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtnuGp, new IntrinsicInfo(0x1e210000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtnV, new IntrinsicInfo(0x0e216800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64FcvtpsS, new IntrinsicInfo(0x5ea1a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtpsV, new IntrinsicInfo(0x0ea1a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtpsGp, new IntrinsicInfo(0x1e280000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtpuS, new IntrinsicInfo(0x7ea1a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtpuV, new IntrinsicInfo(0x2ea1a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtpuGp, new IntrinsicInfo(0x1e290000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtxnS, new IntrinsicInfo(0x7e216800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtxnV, new IntrinsicInfo(0x2e216800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtzsSFixed, new IntrinsicInfo(0x5f00fc00u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64FcvtzsVFixed, new IntrinsicInfo(0x0f00fc00u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64FcvtzsS, new IntrinsicInfo(0x5ea1b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtzsV, new IntrinsicInfo(0x0ea1b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtzsGpFixed, new IntrinsicInfo(0x1e180000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64FcvtzsGp, new IntrinsicInfo(0x1e380000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtzuSFixed, new IntrinsicInfo(0x7f00fc00u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64FcvtzuVFixed, new IntrinsicInfo(0x2f00fc00u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64FcvtzuS, new IntrinsicInfo(0x7ea1b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtzuV, new IntrinsicInfo(0x2ea1b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtzuGpFixed, new IntrinsicInfo(0x1e190000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64FcvtzuGp, new IntrinsicInfo(0x1e390000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtS, new IntrinsicInfo(0x1e224000u, IntrinsicType.ScalarFPConv)); + Add(Intrinsic.Arm64FdivV, new IntrinsicInfo(0x2e20fc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FdivS, new IntrinsicInfo(0x1e201800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmaddS, new IntrinsicInfo(0x1f000000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FmaxnmpS, new IntrinsicInfo(0x7e30c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FmaxnmpV, new IntrinsicInfo(0x2e20c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxnmvV, new IntrinsicInfo(0x2e30c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FmaxnmV, new IntrinsicInfo(0x0e20c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxnmS, new IntrinsicInfo(0x1e206800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmaxpS, new IntrinsicInfo(0x7e30f800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FmaxpV, new IntrinsicInfo(0x2e20f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxvV, new IntrinsicInfo(0x2e30f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FmaxV, new IntrinsicInfo(0x0e20f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxS, new IntrinsicInfo(0x1e204800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FminnmpS, new IntrinsicInfo(0x7eb0c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FminnmpV, new IntrinsicInfo(0x2ea0c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminnmvV, new IntrinsicInfo(0x2eb0c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FminnmV, new IntrinsicInfo(0x0ea0c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminnmS, new IntrinsicInfo(0x1e207800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FminpS, new IntrinsicInfo(0x7eb0f800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FminpV, new IntrinsicInfo(0x2ea0f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminvV, new IntrinsicInfo(0x2eb0f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FminV, new IntrinsicInfo(0x0ea0f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminS, new IntrinsicInfo(0x1e205800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmlaSe, new IntrinsicInfo(0x5f801000u, IntrinsicType.ScalarTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlaVe, new IntrinsicInfo(0x0f801000u, IntrinsicType.VectorTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlaV, new IntrinsicInfo(0x0e20cc00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64FmlsSe, new IntrinsicInfo(0x5f805000u, IntrinsicType.ScalarTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlsVe, new IntrinsicInfo(0x0f805000u, IntrinsicType.VectorTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlsV, new IntrinsicInfo(0x0ea0cc00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64FmovVi, new IntrinsicInfo(0x0f00f400u, IntrinsicType.VectorFmovi)); + Add(Intrinsic.Arm64FmovS, new IntrinsicInfo(0x1e204000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FmovGp, new IntrinsicInfo(0x1e260000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FmovSi, new IntrinsicInfo(0x1e201000u, IntrinsicType.ScalarFmovi)); + Add(Intrinsic.Arm64FmsubS, new IntrinsicInfo(0x1f008000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FmulxSe, new IntrinsicInfo(0x7f809000u, IntrinsicType.ScalarBinaryFPByElem)); + Add(Intrinsic.Arm64FmulxVe, new IntrinsicInfo(0x2f809000u, IntrinsicType.VectorBinaryFPByElem)); + Add(Intrinsic.Arm64FmulxS, new IntrinsicInfo(0x5e20dc00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmulxV, new IntrinsicInfo(0x0e20dc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmulSe, new IntrinsicInfo(0x5f809000u, IntrinsicType.ScalarBinaryFPByElem)); + Add(Intrinsic.Arm64FmulVe, new IntrinsicInfo(0x0f809000u, IntrinsicType.VectorBinaryFPByElem)); + Add(Intrinsic.Arm64FmulV, new IntrinsicInfo(0x2e20dc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmulS, new IntrinsicInfo(0x1e200800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FnegV, new IntrinsicInfo(0x2ea0f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FnegS, new IntrinsicInfo(0x1e214000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FnmaddS, new IntrinsicInfo(0x1f200000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FnmsubS, new IntrinsicInfo(0x1f208000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FnmulS, new IntrinsicInfo(0x1e208800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FrecpeS, new IntrinsicInfo(0x5ea1d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrecpeV, new IntrinsicInfo(0x0ea1d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrecpsS, new IntrinsicInfo(0x5e20fc00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FrecpsV, new IntrinsicInfo(0x0e20fc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FrecpxS, new IntrinsicInfo(0x5ea1f800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintaV, new IntrinsicInfo(0x2e218800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintaS, new IntrinsicInfo(0x1e264000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintiV, new IntrinsicInfo(0x2ea19800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintiS, new IntrinsicInfo(0x1e27c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintmV, new IntrinsicInfo(0x0e219800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintmS, new IntrinsicInfo(0x1e254000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintnV, new IntrinsicInfo(0x0e218800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintnS, new IntrinsicInfo(0x1e244000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintpV, new IntrinsicInfo(0x0ea18800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintpS, new IntrinsicInfo(0x1e24c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintxV, new IntrinsicInfo(0x2e219800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintxS, new IntrinsicInfo(0x1e274000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintzV, new IntrinsicInfo(0x0ea19800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintzS, new IntrinsicInfo(0x1e25c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrsqrteS, new IntrinsicInfo(0x7ea1d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrsqrteV, new IntrinsicInfo(0x2ea1d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrsqrtsS, new IntrinsicInfo(0x5ea0fc00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FrsqrtsV, new IntrinsicInfo(0x0ea0fc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FsqrtV, new IntrinsicInfo(0x2ea1f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FsqrtS, new IntrinsicInfo(0x1e21c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FsubV, new IntrinsicInfo(0x0ea0d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FsubS, new IntrinsicInfo(0x1e203800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64InsVe, new IntrinsicInfo(0x6e000400u, IntrinsicType.VectorInsertByElem)); + Add(Intrinsic.Arm64InsGp, new IntrinsicInfo(0x4e001c00u, IntrinsicType.ScalarUnaryByElem)); + Add(Intrinsic.Arm64Ld1rV, new IntrinsicInfo(0x0d40c000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld1Vms, new IntrinsicInfo(0x0c402000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld1Vss, new IntrinsicInfo(0x0d400000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64Ld2rV, new IntrinsicInfo(0x0d60c000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld2Vms, new IntrinsicInfo(0x0c408000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld2Vss, new IntrinsicInfo(0x0d600000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64Ld3rV, new IntrinsicInfo(0x0d40e000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld3Vms, new IntrinsicInfo(0x0c404000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld3Vss, new IntrinsicInfo(0x0d402000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64Ld4rV, new IntrinsicInfo(0x0d60e000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld4Vms, new IntrinsicInfo(0x0c400000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld4Vss, new IntrinsicInfo(0x0d602000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64MlaVe, new IntrinsicInfo(0x2f000000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64MlaV, new IntrinsicInfo(0x0e209400u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64MoviV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorMovi)); + Add(Intrinsic.Arm64MrsFpcr, new IntrinsicInfo(0xd53b4400u, IntrinsicType.GetRegister)); + Add(Intrinsic.Arm64MsrFpcr, new IntrinsicInfo(0xd51b4400u, IntrinsicType.SetRegister)); + Add(Intrinsic.Arm64MrsFpsr, new IntrinsicInfo(0xd53b4420u, IntrinsicType.GetRegister)); + Add(Intrinsic.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister)); + Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64MulV, new IntrinsicInfo(0x0e209c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64MvniV, new IntrinsicInfo(0x2f000400u, IntrinsicType.VectorMvni)); + Add(Intrinsic.Arm64NegS, new IntrinsicInfo(0x7e20b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64NegV, new IntrinsicInfo(0x2e20b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64NotV, new IntrinsicInfo(0x2e205800u, IntrinsicType.VectorUnaryBitwise)); + Add(Intrinsic.Arm64OrnV, new IntrinsicInfo(0x0ee01c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64OrrVi, new IntrinsicInfo(0x0f001400u, IntrinsicType.VectorBinaryBitwiseImm)); + Add(Intrinsic.Arm64OrrV, new IntrinsicInfo(0x0ea01c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64PmullV, new IntrinsicInfo(0x0e20e000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64PmulV, new IntrinsicInfo(0x2e209c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64RaddhnV, new IntrinsicInfo(0x2e204000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64RbitV, new IntrinsicInfo(0x2e605800u, IntrinsicType.VectorUnaryBitwise)); + Add(Intrinsic.Arm64Rev16V, new IntrinsicInfo(0x0e201800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64Rev32V, new IntrinsicInfo(0x2e200800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64Rev64V, new IntrinsicInfo(0x0e200800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64RshrnV, new IntrinsicInfo(0x0f008c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64RsubhnV, new IntrinsicInfo(0x2e206000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SabalV, new IntrinsicInfo(0x0e205000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SabaV, new IntrinsicInfo(0x0e207c00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SabdlV, new IntrinsicInfo(0x0e207000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SabdV, new IntrinsicInfo(0x0e207400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SadalpV, new IntrinsicInfo(0x0e206800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64SaddlpV, new IntrinsicInfo(0x0e202800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SaddlvV, new IntrinsicInfo(0x0e303800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SaddlV, new IntrinsicInfo(0x0e200000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SaddwV, new IntrinsicInfo(0x0e201000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64ScvtfSFixed, new IntrinsicInfo(0x5f00e400u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64ScvtfVFixed, new IntrinsicInfo(0x0f00e400u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64ScvtfS, new IntrinsicInfo(0x5e21d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64ScvtfV, new IntrinsicInfo(0x0e21d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64ScvtfGpFixed, new IntrinsicInfo(0x1e020000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64ScvtfGp, new IntrinsicInfo(0x1e220000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64Sha1cV, new IntrinsicInfo(0x5e000000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1hV, new IntrinsicInfo(0x5e280800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64Sha1mV, new IntrinsicInfo(0x5e002000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1pV, new IntrinsicInfo(0x5e001000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1su0V, new IntrinsicInfo(0x5e003000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1su1V, new IntrinsicInfo(0x5e281800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64Sha256h2V, new IntrinsicInfo(0x5e005000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha256hV, new IntrinsicInfo(0x5e004000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha256su0V, new IntrinsicInfo(0x5e282800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64Sha256su1V, new IntrinsicInfo(0x5e006000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64ShaddV, new IntrinsicInfo(0x0e200400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64ShllV, new IntrinsicInfo(0x2e213800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64ShlS, new IntrinsicInfo(0x5f005400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64ShlV, new IntrinsicInfo(0x0f005400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64ShrnV, new IntrinsicInfo(0x0f008400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64ShsubV, new IntrinsicInfo(0x0e202400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SliS, new IntrinsicInfo(0x7f005400u, IntrinsicType.ScalarTernaryShlRd)); + Add(Intrinsic.Arm64SliV, new IntrinsicInfo(0x2f005400u, IntrinsicType.VectorTernaryShlRd)); + Add(Intrinsic.Arm64SmaxpV, new IntrinsicInfo(0x0e20a400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SmaxvV, new IntrinsicInfo(0x0e30a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SmaxV, new IntrinsicInfo(0x0e206400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SminpV, new IntrinsicInfo(0x0e20ac00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SminvV, new IntrinsicInfo(0x0e31a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SminV, new IntrinsicInfo(0x0e206c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SmlalVe, new IntrinsicInfo(0x0f002000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64SmlalV, new IntrinsicInfo(0x0e208000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SmlslVe, new IntrinsicInfo(0x0f006000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64SmlslV, new IntrinsicInfo(0x0e20a000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SmovV, new IntrinsicInfo(0x0e002c00u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64SmullVe, new IntrinsicInfo(0x0f00a000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SmullV, new IntrinsicInfo(0x0e20c000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqabsS, new IntrinsicInfo(0x5e207800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64SqabsV, new IntrinsicInfo(0x0e207800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SqaddS, new IntrinsicInfo(0x5e200c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqaddV, new IntrinsicInfo(0x0e200c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmlalSe, new IntrinsicInfo(0x5f003000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmlalVe, new IntrinsicInfo(0x0f003000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmlalS, new IntrinsicInfo(0x5e209000u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmlalV, new IntrinsicInfo(0x0e209000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmlslSe, new IntrinsicInfo(0x5f007000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmlslVe, new IntrinsicInfo(0x0f007000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmlslS, new IntrinsicInfo(0x5e20b000u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmlslV, new IntrinsicInfo(0x0e20b000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmulhSe, new IntrinsicInfo(0x5f00c000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmulhVe, new IntrinsicInfo(0x0f00c000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmulhS, new IntrinsicInfo(0x5e20b400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmulhV, new IntrinsicInfo(0x0e20b400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmullSe, new IntrinsicInfo(0x5f00b000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmullVe, new IntrinsicInfo(0x0f00b000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmullS, new IntrinsicInfo(0x5e20d000u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmullV, new IntrinsicInfo(0x0e20d000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqnegS, new IntrinsicInfo(0x7e207800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64SqnegV, new IntrinsicInfo(0x2e207800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SqrdmulhSe, new IntrinsicInfo(0x5f00d000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqrdmulhVe, new IntrinsicInfo(0x0f00d000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqrdmulhS, new IntrinsicInfo(0x7e20b400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqrdmulhV, new IntrinsicInfo(0x2e20b400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqrshlS, new IntrinsicInfo(0x5e205c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqrshlV, new IntrinsicInfo(0x0e205c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqrshrnS, new IntrinsicInfo(0x5f009c00u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqrshrnV, new IntrinsicInfo(0x0f009c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqrshrunS, new IntrinsicInfo(0x7f008c00u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqrshrunV, new IntrinsicInfo(0x2f008c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqshluS, new IntrinsicInfo(0x7f006400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64SqshluV, new IntrinsicInfo(0x2f006400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64SqshlSi, new IntrinsicInfo(0x5f007400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64SqshlVi, new IntrinsicInfo(0x0f007400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64SqshlS, new IntrinsicInfo(0x5e204c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqshlV, new IntrinsicInfo(0x0e204c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqshrnS, new IntrinsicInfo(0x5f009400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqshrnV, new IntrinsicInfo(0x0f009400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqshrunS, new IntrinsicInfo(0x7f008400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqshrunV, new IntrinsicInfo(0x2f008400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqsubS, new IntrinsicInfo(0x5e202c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqsubV, new IntrinsicInfo(0x0e202c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqxtnS, new IntrinsicInfo(0x5e214800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64SqxtnV, new IntrinsicInfo(0x0e214800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64SqxtunS, new IntrinsicInfo(0x7e212800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64SqxtunV, new IntrinsicInfo(0x2e212800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64SrhaddV, new IntrinsicInfo(0x0e201400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SriS, new IntrinsicInfo(0x7f004400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SriV, new IntrinsicInfo(0x2f004400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SrshlS, new IntrinsicInfo(0x5e205400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SrshlV, new IntrinsicInfo(0x0e205400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SrshrS, new IntrinsicInfo(0x5f002400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64SrshrV, new IntrinsicInfo(0x0f002400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64SrsraS, new IntrinsicInfo(0x5f003400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SrsraV, new IntrinsicInfo(0x0f003400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SshllV, new IntrinsicInfo(0x0f00a400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64SshlS, new IntrinsicInfo(0x5e204400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SshlV, new IntrinsicInfo(0x0e204400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SshrS, new IntrinsicInfo(0x5f000400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64SshrV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64SsraS, new IntrinsicInfo(0x5f001400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SsraV, new IntrinsicInfo(0x0f001400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SsublV, new IntrinsicInfo(0x0e202000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SsubwV, new IntrinsicInfo(0x0e203000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64St1Vms, new IntrinsicInfo(0x0c002000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St1Vss, new IntrinsicInfo(0x0d000000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64St2Vms, new IntrinsicInfo(0x0c008000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St2Vss, new IntrinsicInfo(0x0d200000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64St3Vms, new IntrinsicInfo(0x0c004000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St3Vss, new IntrinsicInfo(0x0d002000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64St4Vms, new IntrinsicInfo(0x0c000000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St4Vss, new IntrinsicInfo(0x0d202000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64SubhnV, new IntrinsicInfo(0x0e206000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SubS, new IntrinsicInfo(0x7e208400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SubV, new IntrinsicInfo(0x2e208400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SuqaddS, new IntrinsicInfo(0x5e203800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64SuqaddV, new IntrinsicInfo(0x0e203800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64TblV, new IntrinsicInfo(0x0e000000u, IntrinsicType.VectorLookupTable)); + Add(Intrinsic.Arm64TbxV, new IntrinsicInfo(0x0e001000u, IntrinsicType.VectorLookupTable)); + Add(Intrinsic.Arm64Trn1V, new IntrinsicInfo(0x0e002800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Trn2V, new IntrinsicInfo(0x0e006800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UabalV, new IntrinsicInfo(0x2e205000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UabaV, new IntrinsicInfo(0x2e207c00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UabdlV, new IntrinsicInfo(0x2e207000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UabdV, new IntrinsicInfo(0x2e207400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UadalpV, new IntrinsicInfo(0x2e206800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64UaddlpV, new IntrinsicInfo(0x2e202800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UaddlvV, new IntrinsicInfo(0x2e303800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UaddlV, new IntrinsicInfo(0x2e200000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UaddwV, new IntrinsicInfo(0x2e201000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UcvtfSFixed, new IntrinsicInfo(0x7f00e400u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64UcvtfVFixed, new IntrinsicInfo(0x2f00e400u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64UcvtfS, new IntrinsicInfo(0x7e21d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64UcvtfV, new IntrinsicInfo(0x2e21d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UcvtfGpFixed, new IntrinsicInfo(0x1e030000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64UcvtfGp, new IntrinsicInfo(0x1e230000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64UhaddV, new IntrinsicInfo(0x2e200400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UhsubV, new IntrinsicInfo(0x2e202400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UmaxpV, new IntrinsicInfo(0x2e20a400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UmaxvV, new IntrinsicInfo(0x2e30a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UmaxV, new IntrinsicInfo(0x2e206400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UminpV, new IntrinsicInfo(0x2e20ac00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UminvV, new IntrinsicInfo(0x2e31a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UminV, new IntrinsicInfo(0x2e206c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UmlalVe, new IntrinsicInfo(0x2f002000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64UmlalV, new IntrinsicInfo(0x2e208000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UmlslVe, new IntrinsicInfo(0x2f006000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64UmlslV, new IntrinsicInfo(0x2e20a000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UmovV, new IntrinsicInfo(0x0e003c00u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64UmullVe, new IntrinsicInfo(0x2f00a000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64UmullV, new IntrinsicInfo(0x2e20c000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqaddS, new IntrinsicInfo(0x7e200c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqaddV, new IntrinsicInfo(0x2e200c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqrshlS, new IntrinsicInfo(0x7e205c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqrshlV, new IntrinsicInfo(0x2e205c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqrshrnS, new IntrinsicInfo(0x7f009c00u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UqrshrnV, new IntrinsicInfo(0x2f009c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UqshlSi, new IntrinsicInfo(0x7f007400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64UqshlVi, new IntrinsicInfo(0x2f007400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64UqshlS, new IntrinsicInfo(0x7e204c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqshlV, new IntrinsicInfo(0x2e204c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqshrnS, new IntrinsicInfo(0x7f009400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UqshrnV, new IntrinsicInfo(0x2f009400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UqsubS, new IntrinsicInfo(0x7e202c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqsubV, new IntrinsicInfo(0x2e202c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqxtnS, new IntrinsicInfo(0x7e214800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64UqxtnV, new IntrinsicInfo(0x2e214800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64UrecpeV, new IntrinsicInfo(0x0ea1c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UrhaddV, new IntrinsicInfo(0x2e201400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UrshlS, new IntrinsicInfo(0x7e205400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UrshlV, new IntrinsicInfo(0x2e205400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UrshrS, new IntrinsicInfo(0x7f002400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64UrshrV, new IntrinsicInfo(0x2f002400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64UrsqrteV, new IntrinsicInfo(0x2ea1c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UrsraS, new IntrinsicInfo(0x7f003400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UrsraV, new IntrinsicInfo(0x2f003400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UshllV, new IntrinsicInfo(0x2f00a400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64UshlS, new IntrinsicInfo(0x7e204400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UshlV, new IntrinsicInfo(0x2e204400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UshrS, new IntrinsicInfo(0x7f000400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64UshrV, new IntrinsicInfo(0x2f000400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64UsqaddS, new IntrinsicInfo(0x7e203800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64UsqaddV, new IntrinsicInfo(0x2e203800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64UsraS, new IntrinsicInfo(0x7f001400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UsraV, new IntrinsicInfo(0x2f001400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UsublV, new IntrinsicInfo(0x2e202000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UsubwV, new IntrinsicInfo(0x2e203000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Uzp1V, new IntrinsicInfo(0x0e001800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Uzp2V, new IntrinsicInfo(0x0e005800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64XtnV, new IntrinsicInfo(0x0e212800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64Zip1V, new IntrinsicInfo(0x0e003800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Zip2V, new IntrinsicInfo(0x0e007800u, IntrinsicType.VectorBinary)); +#pragma warning restore IDE0055 + } + + private static void Add(Intrinsic intrin, IntrinsicInfo info) + { + _intrinTable[(int)intrin] = info; + } + + public static IntrinsicInfo GetInfo(Intrinsic intrin) + { + return _intrinTable[(int)intrin]; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs b/src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs new file mode 100644 index 000000000..7538575c9 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs @@ -0,0 +1,60 @@ +namespace ARMeilleure.CodeGen.Arm64 +{ + enum IntrinsicType + { + ScalarUnary, + ScalarUnaryByElem, + ScalarBinary, + ScalarBinaryByElem, + ScalarBinaryFPByElem, + ScalarBinaryRd, + ScalarBinaryShl, + ScalarBinaryShr, + ScalarFcsel, + ScalarFmovi, + ScalarFPCompare, + ScalarFPCompareCond, + ScalarFPConv, + ScalarFPConvFixed, + ScalarFPConvFixedGpr, + ScalarFPConvGpr, + ScalarTernary, + ScalarTernaryFPRdByElem, + ScalarTernaryShlRd, + ScalarTernaryShrRd, + + Vector128Unary, + Vector128Binary, + Vector128BinaryRd, + + VectorUnary, + VectorUnaryBitwise, + VectorUnaryByElem, + VectorBinary, + VectorBinaryBitwise, + VectorBinaryBitwiseImm, + VectorBinaryByElem, + VectorBinaryFPByElem, + VectorBinaryRd, + VectorBinaryShl, + VectorBinaryShr, + VectorExt, + VectorFmovi, + VectorFPConvFixed, + VectorInsertByElem, + VectorLdSt, + VectorLdStSs, + VectorLookupTable, + VectorMovi, + VectorMvni, + VectorTernaryFPRdByElem, + VectorTernaryRd, + VectorTernaryRdBitwise, + VectorTernaryRdByElem, + VectorTernaryShlRd, + VectorTernaryShrRd, + + GetRegister, + SetRegister, + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs b/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs new file mode 100644 index 000000000..f66bb66e6 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs @@ -0,0 +1,887 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class PreAllocator + { + private class ConstantDict + { + private readonly Dictionary<(ulong, OperandType), Operand> _constants; + + public ConstantDict() + { + _constants = new Dictionary<(ulong, OperandType), Operand>(); + } + + public void Add(ulong value, OperandType type, Operand local) + { + _constants.Add((value, type), local); + } + + public bool TryGetValue(ulong value, OperandType type, out Operand local) + { + return _constants.TryGetValue((value, type), out local); + } + } + + public static void RunPass(CompilerContext cctx, out int maxCallArgs) + { + maxCallArgs = -1; + + Span buffer = default; + + Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()]; + + for (BasicBlock block = cctx.Cfg.Blocks.First; block != null; block = block.ListNext) + { + ConstantDict constants = new(); + + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + if (node.Instruction == Instruction.Phi) + { + continue; + } + + InsertConstantRegCopies(constants, block.Operations, node); + InsertDestructiveRegCopies(block.Operations, node); + + switch (node.Instruction) + { + case Instruction.Call: + // Get the maximum number of arguments used on a call. + // On windows, when a struct is returned from the call, + // we also need to pass the pointer where the struct + // should be written on the first argument. + int argsCount = node.SourcesCount - 1; + + if (node.Destination != default && node.Destination.Type == OperandType.V128) + { + argsCount++; + } + + if (maxCallArgs < argsCount) + { + maxCallArgs = argsCount; + } + + // Copy values to registers expected by the function + // being called, as mandated by the ABI. + InsertCallCopies(constants, block.Operations, node); + break; + case Instruction.CompareAndSwap: + case Instruction.CompareAndSwap16: + case Instruction.CompareAndSwap8: + nextNode = GenerateCompareAndSwap(block.Operations, node); + break; + case Instruction.LoadArgument: + nextNode = InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node); + break; + case Instruction.Return: + InsertReturnCopy(block.Operations, node); + break; + case Instruction.Tailcall: + InsertTailcallCopies(constants, block.Operations, node, node); + break; + } + } + } + } + + private static void InsertConstantRegCopies(ConstantDict constants, IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0 || IsIntrinsicWithConst(node)) + { + return; + } + + Instruction inst = node.Instruction; + + Operand src1 = node.GetSource(0); + Operand src2; + + if (src1.Kind == OperandKind.Constant) + { + if (!src1.Type.IsInteger()) + { + // Handle non-integer types (FP32, FP64 and V128). + // For instructions without an immediate operand, we do the following: + // - Insert a copy with the constant value (as integer) to a GPR. + // - Insert a copy from the GPR to a XMM register. + // - Replace the constant use with the XMM register. + src1 = AddFloatConstantCopy(constants, nodes, node, src1); + + node.SetSource(0, src1); + } + else if (!HasConstSrc1(node, src1.Value)) + { + // Handle integer types. + // Most ALU instructions accepts a 32-bits immediate on the second operand. + // We need to ensure the following: + // - If the constant is on operand 1, we need to move it. + // -- But first, we try to swap operand 1 and 2 if the instruction is commutative. + // -- Doing so may allow us to encode the constant as operand 2 and avoid a copy. + // - If the constant is on operand 2, we check if the instruction supports it, + // if not, we also add a copy. 64-bits constants are usually not supported. + if (IsCommutative(node)) + { + src2 = node.GetSource(1); + + (src2, src1) = (src1, src2); + + node.SetSource(0, src1); + node.SetSource(1, src2); + } + + if (src1.Kind == OperandKind.Constant) + { + src1 = AddIntConstantCopy(constants, nodes, node, src1); + + node.SetSource(0, src1); + } + } + } + + if (node.SourcesCount < 2) + { + return; + } + + src2 = node.GetSource(1); + + if (src2.Kind == OperandKind.Constant) + { + if (!src2.Type.IsInteger()) + { + src2 = AddFloatConstantCopy(constants, nodes, node, src2); + + node.SetSource(1, src2); + } + else if (!HasConstSrc2(inst, src2)) + { + src2 = AddIntConstantCopy(constants, nodes, node, src2); + + node.SetSource(1, src2); + } + } + + if (node.SourcesCount < 3 || + node.Instruction == Instruction.BranchIf || + node.Instruction == Instruction.Compare || + node.Instruction == Instruction.VectorInsert || + node.Instruction == Instruction.VectorInsert16 || + node.Instruction == Instruction.VectorInsert8) + { + return; + } + + for (int srcIndex = 2; srcIndex < node.SourcesCount; srcIndex++) + { + Operand src = node.GetSource(srcIndex); + + if (src.Kind == OperandKind.Constant) + { + if (!src.Type.IsInteger()) + { + src = AddFloatConstantCopy(constants, nodes, node, src); + + node.SetSource(srcIndex, src); + } + else + { + src = AddIntConstantCopy(constants, nodes, node, src); + + node.SetSource(srcIndex, src); + } + } + } + } + + private static void InsertDestructiveRegCopies(IntrusiveList nodes, Operation node) + { + if (node.Destination == default || node.SourcesCount == 0) + { + return; + } + + Operand dest = node.Destination; + Operand src1 = node.GetSource(0); + + if (IsSameOperandDestSrc1(node) && src1.Kind == OperandKind.LocalVariable) + { + bool useNewLocal = false; + + for (int srcIndex = 1; srcIndex < node.SourcesCount; srcIndex++) + { + if (node.GetSource(srcIndex) == dest) + { + useNewLocal = true; + + break; + } + } + + if (useNewLocal) + { + // Dest is being used as some source already, we need to use a new + // local to store the temporary value, otherwise the value on dest + // local would be overwritten. + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, src1)); + + node.SetSource(0, temp); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, temp)); + + node.Destination = temp; + } + else + { + nodes.AddBefore(node, Operation(Instruction.Copy, dest, src1)); + + node.SetSource(0, dest); + } + } + } + + private static void InsertCallCopies(ConstantDict constants, IntrusiveList nodes, Operation node) + { + Operation operation = node; + + Operand dest = operation.Destination; + + List sources = new() + { + operation.GetSource(0), + }; + + int argsCount = operation.SourcesCount - 1; + + int intMax = CallingConvention.GetArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + int stackOffset = 0; + + for (int index = 0; index < argsCount; index++) + { + Operand source = operation.GetSource(index + 1); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < intMax; + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + Operand offset = Const(stackOffset); + + Operation spillOp = Operation(Instruction.SpillArg, default, offset, source); + + InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, spillOp)); + + stackOffset += source.Type.GetSizeInBytes(); + } + } + + if (dest != default) + { + if (dest.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg)); + nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1))); + + operation.Destination = default; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, dest, retReg); + + nodes.AddAfter(node, copyOp); + + operation.Destination = retReg; + } + } + + operation.SetSources(sources.ToArray()); + } + + private static void InsertTailcallCopies(ConstantDict constants, + IntrusiveList nodes, + Operation node, + Operation operation) + { + List sources = new() + { + operation.GetSource(0), + }; + + int argsCount = operation.SourcesCount - 1; + + int intMax = CallingConvention.GetArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = operation.GetSource(1 + index); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)"); + } + } + + // The target address must be on the return registers, since we + // don't return anything and it is guaranteed to not be a + // callee saved register (which would be trashed on the epilogue). + Operand tcAddress = Gpr(CodeGenCommon.TcAddressRegister, OperandType.I64); + + Operation addrCopyOp = Operation(Instruction.Copy, tcAddress, operation.GetSource(0)); + + nodes.AddBefore(node, addrCopyOp); + + sources[0] = tcAddress; + + operation.SetSources(sources.ToArray()); + } + + private static Operation GenerateCompareAndSwap(IntrusiveList nodes, Operation node) + { + Operand expected = node.GetSource(1); + + if (expected.Type == OperandType.V128) + { + Operand dest = node.Destination; + Operand expectedLow = Local(OperandType.I64); + Operand expectedHigh = Local(OperandType.I64); + Operand desiredLow = Local(OperandType.I64); + Operand desiredHigh = Local(OperandType.I64); + Operand actualLow = Local(OperandType.I64); + Operand actualHigh = Local(OperandType.I64); + + Operand address = node.GetSource(0); + Operand desired = node.GetSource(2); + + void SplitOperand(Operand source, Operand low, Operand high) + { + nodes.AddBefore(node, Operation(Instruction.VectorExtract, low, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, high, source, Const(1))); + } + + SplitOperand(expected, expectedLow, expectedHigh); + SplitOperand(desired, desiredLow, desiredHigh); + + Operation operation = node; + + // Update the sources and destinations with split 64-bit halfs of the whole 128-bit values. + // We also need a additional registers that will be used to store temporary information. + operation.SetDestinations(new[] { actualLow, actualHigh, Local(OperandType.I64), Local(OperandType.I64) }); + operation.SetSources(new[] { address, expectedLow, expectedHigh, desiredLow, desiredHigh }); + + // Add some dummy uses of the input operands, as the CAS operation will be a loop, + // so they can't be used as destination operand. + for (int i = 0; i < operation.SourcesCount; i++) + { + Operand src = operation.GetSource(i); + node = nodes.AddAfter(node, Operation(Instruction.Copy, src, src)); + } + + // Assemble the vector with the 64-bit values at the given memory location. + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, actualLow)); + node = nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, actualHigh, Const(1))); + } + else + { + // We need a additional register where the store result will be written to. + node.SetDestinations(new[] { node.Destination, Local(OperandType.I32) }); + + // Add some dummy uses of the input operands, as the CAS operation will be a loop, + // so they can't be used as destination operand. + Operation operation = node; + + for (int i = 0; i < operation.SourcesCount; i++) + { + Operand src = operation.GetSource(i); + node = nodes.AddAfter(node, Operation(Instruction.Copy, src, src)); + } + } + + return node.ListNext; + } + + private static void InsertReturnCopy(IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0) + { + return; + } + + Operand source = node.GetSource(0); + + if (source.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1))); + } + else + { + Operand retReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), source.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), source.Type); + + Operation retCopyOp = Operation(Instruction.Copy, retReg, source); + + nodes.AddBefore(node, retCopyOp); + } + } + + private static Operation InsertLoadArgumentCopy( + CompilerContext cctx, + ref Span buffer, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + Operand source = node.GetSource(0); + + Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind."); + + int index = source.AsInt32(); + + int intCount = 0; + int vecCount = 0; + + for (int cIndex = 0; cIndex < index; cIndex++) + { + OperandType argType = cctx.FuncArgTypes[cIndex]; + + if (argType.IsInteger()) + { + intCount++; + } + else if (argType == OperandType.V128) + { + intCount += 2; + } + else + { + vecCount++; + } + } + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < CallingConvention.GetArgumentsOnRegsCount(); + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < CallingConvention.GetArgumentsOnRegsCount(); + } + else + { + passOnReg = vecCount < CallingConvention.GetArgumentsOnRegsCount(); + } + + if (passOnReg) + { + Operand dest = node.Destination; + + if (preservedArgs[index] == default) + { + if (dest.Type == OperandType.V128) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand pArg = Local(OperandType.V128); + + Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64); + Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64); + + Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg); + Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1)); + + cctx.Cfg.Entry.Operations.AddFirst(copyH); + cctx.Cfg.Entry.Operations.AddFirst(copyL); + + preservedArgs[index] = pArg; + } + else + { + Operand pArg = Local(dest.Type); + + Operand argReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + } + + Operation nextNode; + + if (dest.AssignmentsCount == 1) + { + // Let's propagate the argument if we can to avoid copies. + PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]); + nextNode = node.ListNext; + } + else + { + Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]); + nextNode = nodes.AddBefore(node, argCopyOp); + } + + Delete(nodes, node); + return nextNode; + } + else + { + // TODO: Pass on stack. + return node; + } + } + + private static Operand AddFloatConstantCopy( + ConstantDict constants, + IntrusiveList nodes, + Operation node, + Operand source) + { + Operand temp = Local(source.Type); + + Operand intConst = AddIntConstantCopy(constants, nodes, node, GetIntConst(source)); + + Operation copyOp = Operation(Instruction.VectorCreateScalar, temp, intConst); + + nodes.AddBefore(node, copyOp); + + return temp; + } + + private static Operand AddIntConstantCopy( + ConstantDict constants, + IntrusiveList nodes, + Operation node, + Operand source) + { + if (constants.TryGetValue(source.Value, source.Type, out Operand temp)) + { + return temp; + } + + temp = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, temp, source); + + nodes.AddBefore(node, copyOp); + + constants.Add(source.Value, source.Type, temp); + + return temp; + } + + private static Operand GetIntConst(Operand value) + { + if (value.Type == OperandType.FP32) + { + return Const(value.AsInt32()); + } + else if (value.Type == OperandType.FP64) + { + return Const(value.AsInt64()); + } + + return value; + } + + private static void Delete(IntrusiveList nodes, Operation node) + { + node.Destination = default; + + for (int index = 0; index < node.SourcesCount; index++) + { + node.SetSource(index, default); + } + + nodes.Remove(node); + } + + private static Operand Gpr(int register, OperandType type) + { + return Register(register, RegisterType.Integer, type); + } + + private static Operand Xmm(int register, OperandType type) + { + return Register(register, RegisterType.Vector, type); + } + + private static bool IsSameOperandDestSrc1(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Extended: + return IsSameOperandDestSrc1(operation.Intrinsic); + case Instruction.VectorInsert: + case Instruction.VectorInsert16: + case Instruction.VectorInsert8: + return true; + } + + return false; + } + + private static bool IsSameOperandDestSrc1(Intrinsic intrinsic) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(intrinsic & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); + + return info.Type == IntrinsicType.ScalarBinaryRd || + info.Type == IntrinsicType.ScalarTernaryFPRdByElem || + info.Type == IntrinsicType.ScalarTernaryShlRd || + info.Type == IntrinsicType.ScalarTernaryShrRd || + info.Type == IntrinsicType.Vector128BinaryRd || + info.Type == IntrinsicType.VectorBinaryRd || + info.Type == IntrinsicType.VectorInsertByElem || + info.Type == IntrinsicType.VectorTernaryRd || + info.Type == IntrinsicType.VectorTernaryRdBitwise || + info.Type == IntrinsicType.VectorTernaryFPRdByElem || + info.Type == IntrinsicType.VectorTernaryRdByElem || + info.Type == IntrinsicType.VectorTernaryShlRd || + info.Type == IntrinsicType.VectorTernaryShrRd; + } + + private static bool HasConstSrc1(Operation node, ulong value) + { + switch (node.Instruction) + { + case Instruction.Add: + case Instruction.BranchIf: + case Instruction.Compare: + case Instruction.Subtract: + // The immediate encoding of those instructions does not allow Rn to be + // XZR (it will be SP instead), so we can't allow a Rn constant in this case. + return value == 0 && NotConstOrConst0(node.GetSource(1)); + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + case Instruction.ByteSwap: + case Instruction.CountLeadingZeros: + case Instruction.Multiply: + case Instruction.Negate: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + return value == 0; + case Instruction.Copy: + case Instruction.LoadArgument: + case Instruction.Spill: + case Instruction.SpillArg: + return true; + case Instruction.Extended: + return value == 0; + } + + return false; + } + + private static bool NotConstOrConst0(Operand operand) + { + return operand.Kind != OperandKind.Constant || operand.Value == 0; + } + + private static bool HasConstSrc2(Instruction inst, Operand operand) + { + ulong value = operand.Value; + + switch (inst) + { + case Instruction.Add: + case Instruction.BranchIf: + case Instruction.Compare: + case Instruction.Subtract: + return ConstFitsOnUImm12Sh(value); + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + return value == 0 || CodeGenCommon.TryEncodeBitMask(operand, out _, out _, out _); + case Instruction.Multiply: + case Instruction.Store: + case Instruction.Store16: + case Instruction.Store8: + return value == 0; + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.VectorExtract: + case Instruction.VectorExtract16: + case Instruction.VectorExtract8: + return true; + case Instruction.Extended: + // TODO: Check if actual intrinsic is supposed to have consts here? + // Right now we only hit this case for fixed-point int <-> FP conversion instructions. + return true; + } + + return false; + } + + private static bool IsCommutative(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.Multiply: + return true; + + case Instruction.BranchIf: + case Instruction.Compare: + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var compType = (Comparison)comp.AsInt32(); + + return compType == Comparison.Equal || compType == Comparison.NotEqual; + } + } + + return false; + } + + private static bool ConstFitsOnUImm12Sh(ulong value) + { + return (value & ~0xfffUL) == 0 || (value & ~0xfff000UL) == 0; + } + + private static bool IsIntrinsicWithConst(Operation operation) + { + bool isIntrinsic = IsIntrinsic(operation.Instruction); + + if (isIntrinsic) + { + Intrinsic intrinsic = operation.Intrinsic; + IntrinsicInfo info = IntrinsicTable.GetInfo(intrinsic & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); + + // Those have integer inputs that don't support consts. + return info.Type != IntrinsicType.ScalarFPConvGpr && + info.Type != IntrinsicType.ScalarFPConvFixedGpr && + info.Type != IntrinsicType.SetRegister; + } + + return false; + } + + private static bool IsIntrinsic(Instruction inst) + { + return inst == Instruction.Extended; + } + } +} diff --git a/src/ARMeilleure/CodeGen/CompiledFunction.cs b/src/ARMeilleure/CodeGen/CompiledFunction.cs new file mode 100644 index 000000000..3844cbfc9 --- /dev/null +++ b/src/ARMeilleure/CodeGen/CompiledFunction.cs @@ -0,0 +1,68 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Translation.Cache; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.CodeGen +{ + /// + /// Represents a compiled function. + /// + readonly struct CompiledFunction + { + /// + /// Gets the machine code of the . + /// + public byte[] Code { get; } + + /// + /// Gets the of the . + /// + public UnwindInfo UnwindInfo { get; } + + /// + /// Gets the of the . + /// + public RelocInfo RelocInfo { get; } + + /// + /// Initializes a new instance of the struct with the specified machine code, + /// unwind info and relocation info. + /// + /// Machine code + /// Unwind info + /// Relocation info + internal CompiledFunction(byte[] code, UnwindInfo unwindInfo, RelocInfo relocInfo) + { + Code = code; + UnwindInfo = unwindInfo; + RelocInfo = relocInfo; + } + + /// + /// Maps the onto the and returns a delegate of type + /// pointing to the mapped function. + /// + /// Type of delegate + /// A delegate of type pointing to the mapped function + public T Map() + { + return MapWithPointer(out _); + } + + /// + /// Maps the onto the and returns a delegate of type + /// pointing to the mapped function. + /// + /// Type of delegate + /// Pointer to the function code in memory + /// A delegate of type pointing to the mapped function + public T MapWithPointer(out IntPtr codePointer) + { + codePointer = JitCache.Map(this); + + return Marshal.GetDelegateForFunctionPointer(codePointer); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Linking/RelocEntry.cs b/src/ARMeilleure/CodeGen/Linking/RelocEntry.cs new file mode 100644 index 000000000..d103bc395 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/RelocEntry.cs @@ -0,0 +1,38 @@ +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Represents a relocation. + /// + readonly struct RelocEntry + { + public const int Stride = 13; // Bytes. + + /// + /// Gets the position of the relocation. + /// + public int Position { get; } + + /// + /// Gets the of the relocation. + /// + public Symbol Symbol { get; } + + /// + /// Initializes a new instance of the struct with the specified position and + /// . + /// + /// Position of relocation + /// Symbol of relocation + public RelocEntry(int position, Symbol symbol) + { + Position = position; + Symbol = symbol; + } + + /// + public override string ToString() + { + return $"({nameof(Position)} = {Position}, {nameof(Symbol)} = {Symbol})"; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Linking/RelocInfo.cs b/src/ARMeilleure/CodeGen/Linking/RelocInfo.cs new file mode 100644 index 000000000..01ff0347b --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/RelocInfo.cs @@ -0,0 +1,32 @@ +using System; + +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Represents relocation information about a . + /// + readonly struct RelocInfo + { + /// + /// Gets an empty . + /// + public static RelocInfo Empty { get; } = new RelocInfo(null); + + private readonly RelocEntry[] _entries; + + /// + /// Gets the set of . + /// + public ReadOnlySpan Entries => _entries; + + /// + /// Initializes a new instance of the struct with the specified set of + /// . + /// + /// Set of to use + public RelocInfo(RelocEntry[] entries) + { + _entries = entries; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Linking/Symbol.cs b/src/ARMeilleure/CodeGen/Linking/Symbol.cs new file mode 100644 index 000000000..5559afe09 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/Symbol.cs @@ -0,0 +1,99 @@ +using System; + +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Represents a symbol. + /// + readonly struct Symbol + { + private readonly ulong _value; + + /// + /// Gets the of the . + /// + public SymbolType Type { get; } + + /// + /// Gets the value of the . + /// + /// is + public ulong Value + { + get + { + if (Type == SymbolType.None) + { + ThrowSymbolNone(); + } + + return _value; + } + } + + /// + /// Initializes a new instance of the structure with the specified and value. + /// + /// Type of symbol + /// Value of symbol + public Symbol(SymbolType type, ulong value) + { + (Type, _value) = (type, value); + } + + /// + /// Determines if the specified instances are equal. + /// + /// First instance + /// Second instance + /// if equal; otherwise + public static bool operator ==(Symbol a, Symbol b) + { + return a.Equals(b); + } + + /// + /// Determines if the specified instances are not equal. + /// + /// First instance + /// Second instance + /// if not equal; otherwise + public static bool operator !=(Symbol a, Symbol b) + { + return !(a == b); + } + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// if equal; otherwise + public bool Equals(Symbol other) + { + return other.Type == Type && other._value == _value; + } + + /// + public override bool Equals(object obj) + { + return obj is Symbol sym && Equals(sym); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Type, _value); + } + + /// + public override string ToString() + { + return $"{Type}:{_value}"; + } + + private static void ThrowSymbolNone() + { + throw new InvalidOperationException("Symbol refers to nothing."); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Linking/SymbolType.cs b/src/ARMeilleure/CodeGen/Linking/SymbolType.cs new file mode 100644 index 000000000..29011a762 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/SymbolType.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Types of . + /// + enum SymbolType : byte + { + /// + /// Refers to nothing, i.e no symbol. + /// + None, + + /// + /// Refers to an entry in . + /// + DelegateTable, + + /// + /// Refers to an entry in . + /// + FunctionTable, + + /// + /// Refers to a special symbol which is handled by . + /// + Special, + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs b/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs new file mode 100644 index 000000000..5f0e37721 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs @@ -0,0 +1,72 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class BlockPlacement + { + public static void RunPass(ControlFlowGraph cfg) + { + bool update = false; + + BasicBlock block; + BasicBlock nextBlock; + + BasicBlock lastBlock = cfg.Blocks.Last; + + // Move cold blocks at the end of the list, so that they are emitted away from hot code. + for (block = cfg.Blocks.First; block != null; block = nextBlock) + { + nextBlock = block.ListNext; + + if (block.Frequency == BasicBlockFrequency.Cold) + { + cfg.Blocks.Remove(block); + cfg.Blocks.AddLast(block); + } + + if (block == lastBlock) + { + break; + } + } + + for (block = cfg.Blocks.First; block != null; block = nextBlock) + { + nextBlock = block.ListNext; + + if (block.SuccessorsCount == 2) + { + Operation branchOp = block.Operations.Last; + + Debug.Assert(branchOp.Instruction == Instruction.BranchIf); + + BasicBlock falseSucc = block.GetSuccessor(0); + BasicBlock trueSucc = block.GetSuccessor(1); + + // If true successor is next block in list, invert the condition. We avoid extra branching by + // making the true side the fallthrough (i.e, convert it to the false side). + if (trueSucc == block.ListNext) + { + Comparison comp = (Comparison)branchOp.GetSource(2).AsInt32(); + Comparison compInv = comp.Invert(); + + branchOp.SetSource(2, Const((int)compInv)); + + block.SetSuccessor(0, trueSucc); + block.SetSuccessor(1, falseSucc); + + update = true; + } + } + } + + if (update) + { + cfg.Update(); + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs b/src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs new file mode 100644 index 000000000..be3dff58c --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs @@ -0,0 +1,346 @@ +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.Compare: + if (type == OperandType.I32 && + operation.GetSource(0).Type == type && + operation.GetSource(1).Type == type) + { + switch ((Comparison)operation.GetSource(2).Value) + { + case Comparison.Equal: + EvaluateBinaryI32(operation, (x, y) => x == y ? 1 : 0); + break; + case Comparison.NotEqual: + EvaluateBinaryI32(operation, (x, y) => x != y ? 1 : 0); + break; + case Comparison.Greater: + EvaluateBinaryI32(operation, (x, y) => x > y ? 1 : 0); + break; + case Comparison.LessOrEqual: + EvaluateBinaryI32(operation, (x, y) => x <= y ? 1 : 0); + break; + case Comparison.GreaterUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x > (uint)y ? 1 : 0); + break; + case Comparison.LessOrEqualUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x <= (uint)y ? 1 : 0); + break; + case Comparison.GreaterOrEqual: + EvaluateBinaryI32(operation, (x, y) => x >= y ? 1 : 0); + break; + case Comparison.Less: + EvaluateBinaryI32(operation, (x, y) => x < y ? 1 : 0); + break; + case Comparison.GreaterOrEqualUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x >= (uint)y ? 1 : 0); + break; + case Comparison.LessUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x < (uint)y ? 1 : 0); + break; + } + } + 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 op) + { + int x = operation.GetSource(0).AsInt32(); + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateUnaryI64(Operation operation, Func op) + { + long x = operation.GetSource(0).AsInt64(); + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateBinaryI32(Operation operation, Func 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 op) + { + long x = operation.GetSource(0).AsInt64(); + long y = operation.GetSource(1).AsInt64(); + + operation.TurnIntoCopy(Const(op(x, y))); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs b/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs new file mode 100644 index 000000000..1afc3a782 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs @@ -0,0 +1,252 @@ +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 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 (DestIsSingleLocalVar(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 bool PropagateCompare(ref Span 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 uses = dest.GetUses(ref buffer); + + 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 buffer, Operation copyOp) + { + // Propagate copy source operand to all uses of the destination operand. + Operand dest = copyOp.Destination; + Operand source = copyOp.GetSource(0); + + Span uses = dest.GetUses(ref buffer); + + 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 DestIsSingleLocalVar(node) && node.Destination.UsesCount == 0 && !HasSideEffects(node); + } + + private static bool DestIsSingleLocalVar(Operation node) + { + return node.DestinationsCount == 1 && 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; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs b/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs new file mode 100644 index 000000000..53a7f3ede --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs @@ -0,0 +1,182 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class Simplification + { + public static void RunPass(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + if (operation.GetSource(0).Relocatable || + operation.GetSource(1).Relocatable) + { + break; + } + + TryEliminateBinaryOpComutative(operation, 0); + break; + + case Instruction.BitwiseAnd: + TryEliminateBitwiseAnd(operation); + break; + + case Instruction.BitwiseOr: + TryEliminateBitwiseOr(operation); + break; + + case Instruction.BitwiseExclusiveOr: + TryEliminateBitwiseExclusiveOr(operation); + break; + + case Instruction.ConditionalSelect: + TryEliminateConditionalSelect(operation); + break; + + case Instruction.Divide: + TryEliminateBinaryOpY(operation, 1); + break; + + case Instruction.Multiply: + TryEliminateBinaryOpComutative(operation, 1); + break; + + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.Subtract: + TryEliminateBinaryOpY(operation, 0); + break; + } + } + + private static void TryEliminateBitwiseAnd(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y, + // x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000 + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, AllOnes(x.Type))) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, AllOnes(y.Type))) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, 0) || IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(Const(x.Type, 0)); + } + } + + private static void TryEliminateBitwiseOr(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x | 0x00000000 == x, 0x00000000 | y == y, + // x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, 0)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, AllOnes(x.Type)) || IsConstEqual(y, AllOnes(y.Type))) + { + operation.TurnIntoCopy(Const(AllOnes(x.Type))); + } + } + + private static void TryEliminateBitwiseExclusiveOr(Operation operation) + { + // Try to recognize and optimize those 2 patterns (in order): + // x ^ y == 0x00000000 when x == y + // 0x00000000 ^ y == y, x ^ 0x00000000 == x + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (x == y && x.Type.IsInteger()) + { + operation.TurnIntoCopy(Const(x.Type, 0)); + } + else + { + TryEliminateBinaryOpComutative(operation, 0); + } + } + + private static void TryEliminateBinaryOpY(Operation operation, ulong comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateBinaryOpComutative(Operation operation, ulong comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, comparand)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateConditionalSelect(Operation operation) + { + Operand cond = operation.GetSource(0); + + if (cond.Kind != OperandKind.Constant) + { + return; + } + + // The condition is constant, we can turn it into a copy, and select + // the source based on the condition value. + int srcIndex = cond.Value != 0 ? 1 : 2; + + Operand source = operation.GetSource(srcIndex); + + operation.TurnIntoCopy(source); + } + + private static bool IsConstEqual(Operand operand, ulong comparand) + { + if (operand.Kind != OperandKind.Constant || !operand.Type.IsInteger()) + { + return false; + } + + return operand.Value == comparand; + } + + private static ulong AllOnes(OperandType type) + { + return type switch + { + OperandType.I32 => ~0U, + OperandType.I64 => ~0UL, + _ => throw new ArgumentException("Invalid operand type \"" + type + "\"."), + }; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs b/src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs new file mode 100644 index 000000000..e63c4da0d --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs @@ -0,0 +1,83 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class TailMerge + { + public static void RunPass(in CompilerContext cctx) + { + ControlFlowGraph cfg = cctx.Cfg; + + BasicBlock mergedReturn = new(cfg.Blocks.Count); + + Operand returnValue; + Operation returnOp; + + if (cctx.FuncReturnType == OperandType.None) + { + returnValue = default; + returnOp = Operation(Instruction.Return, default); + } + else + { + returnValue = cfg.AllocateLocal(cctx.FuncReturnType); + returnOp = Operation(Instruction.Return, default, returnValue); + } + + mergedReturn.Frequency = BasicBlockFrequency.Cold; + mergedReturn.Operations.AddLast(returnOp); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + Operation op = block.Operations.Last; + + if (op != default && op.Instruction == Instruction.Return) + { + block.Operations.Remove(op); + + if (cctx.FuncReturnType == OperandType.None) + { + PrepareMerge(block, mergedReturn); + } + else + { + Operation copyOp = Operation(Instruction.Copy, returnValue, op.GetSource(0)); + + PrepareMerge(block, mergedReturn).Append(copyOp); + } + } + } + + cfg.Blocks.AddLast(mergedReturn); + cfg.Update(); + } + + private static BasicBlock PrepareMerge(BasicBlock from, BasicBlock to) + { + BasicBlock fromPred = from.Predecessors.Count == 1 ? from.Predecessors[0] : null; + + // If the block is empty, we can try to append to the predecessor and avoid unnecessary jumps. + if (from.Operations.Count == 0 && fromPred != null && fromPred.SuccessorsCount == 1) + { + for (int i = 0; i < fromPred.SuccessorsCount; i++) + { + if (fromPred.GetSuccessor(i) == from) + { + fromPred.SetSuccessor(i, to); + } + } + + // NOTE: `from` becomes unreachable and the call to `cfg.Update()` will remove it. + return fromPred; + } + else + { + from.AddSuccessor(to); + + return from; + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/PreAllocatorCommon.cs b/src/ARMeilleure/CodeGen/PreAllocatorCommon.cs new file mode 100644 index 000000000..53f279fb9 --- /dev/null +++ b/src/ARMeilleure/CodeGen/PreAllocatorCommon.cs @@ -0,0 +1,57 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen +{ + static class PreAllocatorCommon + { + public static void Propagate(ref Span buffer, Operand dest, Operand value) + { + ReadOnlySpan uses = dest.GetUses(ref buffer); + + foreach (Operation use in uses) + { + for (int srcIndex = 0; srcIndex < use.SourcesCount; srcIndex++) + { + Operand useSrc = use.GetSource(srcIndex); + + if (useSrc == dest) + { + use.SetSource(srcIndex, value); + } + else if (useSrc.Kind == OperandKind.Memory) + { + MemoryOperand memoryOp = useSrc.GetMemory(); + + Operand baseAddr = memoryOp.BaseAddress; + Operand index = memoryOp.Index; + bool changed = false; + + if (baseAddr == dest) + { + baseAddr = value; + changed = true; + } + + if (index == dest) + { + index = value; + changed = true; + } + + if (changed) + { + use.SetSource(srcIndex, MemoryOp( + useSrc.Type, + baseAddr, + index, + memoryOp.Scale, + memoryOp.Displacement)); + } + } + } + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs new file mode 100644 index 000000000..7b9c2f77f --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + readonly struct AllocationResult + { + public int IntUsedRegisters { get; } + public int VecUsedRegisters { get; } + public int SpillRegionSize { get; } + + public AllocationResult( + int intUsedRegisters, + int vecUsedRegisters, + int spillRegionSize) + { + IntUsedRegisters = intUsedRegisters; + VecUsedRegisters = vecUsedRegisters; + SpillRegionSize = spillRegionSize; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs new file mode 100644 index 000000000..af10330ba --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs @@ -0,0 +1,249 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class CopyResolver + { + private class ParallelCopy + { + private readonly struct Copy + { + public Register Dest { get; } + public Register Source { get; } + + public OperandType Type { get; } + + public Copy(Register dest, Register source, OperandType type) + { + Dest = dest; + Source = source; + Type = type; + } + } + + private readonly List _copies; + + public int Count => _copies.Count; + + public ParallelCopy() + { + _copies = new List(); + } + + public void AddCopy(Register dest, Register source, OperandType type) + { + _copies.Add(new Copy(dest, source, type)); + } + + public void Sequence(List sequence) + { + Dictionary locations = new(); + Dictionary sources = new(); + + Dictionary types = new(); + + Queue pendingQueue = new(); + Queue readyQueue = new(); + + foreach (Copy copy in _copies) + { + locations[copy.Source] = copy.Source; + sources[copy.Dest] = copy.Source; + types[copy.Dest] = copy.Type; + + pendingQueue.Enqueue(copy.Dest); + } + + foreach (Copy copy in _copies) + { + // If the destination is not used anywhere, we can assign it immediately. + if (!locations.ContainsKey(copy.Dest)) + { + readyQueue.Enqueue(copy.Dest); + } + } + + while (pendingQueue.TryDequeue(out Register current)) + { + Register copyDest; + Register origSource; + Register copySource; + + while (readyQueue.TryDequeue(out copyDest)) + { + origSource = sources[copyDest]; + copySource = locations[origSource]; + + OperandType type = types[copyDest]; + + EmitCopy(sequence, GetRegister(copyDest, type), GetRegister(copySource, type)); + + locations[origSource] = copyDest; + + if (origSource == copySource && sources.ContainsKey(origSource)) + { + readyQueue.Enqueue(origSource); + } + } + + copyDest = current; + origSource = sources[copyDest]; + copySource = locations[origSource]; + + if (copyDest != copySource) + { + OperandType type = types[copyDest]; + + type = type.IsInteger() ? OperandType.I64 : OperandType.V128; + + EmitXorSwap(sequence, GetRegister(copyDest, type), GetRegister(copySource, type)); + + locations[origSource] = copyDest; + + Register swapOther = copySource; + + if (copyDest != locations[sources[copySource]]) + { + // Find the other swap destination register. + // To do that, we search all the pending registers, and pick + // the one where the copy source register is equal to the + // current destination register being processed (copyDest). + foreach (Register pending in pendingQueue) + { + // Is this a copy of pending <- copyDest? + if (copyDest == locations[sources[pending]]) + { + swapOther = pending; + + break; + } + } + } + + // The value that was previously at "copyDest" now lives on + // "copySource" thanks to the swap, now we need to update the + // location for the next copy that is supposed to copy the value + // that used to live on "copyDest". + locations[sources[swapOther]] = copySource; + } + } + } + + private static void EmitCopy(List sequence, Operand x, Operand y) + { + sequence.Add(Operation(Instruction.Copy, x, y)); + } + + private static void EmitXorSwap(List sequence, Operand x, Operand y) + { + sequence.Add(Operation(Instruction.BitwiseExclusiveOr, x, x, y)); + sequence.Add(Operation(Instruction.BitwiseExclusiveOr, y, y, x)); + sequence.Add(Operation(Instruction.BitwiseExclusiveOr, x, x, y)); + } + } + + private Queue _fillQueue = null; + private Queue _spillQueue = null; + private ParallelCopy _parallelCopy = null; + + public bool HasCopy { get; private set; } + + public void AddSplit(LiveInterval left, LiveInterval right) + { + if (left.Local != right.Local) + { + throw new ArgumentException("Intervals of different variables are not allowed."); + } + + OperandType type = left.Local.Type; + + if (left.IsSpilled && !right.IsSpilled) + { + // Move from the stack to a register. + AddSplitFill(left, right, type); + } + else if (!left.IsSpilled && right.IsSpilled) + { + // Move from a register to the stack. + AddSplitSpill(left, right, type); + } + else if (!left.IsSpilled && !right.IsSpilled && left.Register != right.Register) + { + // Move from one register to another. + AddSplitCopy(left, right, type); + } + else if (left.SpillOffset != right.SpillOffset) + { + // This would be the stack-to-stack move case, but this is not supported. + throw new ArgumentException("Both intervals were spilled."); + } + } + + private void AddSplitFill(LiveInterval left, LiveInterval right, OperandType type) + { + _fillQueue ??= new Queue(); + + Operand register = GetRegister(right.Register, type); + Operand offset = Const(left.SpillOffset); + + _fillQueue.Enqueue(Operation(Instruction.Fill, register, offset)); + + HasCopy = true; + } + + private void AddSplitSpill(LiveInterval left, LiveInterval right, OperandType type) + { + _spillQueue ??= new Queue(); + + Operand offset = Const(right.SpillOffset); + Operand register = GetRegister(left.Register, type); + + _spillQueue.Enqueue(Operation(Instruction.Spill, default, offset, register)); + + HasCopy = true; + } + + private void AddSplitCopy(LiveInterval left, LiveInterval right, OperandType type) + { + _parallelCopy ??= new ParallelCopy(); + + _parallelCopy.AddCopy(right.Register, left.Register, type); + + HasCopy = true; + } + + public Operation[] Sequence() + { + List sequence = new(); + + if (_spillQueue != null) + { + while (_spillQueue.TryDequeue(out Operation spillOp)) + { + sequence.Add(spillOp); + } + } + + _parallelCopy?.Sequence(sequence); + + if (_fillQueue != null) + { + while (_fillQueue.TryDequeue(out Operation fillOp)) + { + sequence.Add(fillOp); + } + } + + return sequence.ToArray(); + } + + private static Operand GetRegister(Register reg, OperandType type) + { + return Register(reg.Index, reg.Type, type); + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs new file mode 100644 index 000000000..5f1d6ce89 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs @@ -0,0 +1,454 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class HybridAllocator : IRegisterAllocator + { + private readonly struct BlockInfo + { + public bool HasCall { get; } + + public int IntFixedRegisters { get; } + public int VecFixedRegisters { get; } + + public BlockInfo(bool hasCall, int intFixedRegisters, int vecFixedRegisters) + { + HasCall = hasCall; + IntFixedRegisters = intFixedRegisters; + VecFixedRegisters = vecFixedRegisters; + } + } + + private struct LocalInfo + { + public int Uses { get; set; } + public int UsesAllocated { get; set; } + public int Sequence { get; set; } + public Operand Temp { get; set; } + public Operand Register { get; set; } + public Operand SpillOffset { get; set; } + public OperandType Type { get; } + + private int _first; + private int _last; + + public readonly bool IsBlockLocal => _first == _last; + + public LocalInfo(OperandType type, int uses, int blkIndex) + { + Uses = uses; + Type = type; + + UsesAllocated = 0; + Sequence = 0; + Temp = default; + Register = default; + SpillOffset = default; + + _first = -1; + _last = -1; + + SetBlockIndex(blkIndex); + } + + public void SetBlockIndex(int blkIndex) + { + if (_first == -1 || blkIndex < _first) + { + _first = blkIndex; + } + + if (_last == -1 || blkIndex > _last) + { + _last = blkIndex; + } + } + } + + private const int MaxIROperands = 4; + // The "visited" state is stored in the MSB of the local's value. + private const ulong VisitedMask = 1ul << 63; + + private BlockInfo[] _blockInfo; + private LocalInfo[] _localInfo; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsVisited(Operand local) + { + Debug.Assert(local.Kind == OperandKind.LocalVariable); + + return (local.GetValueUnsafe() & VisitedMask) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetVisited(Operand local) + { + Debug.Assert(local.Kind == OperandKind.LocalVariable); + + local.GetValueUnsafe() |= VisitedMask; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref LocalInfo GetLocalInfo(Operand local) + { + Debug.Assert(local.Kind == OperandKind.LocalVariable); + Debug.Assert(IsVisited(local), "Local variable not visited. Used before defined?"); + + return ref _localInfo[(uint)local.GetValueUnsafe() - 1]; + } + + public AllocationResult RunPass(ControlFlowGraph cfg, StackAllocator stackAlloc, RegisterMasks regMasks) + { + int intUsedRegisters = 0; + int vecUsedRegisters = 0; + + int intFreeRegisters = regMasks.IntAvailableRegisters; + int vecFreeRegisters = regMasks.VecAvailableRegisters; + + _blockInfo = new BlockInfo[cfg.Blocks.Count]; + _localInfo = new LocalInfo[cfg.Blocks.Count * 3]; + + int localInfoCount = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + int intFixedRegisters = 0; + int vecFixedRegisters = 0; + + bool hasCall = false; + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + if (node.Instruction == Instruction.Call) + { + hasCall = true; + } + + foreach (Operand source in node.SourcesUnsafe) + { + if (source.Kind == OperandKind.LocalVariable) + { + GetLocalInfo(source).SetBlockIndex(block.Index); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + GetLocalInfo(memOp.BaseAddress).SetBlockIndex(block.Index); + } + + if (memOp.Index != default) + { + GetLocalInfo(memOp.Index).SetBlockIndex(block.Index); + } + } + } + + foreach (Operand dest in node.DestinationsUnsafe) + { + if (dest.Kind == OperandKind.LocalVariable) + { + if (IsVisited(dest)) + { + GetLocalInfo(dest).SetBlockIndex(block.Index); + } + else + { + dest.NumberLocal(++localInfoCount); + + if (localInfoCount > _localInfo.Length) + { + Array.Resize(ref _localInfo, localInfoCount * 2); + } + + SetVisited(dest); + GetLocalInfo(dest) = new LocalInfo(dest.Type, UsesCount(dest), block.Index); + } + } + else if (dest.Kind == OperandKind.Register) + { + if (dest.Type.IsInteger()) + { + intFixedRegisters |= 1 << dest.GetRegister().Index; + } + else + { + vecFixedRegisters |= 1 << dest.GetRegister().Index; + } + } + } + } + + _blockInfo[block.Index] = new BlockInfo(hasCall, intFixedRegisters, vecFixedRegisters); + } + + int sequence = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + ref BlockInfo blkInfo = ref _blockInfo[block.Index]; + + int intLocalFreeRegisters = intFreeRegisters & ~blkInfo.IntFixedRegisters; + int vecLocalFreeRegisters = vecFreeRegisters & ~blkInfo.VecFixedRegisters; + + int intCallerSavedRegisters = blkInfo.HasCall ? regMasks.IntCallerSavedRegisters : 0; + int vecCallerSavedRegisters = blkInfo.HasCall ? regMasks.VecCallerSavedRegisters : 0; + + int intSpillTempRegisters = SelectSpillTemps( + intCallerSavedRegisters & ~blkInfo.IntFixedRegisters, + intLocalFreeRegisters); + int vecSpillTempRegisters = SelectSpillTemps( + vecCallerSavedRegisters & ~blkInfo.VecFixedRegisters, + vecLocalFreeRegisters); + + intLocalFreeRegisters &= ~(intSpillTempRegisters | intCallerSavedRegisters); + vecLocalFreeRegisters &= ~(vecSpillTempRegisters | vecCallerSavedRegisters); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + int intLocalUse = 0; + int vecLocalUse = 0; + + Operand AllocateRegister(Operand local) + { + ref LocalInfo info = ref GetLocalInfo(local); + + info.UsesAllocated++; + + Debug.Assert(info.UsesAllocated <= info.Uses); + + if (info.Register != default) + { + if (info.UsesAllocated == info.Uses) + { + Register reg = info.Register.GetRegister(); + + if (local.Type.IsInteger()) + { + intLocalFreeRegisters |= 1 << reg.Index; + } + else + { + vecLocalFreeRegisters |= 1 << reg.Index; + } + } + + return info.Register; + } + else + { + Operand temp = info.Temp; + + if (temp == default || info.Sequence != sequence) + { + temp = local.Type.IsInteger() + ? GetSpillTemp(local, intSpillTempRegisters, ref intLocalUse) + : GetSpillTemp(local, vecSpillTempRegisters, ref vecLocalUse); + + info.Sequence = sequence; + info.Temp = temp; + } + + Operation fillOp = Operation(Instruction.Fill, temp, info.SpillOffset); + + block.Operations.AddBefore(node, fillOp); + + return temp; + } + } + + bool folded = false; + + // If operation is a copy of a local and that local is living on the stack, we turn the copy into + // a fill, instead of inserting a fill before it. + if (node.Instruction == Instruction.Copy) + { + Operand source = node.GetSource(0); + + if (source.Kind == OperandKind.LocalVariable) + { + ref LocalInfo info = ref GetLocalInfo(source); + + if (info.Register == default) + { + Operation fillOp = Operation(Instruction.Fill, node.Destination, info.SpillOffset); + + block.Operations.AddBefore(node, fillOp); + block.Operations.Remove(node); + + node = fillOp; + + folded = true; + } + } + } + + if (!folded) + { + foreach (ref Operand source in node.SourcesUnsafe) + { + if (source.Kind == OperandKind.LocalVariable) + { + source = AllocateRegister(source); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + memOp.BaseAddress = AllocateRegister(memOp.BaseAddress); + } + + if (memOp.Index != default) + { + memOp.Index = AllocateRegister(memOp.Index); + } + } + } + } + + int intLocalAsg = 0; + int vecLocalAsg = 0; + + foreach (ref Operand dest in node.DestinationsUnsafe) + { + if (dest.Kind != OperandKind.LocalVariable) + { + continue; + } + + ref LocalInfo info = ref GetLocalInfo(dest); + + if (info.UsesAllocated == 0) + { + int mask = dest.Type.IsInteger() + ? intLocalFreeRegisters + : vecLocalFreeRegisters; + + if (info.IsBlockLocal && mask != 0) + { + int selectedReg = BitOperations.TrailingZeroCount(mask); + + info.Register = Register(selectedReg, info.Type.ToRegisterType(), info.Type); + + if (dest.Type.IsInteger()) + { + intLocalFreeRegisters &= ~(1 << selectedReg); + intUsedRegisters |= 1 << selectedReg; + } + else + { + vecLocalFreeRegisters &= ~(1 << selectedReg); + vecUsedRegisters |= 1 << selectedReg; + } + } + else + { + info.Register = default; + info.SpillOffset = Const(stackAlloc.Allocate(dest.Type.GetSizeInBytes())); + } + } + + info.UsesAllocated++; + + Debug.Assert(info.UsesAllocated <= info.Uses); + + if (info.Register != default) + { + dest = info.Register; + } + else + { + Operand temp = info.Temp; + + if (temp == default || info.Sequence != sequence) + { + temp = dest.Type.IsInteger() + ? GetSpillTemp(dest, intSpillTempRegisters, ref intLocalAsg) + : GetSpillTemp(dest, vecSpillTempRegisters, ref vecLocalAsg); + + info.Sequence = sequence; + info.Temp = temp; + } + + dest = temp; + + Operation spillOp = Operation(Instruction.Spill, default, info.SpillOffset, temp); + + block.Operations.AddAfter(node, spillOp); + + node = spillOp; + } + } + + sequence++; + + intUsedRegisters |= intLocalAsg | intLocalUse; + vecUsedRegisters |= vecLocalAsg | vecLocalUse; + } + } + + return new AllocationResult(intUsedRegisters, vecUsedRegisters, stackAlloc.TotalSize); + } + + private static int SelectSpillTemps(int mask0, int mask1) + { + int selection = 0; + int count = 0; + + while (count < MaxIROperands && mask0 != 0) + { + int mask = mask0 & -mask0; + + selection |= mask; + + mask0 &= ~mask; + + count++; + } + + while (count < MaxIROperands && mask1 != 0) + { + int mask = mask1 & -mask1; + + selection |= mask; + + mask1 &= ~mask; + + count++; + } + + Debug.Assert(count == MaxIROperands, "No enough registers for spill temps."); + + return selection; + } + + private static Operand GetSpillTemp(Operand local, int freeMask, ref int useMask) + { + int selectedReg = BitOperations.TrailingZeroCount(freeMask & ~useMask); + + useMask |= 1 << selectedReg; + + return Register(selectedReg, local.Type.ToRegisterType(), local.Type); + } + + private static int UsesCount(Operand local) + { + return local.AssignmentsCount + local.UsesCount; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs new file mode 100644 index 000000000..7d4ce2ea6 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs @@ -0,0 +1,12 @@ +using ARMeilleure.Translation; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + interface IRegisterAllocator + { + AllocationResult RunPass( + ControlFlowGraph cfg, + StackAllocator stackAlloc, + RegisterMasks regMasks); + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs new file mode 100644 index 000000000..f156e0886 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs @@ -0,0 +1,1098 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + // Based on: + // "Linear Scan Register Allocation for the Java(tm) HotSpot Client Compiler". + // http://www.christianwimmer.at/Publications/Wimmer04a/Wimmer04a.pdf + class LinearScanAllocator : IRegisterAllocator + { + private const int InstructionGap = 2; + private const int InstructionGapMask = InstructionGap - 1; + + private HashSet _blockEdges; + private LiveRange[] _blockRanges; + private BitMap[] _blockLiveIn; + + private List _intervals; + private LiveInterval[] _parentIntervals; + + private List<(IntrusiveList, Operation)> _operationNodes; + private int _operationsCount; + + private class AllocationContext + { + public RegisterMasks Masks { get; } + + public StackAllocator StackAlloc { get; } + + public BitMap Active { get; } + public BitMap Inactive { get; } + + public int IntUsedRegisters { get; set; } + public int VecUsedRegisters { get; set; } + + private readonly int[] _intFreePositions; + private readonly int[] _vecFreePositions; + private readonly int _intFreePositionsCount; + private readonly int _vecFreePositionsCount; + + public AllocationContext(StackAllocator stackAlloc, RegisterMasks masks, int intervalsCount) + { + StackAlloc = stackAlloc; + Masks = masks; + + Active = new BitMap(Allocators.Default, intervalsCount); + Inactive = new BitMap(Allocators.Default, intervalsCount); + + PopulateFreePositions(RegisterType.Integer, out _intFreePositions, out _intFreePositionsCount); + PopulateFreePositions(RegisterType.Vector, out _vecFreePositions, out _vecFreePositionsCount); + + void PopulateFreePositions(RegisterType type, out int[] positions, out int count) + { + positions = new int[masks.RegistersCount]; + count = BitOperations.PopCount((uint)masks.GetAvailableRegisters(type)); + + int mask = masks.GetAvailableRegisters(type); + + for (int i = 0; i < positions.Length; i++) + { + if ((mask & (1 << i)) != 0) + { + positions[i] = int.MaxValue; + } + } + } + } + + public void GetFreePositions(RegisterType type, in Span positions, out int count) + { + if (type == RegisterType.Integer) + { + _intFreePositions.CopyTo(positions); + + count = _intFreePositionsCount; + } + else + { + Debug.Assert(type == RegisterType.Vector); + + _vecFreePositions.CopyTo(positions); + + count = _vecFreePositionsCount; + } + } + + public void MoveActiveToInactive(int bit) + { + Move(Active, Inactive, bit); + } + + public void MoveInactiveToActive(int bit) + { + Move(Inactive, Active, bit); + } + + private static void Move(BitMap source, BitMap dest, int bit) + { + source.Clear(bit); + + dest.Set(bit); + } + } + + public AllocationResult RunPass( + ControlFlowGraph cfg, + StackAllocator stackAlloc, + RegisterMasks regMasks) + { + NumberLocals(cfg, regMasks.RegistersCount); + + var context = new AllocationContext(stackAlloc, regMasks, _intervals.Count); + + BuildIntervals(cfg, context); + + for (int index = 0; index < _intervals.Count; index++) + { + LiveInterval current = _intervals[index]; + + if (current.IsEmpty) + { + continue; + } + + if (current.IsFixed) + { + context.Active.Set(index); + + if (current.IsFixedAndUsed) + { + if (current.Register.Type == RegisterType.Integer) + { + context.IntUsedRegisters |= 1 << current.Register.Index; + } + else /* if (interval.Register.Type == RegisterType.Vector) */ + { + context.VecUsedRegisters |= 1 << current.Register.Index; + } + } + + continue; + } + + AllocateInterval(context, current, index, regMasks.RegistersCount); + } + + for (int index = regMasks.RegistersCount * 2; index < _intervals.Count; index++) + { + if (!_intervals[index].IsSpilled) + { + ReplaceLocalWithRegister(_intervals[index]); + } + } + + InsertSplitCopies(); + InsertSplitCopiesAtEdges(cfg); + + return new AllocationResult(context.IntUsedRegisters, context.VecUsedRegisters, context.StackAlloc.TotalSize); + } + + private void AllocateInterval(AllocationContext context, LiveInterval current, int cIndex, int registersCount) + { + // Check active intervals that already ended. + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + interval.Forward(current.GetStart()); + + if (interval.GetEnd() < current.GetStart()) + { + context.Active.Clear(iIndex); + } + else if (!interval.Overlaps(current.GetStart())) + { + context.MoveActiveToInactive(iIndex); + } + } + + // Check inactive intervals that already ended or were reactivated. + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + + interval.Forward(current.GetStart()); + + if (interval.GetEnd() < current.GetStart()) + { + context.Inactive.Clear(iIndex); + } + else if (interval.Overlaps(current.GetStart())) + { + context.MoveInactiveToActive(iIndex); + } + } + + if (!TryAllocateRegWithoutSpill(context, current, cIndex, registersCount)) + { + AllocateRegWithSpill(context, current, cIndex, registersCount); + } + } + + private bool TryAllocateRegWithoutSpill(AllocationContext context, LiveInterval current, int cIndex, int registersCount) + { + RegisterType regType = current.Local.Type.ToRegisterType(); + + Span freePositions = stackalloc int[registersCount]; + + context.GetFreePositions(regType, freePositions, out int freePositionsCount); + + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + if (reg.Type == regType) + { + freePositions[reg.Index] = 0; + freePositionsCount--; + } + } + + // If all registers are already active, return early. No point in inspecting the inactive set to look for + // holes. + if (freePositionsCount == 0) + { + return false; + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + ref int freePosition = ref freePositions[reg.Index]; + + if (reg.Type == regType && freePosition != 0) + { + int overlapPosition = interval.GetOverlapPosition(current); + + if (overlapPosition != LiveInterval.NotFound && freePosition > overlapPosition) + { + freePosition = overlapPosition; + } + } + } + + int selectedReg = GetHighestValueIndex(freePositions); + int selectedNextUse = freePositions[selectedReg]; + + // Intervals starts and ends at odd positions, unless they span an entire + // block, in this case they will have ranges at a even position. + // When a interval is loaded from the stack to a register, we can only + // do the split at a odd position, because otherwise the split interval + // that is inserted on the list to be processed may clobber a register + // used by the instruction at the same position as the split. + // The problem only happens when a interval ends exactly at this instruction, + // because otherwise they would interfere, and the register wouldn't be selected. + // When the interval is aligned and the above happens, there's no problem as + // the instruction that is actually with the last use is the one + // before that position. + selectedNextUse &= ~InstructionGapMask; + + if (selectedNextUse <= current.GetStart()) + { + return false; + } + else if (selectedNextUse < current.GetEnd()) + { + LiveInterval splitChild = current.Split(selectedNextUse); + + if (splitChild.UsesCount != 0) + { + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild, registersCount); + } + else + { + Spill(context, splitChild); + } + } + + current.Register = new Register(selectedReg, regType); + + if (regType == RegisterType.Integer) + { + context.IntUsedRegisters |= 1 << selectedReg; + } + else /* if (regType == RegisterType.Vector) */ + { + context.VecUsedRegisters |= 1 << selectedReg; + } + + context.Active.Set(cIndex); + + return true; + } + + private void AllocateRegWithSpill(AllocationContext context, LiveInterval current, int cIndex, int registersCount) + { + RegisterType regType = current.Local.Type.ToRegisterType(); + + Span usePositions = stackalloc int[registersCount]; + Span blockedPositions = stackalloc int[registersCount]; + + context.GetFreePositions(regType, usePositions, out _); + context.GetFreePositions(regType, blockedPositions, out _); + + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + if (reg.Type == regType) + { + ref int usePosition = ref usePositions[reg.Index]; + ref int blockedPosition = ref blockedPositions[reg.Index]; + + if (interval.IsFixed) + { + usePosition = 0; + blockedPosition = 0; + } + else + { + int nextUse = interval.NextUseAfter(current.GetStart()); + + if (nextUse != LiveInterval.NotFound && usePosition > nextUse) + { + usePosition = nextUse; + } + } + } + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + if (reg.Type == regType) + { + ref int usePosition = ref usePositions[reg.Index]; + ref int blockedPosition = ref blockedPositions[reg.Index]; + + if (interval.IsFixed) + { + int overlapPosition = interval.GetOverlapPosition(current); + + if (overlapPosition != LiveInterval.NotFound) + { + blockedPosition = Math.Min(blockedPosition, overlapPosition); + usePosition = Math.Min(usePosition, overlapPosition); + } + } + else if (interval.Overlaps(current)) + { + int nextUse = interval.NextUseAfter(current.GetStart()); + + if (nextUse != LiveInterval.NotFound && usePosition > nextUse) + { + usePosition = nextUse; + } + } + } + } + + int selectedReg = GetHighestValueIndex(usePositions); + int currentFirstUse = current.FirstUse(); + + Debug.Assert(currentFirstUse >= 0, "Current interval has no uses."); + + if (usePositions[selectedReg] < currentFirstUse) + { + // All intervals on inactive and active are being used before current, + // so spill the current interval. + Debug.Assert(currentFirstUse > current.GetStart(), "Trying to spill a interval currently being used."); + + LiveInterval splitChild = current.Split(currentFirstUse); + + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild, registersCount); + + Spill(context, current); + } + else if (blockedPositions[selectedReg] > current.GetEnd()) + { + // Spill made the register available for the entire current lifetime, + // so we only need to split the intervals using the selected register. + current.Register = new Register(selectedReg, regType); + + SplitAndSpillOverlappingIntervals(context, current, registersCount); + + context.Active.Set(cIndex); + } + else + { + // There are conflicts even after spill due to the use of fixed registers + // that can't be spilled, so we need to also split current at the point of + // the first fixed register use. + current.Register = new Register(selectedReg, regType); + + int splitPosition = blockedPositions[selectedReg] & ~InstructionGapMask; + + Debug.Assert(splitPosition > current.GetStart(), "Trying to split a interval at a invalid position."); + + LiveInterval splitChild = current.Split(splitPosition); + + if (splitChild.UsesCount != 0) + { + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild, registersCount); + } + else + { + Spill(context, splitChild); + } + + SplitAndSpillOverlappingIntervals(context, current, registersCount); + + context.Active.Set(cIndex); + } + } + + private static int GetHighestValueIndex(Span span) + { + int highest = int.MinValue; + + int selected = 0; + + for (int index = 0; index < span.Length; index++) + { + int current = span[index]; + + if (highest < current) + { + highest = current; + selected = index; + + if (current == int.MaxValue) + { + break; + } + } + } + + return selected; + } + + private void SplitAndSpillOverlappingIntervals(AllocationContext context, LiveInterval current, int registersCount) + { + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + if (!interval.IsFixed && interval.Register == current.Register) + { + SplitAndSpillOverlappingInterval(context, current, interval, registersCount); + + context.Active.Clear(iIndex); + } + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + + if (!interval.IsFixed && interval.Register == current.Register && interval.Overlaps(current)) + { + SplitAndSpillOverlappingInterval(context, current, interval, registersCount); + + context.Inactive.Clear(iIndex); + } + } + } + + private void SplitAndSpillOverlappingInterval( + AllocationContext context, + LiveInterval current, + LiveInterval interval, + int registersCount) + { + // If there's a next use after the start of the current interval, + // we need to split the spilled interval twice, and re-insert it + // on the "pending" list to ensure that it will get a new register + // on that use position. + int nextUse = interval.NextUseAfter(current.GetStart()); + + LiveInterval splitChild; + + if (interval.GetStart() < current.GetStart()) + { + splitChild = interval.Split(current.GetStart()); + } + else + { + splitChild = interval; + } + + if (nextUse != -1) + { + Debug.Assert(nextUse > current.GetStart(), "Trying to spill a interval currently being used."); + + if (nextUse > splitChild.GetStart()) + { + LiveInterval right = splitChild.Split(nextUse); + + Spill(context, splitChild); + + splitChild = right; + } + + InsertInterval(splitChild, registersCount); + } + else + { + Spill(context, splitChild); + } + } + + private void InsertInterval(LiveInterval interval, int registersCount) + { + Debug.Assert(interval.UsesCount != 0, "Trying to insert a interval without uses."); + Debug.Assert(!interval.IsEmpty, "Trying to insert a empty interval."); + Debug.Assert(!interval.IsSpilled, "Trying to insert a spilled interval."); + + int startIndex = registersCount * 2; + + int insertIndex = _intervals.BinarySearch(startIndex, _intervals.Count - startIndex, interval, null); + + if (insertIndex < 0) + { + insertIndex = ~insertIndex; + } + + _intervals.Insert(insertIndex, interval); + } + + private static void Spill(AllocationContext context, LiveInterval interval) + { + Debug.Assert(!interval.IsFixed, "Trying to spill a fixed interval."); + Debug.Assert(interval.UsesCount == 0, "Trying to spill a interval with uses."); + + // We first check if any of the siblings were spilled, if so we can reuse + // the stack offset. Otherwise, we allocate a new space on the stack. + // This prevents stack-to-stack copies being necessary for a split interval. + if (!interval.TrySpillWithSiblingOffset()) + { + interval.Spill(context.StackAlloc.Allocate(interval.Local.Type)); + } + } + + private void InsertSplitCopies() + { + Dictionary copyResolvers = new(); + + CopyResolver GetCopyResolver(int position) + { + if (!copyResolvers.TryGetValue(position, out CopyResolver copyResolver)) + { + copyResolver = new CopyResolver(); + + copyResolvers.Add(position, copyResolver); + } + + return copyResolver; + } + + foreach (LiveInterval interval in _intervals.Where(x => x.IsSplit)) + { + LiveInterval previous = interval; + + foreach (LiveInterval splitChild in interval.SplitChildren()) + { + int splitPosition = splitChild.GetStart(); + + if (!_blockEdges.Contains(splitPosition) && previous.GetEnd() == splitPosition) + { + GetCopyResolver(splitPosition).AddSplit(previous, splitChild); + } + + previous = splitChild; + } + } + + foreach (KeyValuePair kv in copyResolvers) + { + CopyResolver copyResolver = kv.Value; + + if (!copyResolver.HasCopy) + { + continue; + } + + int splitPosition = kv.Key; + + (IntrusiveList nodes, Operation node) = GetOperationNode(splitPosition); + + Operation[] sequence = copyResolver.Sequence(); + + nodes.AddBefore(node, sequence[0]); + + node = sequence[0]; + + for (int index = 1; index < sequence.Length; index++) + { + nodes.AddAfter(node, sequence[index]); + + node = sequence[index]; + } + } + } + + private void InsertSplitCopiesAtEdges(ControlFlowGraph cfg) + { + int blocksCount = cfg.Blocks.Count; + + bool IsSplitEdgeBlock(BasicBlock block) + { + return block.Index >= blocksCount; + } + + // Reset iterators to beginning because GetSplitChild depends on the state of the iterator. + foreach (LiveInterval interval in _intervals) + { + interval.Reset(); + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + if (IsSplitEdgeBlock(block)) + { + continue; + } + + bool hasSingleOrNoSuccessor = block.SuccessorsCount <= 1; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock successor = block.GetSuccessor(i); + + int succIndex = successor.Index; + + // If the current node is a split node, then the actual successor node + // (the successor before the split) should be right after it. + if (IsSplitEdgeBlock(successor)) + { + succIndex = successor.GetSuccessor(0).Index; + } + + CopyResolver copyResolver = null; + + foreach (int iIndex in _blockLiveIn[succIndex]) + { + LiveInterval interval = _parentIntervals[iIndex]; + + if (!interval.IsSplit) + { + continue; + } + + int lEnd = _blockRanges[block.Index].End - 1; + int rStart = _blockRanges[succIndex].Start; + + LiveInterval left = interval.GetSplitChild(lEnd); + LiveInterval right = interval.GetSplitChild(rStart); + + if (left != default && right != default && left != right) + { + copyResolver ??= new CopyResolver(); + + copyResolver.AddSplit(left, right); + } + } + + if (copyResolver == null || !copyResolver.HasCopy) + { + continue; + } + + Operation[] sequence = copyResolver.Sequence(); + + if (hasSingleOrNoSuccessor) + { + foreach (Operation operation in sequence) + { + block.Append(operation); + } + } + else if (successor.Predecessors.Count == 1) + { + successor.Operations.AddFirst(sequence[0]); + + Operation prependNode = sequence[0]; + + for (int index = 1; index < sequence.Length; index++) + { + Operation operation = sequence[index]; + + successor.Operations.AddAfter(prependNode, operation); + + prependNode = operation; + } + } + else + { + // Split the critical edge. + BasicBlock splitBlock = cfg.SplitEdge(block, successor); + + foreach (Operation operation in sequence) + { + splitBlock.Append(operation); + } + } + } + } + } + + private void ReplaceLocalWithRegister(LiveInterval current) + { + Operand register = GetRegister(current); + + foreach (int usePosition in current.UsePositions()) + { + (_, Operation operation) = GetOperationNode(usePosition); + + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (source == current.Local) + { + operation.SetSource(index, register); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress == current.Local) + { + memOp.BaseAddress = register; + } + + if (memOp.Index == current.Local) + { + memOp.Index = register; + } + } + } + + for (int index = 0; index < operation.DestinationsCount; index++) + { + Operand dest = operation.GetDestination(index); + + if (dest == current.Local) + { + operation.SetDestination(index, register); + } + } + } + } + + private static Operand GetRegister(LiveInterval interval) + { + Debug.Assert(!interval.IsSpilled, "Spilled intervals are not allowed."); + + return Operand.Factory.Register( + interval.Register.Index, + interval.Register.Type, + interval.Local.Type); + } + + private (IntrusiveList, Operation) GetOperationNode(int position) + { + return _operationNodes[position / InstructionGap]; + } + + private void NumberLocals(ControlFlowGraph cfg, int registersCount) + { + _operationNodes = new List<(IntrusiveList, Operation)>(); + _intervals = new List(); + + for (int index = 0; index < registersCount; index++) + { + _intervals.Add(new LiveInterval(new Register(index, RegisterType.Integer))); + _intervals.Add(new LiveInterval(new Register(index, RegisterType.Vector))); + } + + // The "visited" state is stored in the MSB of the local's value. + const ulong VisitedMask = 1ul << 63; + + bool IsVisited(Operand local) + { + return (local.GetValueUnsafe() & VisitedMask) != 0; + } + + void SetVisited(Operand local) + { + local.GetValueUnsafe() |= VisitedMask; + } + + _operationsCount = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + _operationNodes.Add((block.Operations, node)); + + for (int i = 0; i < node.DestinationsCount; i++) + { + Operand dest = node.GetDestination(i); + + if (dest.Kind == OperandKind.LocalVariable && !IsVisited(dest)) + { + dest.NumberLocal(_intervals.Count); + + _intervals.Add(new LiveInterval(dest)); + + SetVisited(dest); + } + } + } + + _operationsCount += block.Operations.Count * InstructionGap; + + if (block.Operations.Count == 0) + { + // Pretend we have a dummy instruction on the empty block. + _operationNodes.Add((default, default)); + + _operationsCount += InstructionGap; + } + } + + _parentIntervals = _intervals.ToArray(); + } + + private void BuildIntervals(ControlFlowGraph cfg, AllocationContext context) + { + _blockRanges = new LiveRange[cfg.Blocks.Count]; + + int mapSize = _intervals.Count; + + BitMap[] blkLiveGen = new BitMap[cfg.Blocks.Count]; + BitMap[] blkLiveKill = new BitMap[cfg.Blocks.Count]; + + // Compute local live sets. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + BitMap liveGen = new(Allocators.Default, mapSize); + BitMap liveKill = new(Allocators.Default, mapSize); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int i = 0; i < node.SourcesCount; i++) + { + VisitSource(node.GetSource(i)); + } + + for (int i = 0; i < node.DestinationsCount; i++) + { + VisitDestination(node.GetDestination(i)); + } + + void VisitSource(Operand source) + { + if (IsLocalOrRegister(source.Kind)) + { + int id = GetOperandId(source); + + if (!liveKill.IsSet(id)) + { + liveGen.Set(id); + } + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + VisitSource(memOp.BaseAddress); + } + + if (memOp.Index != default) + { + VisitSource(memOp.Index); + } + } + } + + void VisitDestination(Operand dest) + { + liveKill.Set(GetOperandId(dest)); + } + } + + blkLiveGen[block.Index] = liveGen; + blkLiveKill[block.Index] = liveKill; + } + + // Compute global live sets. + BitMap[] blkLiveIn = new BitMap[cfg.Blocks.Count]; + BitMap[] blkLiveOut = new BitMap[cfg.Blocks.Count]; + + for (int index = 0; index < cfg.Blocks.Count; index++) + { + blkLiveIn[index] = new BitMap(Allocators.Default, mapSize); + blkLiveOut[index] = new BitMap(Allocators.Default, mapSize); + } + + bool modified; + + do + { + modified = false; + + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + BitMap liveOut = blkLiveOut[block.Index]; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock succ = block.GetSuccessor(i); + + modified |= liveOut.Set(blkLiveIn[succ.Index]); + } + + BitMap liveIn = blkLiveIn[block.Index]; + + liveIn.Set(liveOut); + liveIn.Clear(blkLiveKill[block.Index]); + liveIn.Set(blkLiveGen[block.Index]); + } + } + while (modified); + + _blockLiveIn = blkLiveIn; + + _blockEdges = new HashSet(); + + // Compute lifetime intervals. + int operationPos = _operationsCount; + + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + // We handle empty blocks by pretending they have a dummy instruction, + // because otherwise the block would have the same start and end position, + // and this is not valid. + int instCount = Math.Max(block.Operations.Count, 1); + + int blockStart = operationPos - instCount * InstructionGap; + int blockEnd = operationPos; + + _blockRanges[block.Index] = new LiveRange(blockStart, blockEnd); + + _blockEdges.Add(blockStart); + + BitMap liveOut = blkLiveOut[block.Index]; + + foreach (int id in liveOut) + { + _intervals[id].AddRange(blockStart, blockEnd); + } + + if (block.Operations.Count == 0) + { + operationPos -= InstructionGap; + + continue; + } + + for (Operation node = block.Operations.Last; node != default; node = node.ListPrevious) + { + operationPos -= InstructionGap; + + for (int i = 0; i < node.DestinationsCount; i++) + { + VisitDestination(node.GetDestination(i)); + } + + for (int i = 0; i < node.SourcesCount; i++) + { + VisitSource(node.GetSource(i)); + } + + if (node.Instruction == Instruction.Call) + { + AddIntervalCallerSavedReg(context.Masks.IntCallerSavedRegisters, operationPos, RegisterType.Integer); + AddIntervalCallerSavedReg(context.Masks.VecCallerSavedRegisters, operationPos, RegisterType.Vector); + } + + void VisitSource(Operand source) + { + if (IsLocalOrRegister(source.Kind)) + { + LiveInterval interval = _intervals[GetOperandId(source)]; + + interval.AddRange(blockStart, operationPos + 1); + interval.AddUsePosition(operationPos); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + VisitSource(memOp.BaseAddress); + } + + if (memOp.Index != default) + { + VisitSource(memOp.Index); + } + } + } + + void VisitDestination(Operand dest) + { + LiveInterval interval = _intervals[GetOperandId(dest)]; + + if (interval.IsFixed) + { + interval.IsFixedAndUsed = true; + } + + interval.SetStart(operationPos + 1); + interval.AddUsePosition(operationPos + 1); + } + } + } + + foreach (LiveInterval interval in _parentIntervals) + { + interval.Reset(); + } + } + + private void AddIntervalCallerSavedReg(int mask, int operationPos, RegisterType regType) + { + while (mask != 0) + { + int regIndex = BitOperations.TrailingZeroCount(mask); + + Register callerSavedReg = new(regIndex, regType); + + LiveInterval interval = _intervals[GetRegisterId(callerSavedReg)]; + + interval.AddRange(operationPos + 1, operationPos + InstructionGap); + + mask &= ~(1 << regIndex); + } + } + + private static int GetOperandId(Operand operand) + { + if (operand.Kind == OperandKind.LocalVariable) + { + return operand.GetLocalNumber(); + } + else if (operand.Kind == OperandKind.Register) + { + return GetRegisterId(operand.GetRegister()); + } + else + { + throw new ArgumentException($"Invalid operand kind \"{operand.Kind}\"."); + } + } + + private static int GetRegisterId(Register register) + { + return (register.Index << 1) | (register.Type == RegisterType.Vector ? 1 : 0); + } + + private static bool IsLocalOrRegister(OperandKind kind) + { + return kind == OperandKind.LocalVariable || + kind == OperandKind.Register; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs new file mode 100644 index 000000000..333d3951b --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs @@ -0,0 +1,398 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe readonly struct LiveInterval : IComparable + { + 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; + public bool IsFixedAndUsed; + } + + 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 ref bool IsFixedAndUsed => ref _data->IsFixedAndUsed; + 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 = 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 SplitChildren() + { + return Parent.Children.Span; + } + + public ReadOnlySpan 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) + { + 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 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()); + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs new file mode 100644 index 000000000..84b892f42 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs @@ -0,0 +1,40 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe struct LiveIntervalList + { + private LiveInterval* _items; + private int _count; + private int _capacity; + + public readonly int Count => _count; + public readonly Span Span => new(_items, _count); + + public void Add(LiveInterval interval) + { + if (_count + 1 > _capacity) + { + var oldSpan = Span; + + _capacity = Math.Max(4, _capacity * 2); + _items = Allocators.References.Allocate((uint)_capacity); + + var newSpan = Span; + + oldSpan.CopyTo(newSpan); + } + + int position = interval.GetStart(); + int i = _count - 1; + + while (i >= 0 && _items[i].GetStart() > position) + { + _items[i + 1] = _items[i--]; + } + + _items[i + 1] = interval; + _count++; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs new file mode 100644 index 000000000..412d597e8 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs @@ -0,0 +1,74 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe readonly struct LiveRange : IEquatable + { + private struct Data + { + public int Start; + public int End; + public LiveRange Next; + } + + private readonly Data* _data; + + public ref int Start => ref _data->Start; + public ref int End => ref _data->End; + public ref LiveRange Next => ref _data->Next; + + public LiveRange(int start, int end, LiveRange next = default) + { + _data = Allocators.LiveRanges.Allocate(); + + Start = start; + End = end; + Next = next; + } + + public bool Overlaps(int start, int end) + { + return Start < end && start < End; + } + + public bool Overlaps(LiveRange range) + { + return Start < range.End && range.Start < End; + } + + public bool Overlaps(int position) + { + return position >= Start && position < End; + } + + public bool Equals(LiveRange range) + { + return range._data == _data; + } + + public override bool Equals(object obj) + { + return obj is LiveRange range && Equals(range); + } + + public static bool operator ==(LiveRange a, LiveRange b) + { + return a.Equals(b); + } + + public static bool operator !=(LiveRange a, LiveRange b) + { + return !a.Equals(b); + } + + public override int GetHashCode() + { + return HashCode.Combine((IntPtr)_data); + } + + public override string ToString() + { + return $"[{Start}, {End})"; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs new file mode 100644 index 000000000..e6972cf0f --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs @@ -0,0 +1,50 @@ +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 int RegistersCount { get; } + + public RegisterMasks( + int intAvailableRegisters, + int vecAvailableRegisters, + int intCallerSavedRegisters, + int vecCallerSavedRegisters, + int intCalleeSavedRegisters, + int vecCalleeSavedRegisters, + int registersCount) + { + IntAvailableRegisters = intAvailableRegisters; + VecAvailableRegisters = vecAvailableRegisters; + IntCallerSavedRegisters = intCallerSavedRegisters; + VecCallerSavedRegisters = vecCallerSavedRegisters; + IntCalleeSavedRegisters = intCalleeSavedRegisters; + VecCalleeSavedRegisters = vecCalleeSavedRegisters; + RegistersCount = registersCount; + } + + 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}\"."); + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs new file mode 100644 index 000000000..13995bc8d --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs @@ -0,0 +1,25 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class StackAllocator + { + private int _offset; + + public int TotalSize => _offset; + + public int Allocate(OperandType type) + { + return Allocate(type.GetSizeInBytes()); + } + + public int Allocate(int sizeInBytes) + { + int offset = _offset; + + _offset += sizeInBytes; + + return offset; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs new file mode 100644 index 000000000..806002f83 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs @@ -0,0 +1,86 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe struct UseList + { + private int* _items; + private int _capacity; + + public int Count { get; private set; } + + public readonly int FirstUse => Count > 0 ? _items[Count - 1] : LiveInterval.NotFound; + public readonly Span Span => new(_items, Count); + + public void Add(int position) + { + if (Count + 1 > _capacity) + { + var oldSpan = Span; + + _capacity = Math.Max(4, _capacity * 2); + _items = Allocators.Default.Allocate((uint)_capacity); + + var newSpan = Span; + + oldSpan.CopyTo(newSpan); + } + + // Use positions are usually inserted in descending order, so inserting in descending order is faster, + // since the number of half exchanges is reduced. + int i = Count - 1; + + while (i >= 0 && _items[i] < position) + { + _items[i + 1] = _items[i--]; + } + + _items[i + 1] = position; + Count++; + } + + public readonly int NextUse(int position) + { + int index = NextUseIndex(position); + + return index != LiveInterval.NotFound ? _items[index] : LiveInterval.NotFound; + } + + public readonly int NextUseIndex(int position) + { + int i = Count - 1; + + if (i == -1 || position > _items[0]) + { + return LiveInterval.NotFound; + } + + while (i >= 0 && _items[i] < position) + { + i--; + } + + return i; + } + + public UseList Split(int position) + { + int index = NextUseIndex(position); + + // Since the list is in descending order, the new split list takes the front of the list and the current + // list takes the back of the list. + UseList result = new() + { + Count = index + 1, + }; + result._capacity = result.Count; + result._items = _items; + + Count -= result.Count; + _capacity = Count; + _items += result.Count; + + return result; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs b/src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs new file mode 100644 index 000000000..127b84231 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + struct UnwindInfo + { + public const int Stride = 4; // Bytes. + + public UnwindPushEntry[] PushEntries { get; } + public int PrologSize { get; } + + public UnwindInfo(UnwindPushEntry[] pushEntries, int prologSize) + { + PushEntries = pushEntries; + PrologSize = prologSize; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs b/src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs new file mode 100644 index 000000000..2045019a3 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + enum UnwindPseudoOp + { + PushReg = 0, + SetFrame = 1, + AllocStack = 2, + SaveReg = 3, + SaveXmm128 = 4, + } +} diff --git a/src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs b/src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs new file mode 100644 index 000000000..507ace598 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + struct UnwindPushEntry + { + public const int Stride = 16; // Bytes. + + public UnwindPseudoOp PseudoOp { get; } + public int PrologOffset { get; } + public int RegIndex { get; } + public int StackOffsetOrAllocSize { get; } + + public UnwindPushEntry(UnwindPseudoOp pseudoOp, int prologOffset, int regIndex = -1, int stackOffsetOrAllocSize = -1) + { + PseudoOp = pseudoOp; + PrologOffset = prologOffset; + RegIndex = regIndex; + StackOffsetOrAllocSize = stackOffsetOrAllocSize; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/Assembler.cs b/src/ARMeilleure/CodeGen/X86/Assembler.cs new file mode 100644 index 000000000..55bf07248 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/Assembler.cs @@ -0,0 +1,1580 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace ARMeilleure.CodeGen.X86 +{ + partial class Assembler + { + private const int ReservedBytesForJump = 1; + + private const int OpModRMBits = 24; + + private const byte RexPrefix = 0x40; + private const byte RexWPrefix = 0x48; + private const byte LockPrefix = 0xf0; + + private const int MaxRegNumber = 15; + + private struct Jump + { + public bool IsConditional { get; } + public X86Condition Condition { get; } + public Operand JumpLabel { get; } + public long? JumpTarget { get; set; } + public long JumpPosition { get; } + public long Offset { get; set; } + public int InstSize { get; set; } + + public Jump(Operand jumpLabel, long jumpPosition) + { + IsConditional = false; + Condition = 0; + JumpLabel = jumpLabel; + JumpTarget = null; + JumpPosition = jumpPosition; + + Offset = 0; + InstSize = 0; + } + + public Jump(X86Condition condition, Operand jumpLabel, long jumpPosition) + { + IsConditional = true; + Condition = condition; + JumpLabel = jumpLabel; + JumpTarget = null; + JumpPosition = jumpPosition; + + Offset = 0; + InstSize = 0; + } + } + + private struct Reloc + { + public int JumpIndex { get; set; } + public int Position { get; set; } + public Symbol Symbol { get; set; } + } + + private readonly List _jumps; + private readonly List _relocs; + private readonly Dictionary _labels; + private readonly Stream _stream; + + public bool HasRelocs => _relocs != null; + + public Assembler(Stream stream, bool relocatable) + { + _stream = stream; + _labels = new Dictionary(); + _jumps = new List(); + + _relocs = relocatable ? new List() : null; + } + + public void MarkLabel(Operand label) + { + _labels.Add(label, _stream.Position); + } + + public void Add(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Add); + } + + public void Addsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Addsd); + } + + public void Addss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Addss); + } + + public void And(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.And); + } + + public void Bsr(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Bsr); + } + + public void Bswap(Operand dest) + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Bswap); + } + + public void Call(Operand dest) + { + WriteInstruction(dest, default, OperandType.None, X86Instruction.Call); + } + + public void Cdq() + { + WriteByte(0x99); + } + + public void Cmovcc(Operand dest, Operand source, OperandType type, X86Condition condition) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Cmovcc]; + + WriteOpCode(dest, default, source, type, info.Flags, info.OpRRM | (int)condition, rrm: true); + } + + public void Cmp(Operand src1, Operand src2, OperandType type) + { + WriteInstruction(src1, src2, type, X86Instruction.Cmp); + } + + public void Cqo() + { + WriteByte(0x48); + WriteByte(0x99); + } + + public void Cmpxchg(Operand memOp, Operand src) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + + WriteInstruction(memOp, src, src.Type, X86Instruction.Cmpxchg); + } + + public void Cmpxchg16(Operand memOp, Operand src) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + WriteByte(0x66); + + WriteInstruction(memOp, src, src.Type, X86Instruction.Cmpxchg); + } + + public void Cmpxchg16b(Operand memOp) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + + WriteInstruction(memOp, default, OperandType.None, X86Instruction.Cmpxchg16b); + } + + public void Cmpxchg8(Operand memOp, Operand src) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + + WriteInstruction(memOp, src, src.Type, X86Instruction.Cmpxchg8); + } + + public void Comisd(Operand src1, Operand src2) + { + WriteInstruction(src1, default, src2, X86Instruction.Comisd); + } + + public void Comiss(Operand src1, Operand src2) + { + WriteInstruction(src1, default, src2, X86Instruction.Comiss); + } + + public void Cvtsd2ss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtsd2ss); + } + + public void Cvtsi2sd(Operand dest, Operand src1, Operand src2, OperandType type) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtsi2sd, type); + } + + public void Cvtsi2ss(Operand dest, Operand src1, Operand src2, OperandType type) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtsi2ss, type); + } + + public void Cvtss2sd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtss2sd); + } + + public void Div(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Div); + } + + public void Divsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Divsd); + } + + public void Divss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Divss); + } + + public void Idiv(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Idiv); + } + + public void Imul(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Imul128); + } + + public void Imul(Operand dest, Operand source, OperandType type) + { + if (source.Kind != OperandKind.Register) + { + throw new ArgumentException($"Invalid source operand kind \"{source.Kind}\"."); + } + + WriteInstruction(dest, source, type, X86Instruction.Imul); + } + + public void Imul(Operand dest, Operand src1, Operand src2, OperandType type) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Imul]; + + if (src2.Kind != OperandKind.Constant) + { + throw new ArgumentException($"Invalid source 2 operand kind \"{src2.Kind}\"."); + } + + if (IsImm8(src2.Value, src2.Type) && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, default, src1, type, info.Flags, info.OpRMImm8, rrm: true); + + WriteByte(src2.AsByte()); + } + else if (IsImm32(src2.Value, src2.Type) && info.OpRMImm32 != BadOp) + { + WriteOpCode(dest, default, src1, type, info.Flags, info.OpRMImm32, rrm: true); + + WriteInt32(src2.AsInt32()); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{src2.Value:X}."); + } + } + + public void Insertps(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Insertps); + + WriteByte(imm); + } + + public void Jcc(X86Condition condition, Operand dest) + { + if (dest.Kind == OperandKind.Label) + { + _jumps.Add(new Jump(condition, dest, _stream.Position)); + + // ReservedBytesForJump + WriteByte(0); + } + else + { + throw new ArgumentException("Destination operand must be of kind Label", nameof(dest)); + } + } + + public void Jcc(X86Condition condition, long offset) + { + if (ConstFitsOnS8(offset)) + { + WriteByte((byte)(0x70 | (int)condition)); + + WriteByte((byte)offset); + } + else if (ConstFitsOnS32(offset)) + { + WriteByte(0x0f); + WriteByte((byte)(0x80 | (int)condition)); + + WriteInt32((int)offset); + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + public void Jmp(long offset) + { + if (ConstFitsOnS8(offset)) + { + WriteByte(0xeb); + + WriteByte((byte)offset); + } + else if (ConstFitsOnS32(offset)) + { + WriteByte(0xe9); + + WriteInt32((int)offset); + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + public void Jmp(Operand dest) + { + if (dest.Kind == OperandKind.Label) + { + _jumps.Add(new Jump(dest, _stream.Position)); + + // ReservedBytesForJump + WriteByte(0); + } + else + { + WriteInstruction(dest, default, OperandType.None, X86Instruction.Jmp); + } + } + + public void Ldmxcsr(Operand dest) + { + WriteInstruction(dest, default, OperandType.I32, X86Instruction.Ldmxcsr); + } + + public void Lea(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Lea); + } + + public void LockOr(Operand dest, Operand source, OperandType type) + { + WriteByte(LockPrefix); + WriteInstruction(dest, source, type, X86Instruction.Or); + } + + public void Mov(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Mov); + } + + public void Mov16(Operand dest, Operand source) + { + WriteInstruction(dest, source, OperandType.None, X86Instruction.Mov16); + } + + public void Mov8(Operand dest, Operand source) + { + WriteInstruction(dest, source, OperandType.None, X86Instruction.Mov8); + } + + public void Movd(Operand dest, Operand source) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Movd]; + + if (source.Type.IsInteger() || source.Kind == OperandKind.Memory) + { + WriteOpCode(dest, default, source, OperandType.None, info.Flags, info.OpRRM, rrm: true); + } + else + { + WriteOpCode(dest, default, source, OperandType.None, info.Flags, info.OpRMR); + } + } + + public void Movdqu(Operand dest, Operand source) + { + WriteInstruction(dest, default, source, X86Instruction.Movdqu); + } + + public void Movhlps(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movhlps); + } + + public void Movlhps(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movlhps); + } + + public void Movq(Operand dest, Operand source) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Movd]; + + InstructionFlags flags = info.Flags | InstructionFlags.RexW; + + if (source.Type.IsInteger() || source.Kind == OperandKind.Memory) + { + WriteOpCode(dest, default, source, OperandType.None, flags, info.OpRRM, rrm: true); + } + else if (dest.Type.IsInteger() || dest.Kind == OperandKind.Memory) + { + WriteOpCode(dest, default, source, OperandType.None, flags, info.OpRMR); + } + else + { + WriteInstruction(dest, source, OperandType.None, X86Instruction.Movq); + } + } + + public void Movsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movsd); + } + + public void Movss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movss); + } + + public void Movsx16(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movsx16); + } + + public void Movsx32(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movsx32); + } + + public void Movsx8(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movsx8); + } + + public void Movzx16(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movzx16); + } + + public void Movzx8(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movzx8); + } + + public void Mul(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Mul128); + } + + public void Mulsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Mulsd); + } + + public void Mulss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Mulss); + } + + public void Neg(Operand dest) + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Neg); + } + + public void Not(Operand dest) + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Not); + } + + public void Or(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Or); + } + + public void Pclmulqdq(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pclmulqdq); + + WriteByte(imm); + } + + public void Pcmpeqw(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pcmpeqw); + } + + public void Pextrb(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrb); + + WriteByte(imm); + } + + public void Pextrd(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrd); + + WriteByte(imm); + } + + public void Pextrq(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrq); + + WriteByte(imm); + } + + public void Pextrw(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrw); + + WriteByte(imm); + } + + public void Pinsrb(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrb); + + WriteByte(imm); + } + + public void Pinsrd(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrd); + + WriteByte(imm); + } + + public void Pinsrq(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrq); + + WriteByte(imm); + } + + public void Pinsrw(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrw); + + WriteByte(imm); + } + + public void Pop(Operand dest) + { + if (dest.Kind == OperandKind.Register) + { + WriteCompactInst(dest, 0x58); + } + else + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Pop); + } + } + + public void Popcnt(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Popcnt); + } + + public void Pshufd(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pshufd); + + WriteByte(imm); + } + + public void Push(Operand source) + { + if (source.Kind == OperandKind.Register) + { + WriteCompactInst(source, 0x50); + } + else + { + WriteInstruction(default, source, source.Type, X86Instruction.Push); + } + } + + public void Return() + { + WriteByte(0xc3); + } + + public void Ror(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Ror); + } + + public void Sar(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Sar); + } + + public void Shl(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Shl); + } + + public void Shr(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Shr); + } + + public void Setcc(Operand dest, X86Condition condition) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Setcc]; + + WriteOpCode(dest, default, default, OperandType.None, info.Flags, info.OpRRM | (int)condition); + } + + public void Stmxcsr(Operand dest) + { + WriteInstruction(dest, default, OperandType.I32, X86Instruction.Stmxcsr); + } + + public void Sub(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Sub); + } + + public void Subsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Subsd); + } + + public void Subss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Subss); + } + + public void Test(Operand src1, Operand src2, OperandType type) + { + WriteInstruction(src1, src2, type, X86Instruction.Test); + } + + public void Xor(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Xor); + } + + public void Xorps(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Xorps); + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand source, + OperandType type = OperandType.None) + { + WriteInstruction(dest, default, source, inst, type); + } + + public void WriteInstruction(X86Instruction inst, Operand dest, Operand src1, Operand src2) + { + if (src2.Kind == OperandKind.Constant) + { + WriteInstruction(src1, dest, src2, inst); + } + else + { + WriteInstruction(dest, src1, src2, inst); + } + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + OperandType type) + { + WriteInstruction(dest, src1, src2, inst, type); + } + + public void WriteInstruction(X86Instruction inst, Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, inst); + + WriteByte(imm); + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + Operand src3) + { + // 3+ operands can only be encoded with the VEX encoding scheme. + Debug.Assert(HardwareCapabilities.SupportsVexEncoding); + + WriteInstruction(dest, src1, src2, inst); + + WriteByte((byte)(src3.AsByte() << 4)); + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + byte imm) + { + WriteInstruction(dest, src1, src2, inst); + + WriteByte(imm); + } + + private void WriteShiftInst(Operand dest, Operand source, OperandType type, X86Instruction inst) + { + if (source.Kind == OperandKind.Register) + { + X86Register shiftReg = (X86Register)source.GetRegister().Index; + + Debug.Assert(shiftReg == X86Register.Rcx, $"Invalid shift register \"{shiftReg}\"."); + + source = default; + } + else if (source.Kind == OperandKind.Constant) + { + source = Operand.Factory.Const((int)source.Value & (dest.Type == OperandType.I32 ? 0x1f : 0x3f)); + } + + WriteInstruction(dest, source, type, inst); + } + + private void WriteInstruction(Operand dest, Operand source, OperandType type, X86Instruction inst) + { + ref readonly InstructionInfo info = ref _instTable[(int)inst]; + + if (source != default) + { + if (source.Kind == OperandKind.Constant) + { + ulong imm = source.Value; + + if (inst == X86Instruction.Mov8) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else if (inst == X86Instruction.Mov16) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm32); + + WriteInt16((short)imm); + } + else if (IsImm8(imm, type) && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else if (!source.Relocatable && IsImm32(imm, type) && info.OpRMImm32 != BadOp) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm32); + + WriteInt32((int)imm); + } + else if (dest != default && dest.Kind == OperandKind.Register && info.OpRImm64 != BadOp) + { + int rexPrefix = GetRexPrefix(dest, source, type, rrm: false); + + if (rexPrefix != 0) + { + WriteByte((byte)rexPrefix); + } + + WriteByte((byte)(info.OpRImm64 + (dest.GetRegister().Index & 0b111))); + + if (HasRelocs && source.Relocatable) + { + _relocs.Add(new Reloc + { + JumpIndex = _jumps.Count - 1, + Position = (int)_stream.Position, + Symbol = source.Symbol, + }); + } + + WriteUInt64(imm); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{imm:X}."); + } + } + else if (source.Kind == OperandKind.Register && info.OpRMR != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRMR); + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRRM, rrm: true); + } + else + { + throw new ArgumentException($"Invalid source operand kind \"{source.Kind}\"."); + } + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRRM, rrm: true); + } + else if (info.OpRMR != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRMR); + } + else + { + throw new ArgumentNullException(nameof(source)); + } + } + + private void WriteInstruction( + Operand dest, + Operand src1, + Operand src2, + X86Instruction inst, + OperandType type = OperandType.None) + { + ref readonly InstructionInfo info = ref _instTable[(int)inst]; + + if (src2 != default) + { + if (src2.Kind == OperandKind.Constant) + { + ulong imm = src2.Value; + + if ((byte)imm == imm && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, src1, default, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{imm:X}."); + } + } + else if (src2.Kind == OperandKind.Register && info.OpRMR != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRMR); + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRRM, rrm: true); + } + else + { + throw new ArgumentException($"Invalid source operand kind \"{src2.Kind}\"."); + } + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRRM, rrm: true); + } + else if (info.OpRMR != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRMR); + } + else + { + throw new ArgumentNullException(nameof(src2)); + } + } + + private void WriteOpCode( + Operand dest, + Operand src1, + Operand src2, + OperandType type, + InstructionFlags flags, + int opCode, + bool rrm = false) + { + int rexPrefix = GetRexPrefix(dest, src2, type, rrm); + + if ((flags & InstructionFlags.RexW) != 0) + { + rexPrefix |= RexWPrefix; + } + + int modRM = (opCode >> OpModRMBits) << 3; + + MemoryOperand memOp = default; + bool hasMemOp = false; + + if (dest != default) + { + if (dest.Kind == OperandKind.Register) + { + int regIndex = dest.GetRegister().Index; + + modRM |= (regIndex & 0b111) << (rrm ? 3 : 0); + + if ((flags & InstructionFlags.Reg8Dest) != 0 && regIndex >= 4) + { + rexPrefix |= RexPrefix; + } + } + else if (dest.Kind == OperandKind.Memory) + { + memOp = dest.GetMemory(); + hasMemOp = true; + } + else + { + throw new ArgumentException("Invalid destination operand kind \"" + dest.Kind + "\"."); + } + } + + if (src2 != default) + { + if (src2.Kind == OperandKind.Register) + { + int regIndex = src2.GetRegister().Index; + + modRM |= (regIndex & 0b111) << (rrm ? 0 : 3); + + if ((flags & InstructionFlags.Reg8Src) != 0 && regIndex >= 4) + { + rexPrefix |= RexPrefix; + } + } + else if (src2.Kind == OperandKind.Memory && !hasMemOp) + { + memOp = src2.GetMemory(); + hasMemOp = true; + } + else + { + throw new ArgumentException("Invalid source operand kind \"" + src2.Kind + "\"."); + } + } + + bool needsSibByte = false; + bool needsDisplacement = false; + + int sib = 0; + + if (hasMemOp) + { + // Either source or destination is a memory operand. + Register baseReg = memOp.BaseAddress.GetRegister(); + + X86Register baseRegLow = (X86Register)(baseReg.Index & 0b111); + + needsSibByte = memOp.Index != default || baseRegLow == X86Register.Rsp; + needsDisplacement = memOp.Displacement != 0 || baseRegLow == X86Register.Rbp; + + if (needsDisplacement) + { + if (ConstFitsOnS8(memOp.Displacement)) + { + modRM |= 0x40; + } + else /* if (ConstFitsOnS32(memOp.Displacement)) */ + { + modRM |= 0x80; + } + } + + if (baseReg.Index >= 8) + { + Debug.Assert((uint)baseReg.Index <= MaxRegNumber); + + rexPrefix |= RexPrefix | (baseReg.Index >> 3); + } + + if (needsSibByte) + { + sib = (int)baseRegLow; + + if (memOp.Index != default) + { + int indexReg = memOp.Index.GetRegister().Index; + + Debug.Assert(indexReg != (int)X86Register.Rsp, "Using RSP as index register on the memory operand is not allowed."); + + if (indexReg >= 8) + { + Debug.Assert((uint)indexReg <= MaxRegNumber); + + rexPrefix |= RexPrefix | (indexReg >> 3) << 1; + } + + sib |= (indexReg & 0b111) << 3; + } + else + { + sib |= 0b100 << 3; + } + + sib |= (int)memOp.Scale << 6; + + modRM |= 0b100; + } + else + { + modRM |= (int)baseRegLow; + } + } + else + { + // Source and destination are registers. + modRM |= 0xc0; + } + + Debug.Assert(opCode != BadOp, "Invalid opcode value."); + + if ((flags & InstructionFlags.Evex) != 0 && HardwareCapabilities.SupportsEvexEncoding) + { + WriteEvexInst(dest, src1, src2, type, flags, opCode); + + opCode &= 0xff; + } + else if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding) + { + // In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits. + + int vexByte2 = (flags & InstructionFlags.PrefixMask) switch + { + InstructionFlags.Prefix66 => 1, + InstructionFlags.PrefixF3 => 2, + InstructionFlags.PrefixF2 => 3, + _ => 0, + }; + + if (src1 != default) + { + vexByte2 |= (src1.GetRegister().Index ^ 0xf) << 3; + } + else + { + vexByte2 |= 0b1111 << 3; + } + + ushort opCodeHigh = (ushort)(opCode >> 8); + + if ((rexPrefix & 0b1011) == 0 && opCodeHigh == 0xf) + { + // Two-byte form. + WriteByte(0xc5); + + vexByte2 |= (~rexPrefix & 4) << 5; + + WriteByte((byte)vexByte2); + } + else + { + // Three-byte form. + WriteByte(0xc4); + + int vexByte1 = (~rexPrefix & 7) << 5; + + switch (opCodeHigh) + { + case 0xf: + vexByte1 |= 1; + break; + case 0xf38: + vexByte1 |= 2; + break; + case 0xf3a: + vexByte1 |= 3; + break; + + default: + Debug.Assert(false, $"Failed to VEX encode opcode 0x{opCode:X}."); + break; + } + + vexByte2 |= (rexPrefix & 8) << 4; + + WriteByte((byte)vexByte1); + WriteByte((byte)vexByte2); + } + + opCode &= 0xff; + } + else + { + if (flags.HasFlag(InstructionFlags.Prefix66)) + { + WriteByte(0x66); + } + + if (flags.HasFlag(InstructionFlags.PrefixF2)) + { + WriteByte(0xf2); + } + + if (flags.HasFlag(InstructionFlags.PrefixF3)) + { + WriteByte(0xf3); + } + + if (rexPrefix != 0) + { + WriteByte((byte)rexPrefix); + } + } + + if (dest != default && (flags & InstructionFlags.RegOnly) != 0) + { + opCode += dest.GetRegister().Index & 7; + } + + if ((opCode & 0xff0000) != 0) + { + WriteByte((byte)(opCode >> 16)); + } + + if ((opCode & 0xff00) != 0) + { + WriteByte((byte)(opCode >> 8)); + } + + WriteByte((byte)opCode); + + if ((flags & InstructionFlags.RegOnly) == 0) + { + WriteByte((byte)modRM); + + if (needsSibByte) + { + WriteByte((byte)sib); + } + + if (needsDisplacement) + { + if (ConstFitsOnS8(memOp.Displacement)) + { + WriteByte((byte)memOp.Displacement); + } + else /* if (ConstFitsOnS32(memOp.Displacement)) */ + { + WriteInt32(memOp.Displacement); + } + } + } + } + + private void WriteEvexInst( + Operand dest, + Operand src1, + Operand src2, + OperandType type, + InstructionFlags flags, + int opCode, + bool broadcast = false, + int registerWidth = 128, + int maskRegisterIdx = 0, + bool zeroElements = false) + { + int op1Idx = dest.GetRegister().Index; + int op2Idx = src1.GetRegister().Index; + int op3Idx = src2.GetRegister().Index; + + WriteByte(0x62); + + // P0 + // Extend operand 1 register + bool r = (op1Idx & 8) == 0; + // Extend operand 3 register + bool x = (op3Idx & 16) == 0; + // Extend operand 3 register + bool b = (op3Idx & 8) == 0; + // Extend operand 1 register + bool rp = (op1Idx & 16) == 0; + // Escape code index + byte mm = 0b00; + + switch ((ushort)(opCode >> 8)) + { + case 0xf00: + mm = 0b01; + break; + case 0xf38: + mm = 0b10; + break; + case 0xf3a: + mm = 0b11; + break; + + default: + Debug.Fail($"Failed to EVEX encode opcode 0x{opCode:X}."); + break; + } + + WriteByte( + (byte)( + (r ? 0x80 : 0) | + (x ? 0x40 : 0) | + (b ? 0x20 : 0) | + (rp ? 0x10 : 0) | + mm)); + + // P1 + // Specify 64-bit lane mode + bool w = Is64Bits(type); + // Operand 2 register index + byte vvvv = (byte)(~op2Idx & 0b1111); + // Opcode prefix + byte pp = (flags & InstructionFlags.PrefixMask) switch + { + InstructionFlags.Prefix66 => 0b01, + InstructionFlags.PrefixF3 => 0b10, + InstructionFlags.PrefixF2 => 0b11, + _ => 0, + }; + WriteByte( + (byte)( + (w ? 0x80 : 0) | + (vvvv << 3) | + 0b100 | + pp)); + + // P2 + // Mask register determines what elements to zero, rather than what elements to merge + bool z = zeroElements; + // Specifies register-width + byte ll = 0b00; + switch (registerWidth) + { + case 128: + ll = 0b00; + break; + case 256: + ll = 0b01; + break; + case 512: + ll = 0b10; + break; + + default: + Debug.Fail($"Invalid EVEX vector register width {registerWidth}."); + break; + } + // Embedded broadcast in the case of a memory operand + bool bcast = broadcast; + // Extend operand 2 register + bool vp = (op2Idx & 16) == 0; + // Mask register index + Debug.Assert(maskRegisterIdx < 8, $"Invalid mask register index {maskRegisterIdx}."); + byte aaa = (byte)(maskRegisterIdx & 0b111); + + WriteByte( + (byte)( + (z ? 0x80 : 0) | + (ll << 5) | + (bcast ? 0x10 : 0) | + (vp ? 8 : 0) | + aaa)); + } + + private void WriteCompactInst(Operand operand, int opCode) + { + int regIndex = operand.GetRegister().Index; + + if (regIndex >= 8) + { + WriteByte(0x41); + } + + WriteByte((byte)(opCode + (regIndex & 0b111))); + } + + private static int GetRexPrefix(Operand dest, Operand source, OperandType type, bool rrm) + { + int rexPrefix = 0; + + if (Is64Bits(type)) + { + rexPrefix = RexWPrefix; + } + + void SetRegisterHighBit(Register reg, int bit) + { + if (reg.Index >= 8) + { + rexPrefix |= RexPrefix | (reg.Index >> 3) << bit; + } + } + + if (dest != default && dest.Kind == OperandKind.Register) + { + SetRegisterHighBit(dest.GetRegister(), rrm ? 2 : 0); + } + + if (source != default && source.Kind == OperandKind.Register) + { + SetRegisterHighBit(source.GetRegister(), rrm ? 0 : 2); + } + + return rexPrefix; + } + + public (byte[], RelocInfo) GetCode() + { + var jumps = CollectionsMarshal.AsSpan(_jumps); + var relocs = CollectionsMarshal.AsSpan(_relocs); + + // Write jump relative offsets. + bool modified; + + do + { + modified = false; + + for (int i = 0; i < jumps.Length; i++) + { + ref Jump jump = ref jumps[i]; + + // If jump target not resolved yet, resolve it. + jump.JumpTarget ??= _labels[jump.JumpLabel]; + + long jumpTarget = jump.JumpTarget.Value; + long offset = jumpTarget - jump.JumpPosition; + + if (offset < 0) + { + for (int j = i - 1; j >= 0; j--) + { + ref Jump jump2 = ref jumps[j]; + + if (jump2.JumpPosition < jumpTarget) + { + break; + } + + offset -= jump2.InstSize - ReservedBytesForJump; + } + } + else + { + for (int j = i + 1; j < jumps.Length; j++) + { + ref Jump jump2 = ref jumps[j]; + + if (jump2.JumpPosition >= jumpTarget) + { + break; + } + + offset += jump2.InstSize - ReservedBytesForJump; + } + + offset -= ReservedBytesForJump; + } + + if (jump.IsConditional) + { + jump.InstSize = GetJccLength(offset); + } + else + { + jump.InstSize = GetJmpLength(offset); + } + + // The jump is relative to the next instruction, not the current one. + // Since we didn't know the next instruction address when calculating + // the offset (as the size of the current jump instruction was not known), + // we now need to compensate the offset with the jump instruction size. + // It's also worth noting that: + // - This is only needed for backward jumps. + // - The GetJmpLength and GetJccLength also compensates the offset + // internally when computing the jump instruction size. + if (offset < 0) + { + offset -= jump.InstSize; + } + + if (jump.Offset != offset) + { + jump.Offset = offset; + + modified = true; + } + } + } + while (modified); + + // Write the code, ignoring the dummy bytes after jumps, into a new stream. + _stream.Seek(0, SeekOrigin.Begin); + + using var codeStream = MemoryStreamManager.Shared.GetStream(); + var assembler = new Assembler(codeStream, HasRelocs); + + bool hasRelocs = HasRelocs; + int relocIndex = 0; + int relocOffset = 0; + var relocEntries = hasRelocs + ? new RelocEntry[relocs.Length] + : Array.Empty(); + + for (int i = 0; i < jumps.Length; i++) + { + ref Jump jump = ref jumps[i]; + + // If has relocations, calculate their new positions compensating for jumps. + if (hasRelocs) + { + relocOffset += jump.InstSize - ReservedBytesForJump; + + for (; relocIndex < relocEntries.Length; relocIndex++) + { + ref Reloc reloc = ref relocs[relocIndex]; + + if (reloc.JumpIndex > i) + { + break; + } + + relocEntries[relocIndex] = new RelocEntry(reloc.Position + relocOffset, reloc.Symbol); + } + } + + Span buffer = new byte[jump.JumpPosition - _stream.Position]; + + _stream.Read(buffer); + _stream.Seek(ReservedBytesForJump, SeekOrigin.Current); + + codeStream.Write(buffer); + + if (jump.IsConditional) + { + assembler.Jcc(jump.Condition, jump.Offset); + } + else + { + assembler.Jmp(jump.Offset); + } + } + + // Write remaining relocations. This case happens when there are no jumps assembled. + for (; relocIndex < relocEntries.Length; relocIndex++) + { + ref Reloc reloc = ref relocs[relocIndex]; + + relocEntries[relocIndex] = new RelocEntry(reloc.Position + relocOffset, reloc.Symbol); + } + + _stream.CopyTo(codeStream); + + var code = codeStream.ToArray(); + var relocInfo = new RelocInfo(relocEntries); + + return (code, relocInfo); + } + + private static bool Is64Bits(OperandType type) + { + return type == OperandType.I64 || type == OperandType.FP64; + } + + private static bool IsImm8(ulong immediate, OperandType type) + { + long value = type == OperandType.I32 ? (int)immediate : (long)immediate; + + return ConstFitsOnS8(value); + } + + private static bool IsImm32(ulong immediate, OperandType type) + { + long value = type == OperandType.I32 ? (int)immediate : (long)immediate; + + return ConstFitsOnS32(value); + } + + private static int GetJccLength(long offset) + { + if (ConstFitsOnS8(offset < 0 ? offset - 2 : offset)) + { + return 2; + } + else if (ConstFitsOnS32(offset < 0 ? offset - 6 : offset)) + { + return 6; + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + private static int GetJmpLength(long offset) + { + if (ConstFitsOnS8(offset < 0 ? offset - 2 : offset)) + { + return 2; + } + else if (ConstFitsOnS32(offset < 0 ? offset - 5 : offset)) + { + return 5; + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + private static bool ConstFitsOnS8(long value) + { + return value == (sbyte)value; + } + + private static bool ConstFitsOnS32(long value) + { + return value == (int)value; + } + + private void WriteInt16(short value) + { + WriteUInt16((ushort)value); + } + + private void WriteInt32(int value) + { + WriteUInt32((uint)value); + } + + private void WriteByte(byte value) + { + _stream.WriteByte(value); + } + + private void WriteUInt16(ushort value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + } + + private void WriteUInt32(uint value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + } + + private void WriteUInt64(ulong value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + _stream.WriteByte((byte)(value >> 32)); + _stream.WriteByte((byte)(value >> 40)); + _stream.WriteByte((byte)(value >> 48)); + _stream.WriteByte((byte)(value >> 56)); + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/AssemblerTable.cs b/src/ARMeilleure/CodeGen/X86/AssemblerTable.cs new file mode 100644 index 000000000..8910e8891 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/AssemblerTable.cs @@ -0,0 +1,299 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +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] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + private enum InstructionFlags + { + None = 0, + RegOnly = 1 << 0, + Reg8Src = 1 << 1, + Reg8Dest = 1 << 2, + RexW = 1 << 3, + Vex = 1 << 4, + Evex = 1 << 5, + + 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]; + +#pragma warning disable IDE0055 // Disable formatting + // 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.Vfmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); + 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.Vfnmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); + 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.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | 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)); +#pragma warning restore IDE0055 + + static void Add(X86Instruction inst, in InstructionInfo info) + { + _instTable[(int)inst] = info; + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CallConvName.cs b/src/ARMeilleure/CodeGen/X86/CallConvName.cs new file mode 100644 index 000000000..6208da1ec --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CallConvName.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum CallConvName + { + SystemV, + Windows, + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CallingConvention.cs b/src/ARMeilleure/CodeGen/X86/CallingConvention.cs new file mode 100644 index 000000000..8433aaea9 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CallingConvention.cs @@ -0,0 +1,170 @@ +using System; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CallingConvention + { + private const int RegistersMask = 0xffff; + + public static int GetIntAvailableRegisters() + { + return RegistersMask & ~(1 << (int)X86Register.Rsp); + } + + public static int GetVecAvailableRegisters() + { + return RegistersMask; + } + + public static int GetIntCallerSavedRegisters() + { + if (GetCurrentCallConv() == CallConvName.Windows) + { +#pragma warning disable IDE0055 // Disable formatting + return (1 << (int)X86Register.Rax) | + (1 << (int)X86Register.Rcx) | + (1 << (int)X86Register.Rdx) | + (1 << (int)X86Register.R8) | + (1 << (int)X86Register.R9) | + (1 << (int)X86Register.R10) | + (1 << (int)X86Register.R11); + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + return (1 << (int)X86Register.Rax) | + (1 << (int)X86Register.Rcx) | + (1 << (int)X86Register.Rdx) | + (1 << (int)X86Register.Rsi) | + (1 << (int)X86Register.Rdi) | + (1 << (int)X86Register.R8) | + (1 << (int)X86Register.R9) | + (1 << (int)X86Register.R10) | + (1 << (int)X86Register.R11); +#pragma warning restore IDE0055 + } + } + + public static int GetVecCallerSavedRegisters() + { + if (GetCurrentCallConv() == CallConvName.Windows) + { + return (1 << (int)X86Register.Xmm0) | + (1 << (int)X86Register.Xmm1) | + (1 << (int)X86Register.Xmm2) | + (1 << (int)X86Register.Xmm3) | + (1 << (int)X86Register.Xmm4) | + (1 << (int)X86Register.Xmm5); + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + return RegistersMask; + } + } + + public static int GetIntCalleeSavedRegisters() + { + return GetIntCallerSavedRegisters() ^ RegistersMask; + } + + public static int GetVecCalleeSavedRegisters() + { + return GetVecCallerSavedRegisters() ^ RegistersMask; + } + + public static int GetArgumentsOnRegsCount() + { + return 4; + } + + public static int GetIntArgumentsOnRegsCount() + { + return 6; + } + + public static int GetVecArgumentsOnRegsCount() + { + return 8; + } + + public static X86Register GetIntArgumentRegister(int index) + { + if (GetCurrentCallConv() == CallConvName.Windows) + { + switch (index) + { + case 0: + return X86Register.Rcx; + case 1: + return X86Register.Rdx; + case 2: + return X86Register.R8; + case 3: + return X86Register.R9; + } + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + switch (index) + { + case 0: + return X86Register.Rdi; + case 1: + return X86Register.Rsi; + case 2: + return X86Register.Rdx; + case 3: + return X86Register.Rcx; + case 4: + return X86Register.R8; + case 5: + return X86Register.R9; + } + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static X86Register GetVecArgumentRegister(int index) + { + int count; + + if (GetCurrentCallConv() == CallConvName.Windows) + { + count = 4; + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + count = 8; + } + + if ((uint)index < count) + { + return X86Register.Xmm0 + index; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static X86Register GetIntReturnRegister() + { + return X86Register.Rax; + } + + public static X86Register GetIntReturnRegisterHigh() + { + return X86Register.Rdx; + } + + public static X86Register GetVecReturnRegister() + { + return X86Register.Xmm0; + } + + public static CallConvName GetCurrentCallConv() + { + return OperatingSystem.IsWindows() + ? CallConvName.Windows + : CallConvName.SystemV; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs b/src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs new file mode 100644 index 000000000..ae83ea80c --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs @@ -0,0 +1,19 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CodeGenCommon + { + public static bool IsLongConst(Operand op) + { + long value = op.Type == OperandType.I32 ? op.AsInt32() : op.AsInt64(); + + return !ConstFitsOnS32(value); + } + + private static bool ConstFitsOnS32(long value) + { + return value == (int)value; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenContext.cs b/src/ARMeilleure/CodeGen/X86/CodeGenContext.cs new file mode 100644 index 000000000..d4d4c2058 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CodeGenContext.cs @@ -0,0 +1,105 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; +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 = MemoryStreamManager.Shared.GetStream(); + _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; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs new file mode 100644 index 000000000..9e94a077f --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs @@ -0,0 +1,1891 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Common; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CodeGenerator + { + private const int RegistersCount = 16; + private const int PageSize = 0x1000; + private const int StackGuardSize = 0x2000; + + private static readonly Action[] _instTable; + + static CodeGenerator() + { + _instTable = new Action[EnumUtils.GetCount(typeof(Instruction))]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Instruction.Add, GenerateAdd); + Add(Instruction.BitwiseAnd, GenerateBitwiseAnd); + Add(Instruction.BitwiseExclusiveOr, GenerateBitwiseExclusiveOr); + Add(Instruction.BitwiseNot, GenerateBitwiseNot); + Add(Instruction.BitwiseOr, GenerateBitwiseOr); + Add(Instruction.BranchIf, GenerateBranchIf); + Add(Instruction.ByteSwap, GenerateByteSwap); + Add(Instruction.Call, GenerateCall); + Add(Instruction.Clobber, GenerateClobber); + Add(Instruction.Compare, GenerateCompare); + Add(Instruction.CompareAndSwap, GenerateCompareAndSwap); + Add(Instruction.CompareAndSwap16, GenerateCompareAndSwap16); + Add(Instruction.CompareAndSwap8, GenerateCompareAndSwap8); + Add(Instruction.ConditionalSelect, GenerateConditionalSelect); + Add(Instruction.ConvertI64ToI32, GenerateConvertI64ToI32); + Add(Instruction.ConvertToFP, GenerateConvertToFP); + Add(Instruction.Copy, GenerateCopy); + Add(Instruction.CountLeadingZeros, GenerateCountLeadingZeros); + Add(Instruction.Divide, GenerateDivide); + Add(Instruction.DivideUI, GenerateDivideUI); + Add(Instruction.Fill, GenerateFill); + Add(Instruction.Load, GenerateLoad); + Add(Instruction.Load16, GenerateLoad16); + Add(Instruction.Load8, GenerateLoad8); + Add(Instruction.MemoryBarrier, GenerateMemoryBarrier); + Add(Instruction.Multiply, GenerateMultiply); + Add(Instruction.Multiply64HighSI, GenerateMultiply64HighSI); + Add(Instruction.Multiply64HighUI, GenerateMultiply64HighUI); + Add(Instruction.Negate, GenerateNegate); + Add(Instruction.Return, GenerateReturn); + Add(Instruction.RotateRight, GenerateRotateRight); + Add(Instruction.ShiftLeft, GenerateShiftLeft); + Add(Instruction.ShiftRightSI, GenerateShiftRightSI); + Add(Instruction.ShiftRightUI, GenerateShiftRightUI); + Add(Instruction.SignExtend16, GenerateSignExtend16); + Add(Instruction.SignExtend32, GenerateSignExtend32); + Add(Instruction.SignExtend8, GenerateSignExtend8); + Add(Instruction.Spill, GenerateSpill); + Add(Instruction.SpillArg, GenerateSpillArg); + Add(Instruction.StackAlloc, GenerateStackAlloc); + Add(Instruction.Store, GenerateStore); + Add(Instruction.Store16, GenerateStore16); + Add(Instruction.Store8, GenerateStore8); + Add(Instruction.Subtract, GenerateSubtract); + Add(Instruction.Tailcall, GenerateTailcall); + Add(Instruction.VectorCreateScalar, GenerateVectorCreateScalar); + Add(Instruction.VectorExtract, GenerateVectorExtract); + Add(Instruction.VectorExtract16, GenerateVectorExtract16); + Add(Instruction.VectorExtract8, GenerateVectorExtract8); + Add(Instruction.VectorInsert, GenerateVectorInsert); + Add(Instruction.VectorInsert16, GenerateVectorInsert16); + Add(Instruction.VectorInsert8, GenerateVectorInsert8); + Add(Instruction.VectorOne, GenerateVectorOne); + Add(Instruction.VectorZero, GenerateVectorZero); + Add(Instruction.VectorZeroUpper64, GenerateVectorZeroUpper64); + Add(Instruction.VectorZeroUpper96, GenerateVectorZeroUpper96); + Add(Instruction.ZeroExtend16, GenerateZeroExtend16); + Add(Instruction.ZeroExtend32, GenerateZeroExtend32); + Add(Instruction.ZeroExtend8, GenerateZeroExtend8); +#pragma warning restore IDE0055 + + static void Add(Instruction inst, Action func) + { + _instTable[(int)inst] = func; + } + } + + public static CompiledFunction Generate(CompilerContext cctx) + { + ControlFlowGraph cfg = cctx.Cfg; + + Logger.StartPass(PassName.Optimization); + + if (cctx.Options.HasFlag(CompilerOptions.Optimize)) + { + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Optimizer.RunPass(cfg); + } + + BlockPlacement.RunPass(cfg); + } + + X86Optimizer.RunPass(cfg); + + Logger.EndPass(PassName.Optimization, cfg); + + Logger.StartPass(PassName.PreAllocation); + + StackAllocator stackAlloc = new(); + + PreAllocator.RunPass(cctx, stackAlloc, out int maxCallArgs); + + Logger.EndPass(PassName.PreAllocation, cfg); + + Logger.StartPass(PassName.RegisterAllocation); + + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Ssa.Deconstruct(cfg); + } + + IRegisterAllocator regAlloc; + + if (cctx.Options.HasFlag(CompilerOptions.Lsra)) + { + regAlloc = new LinearScanAllocator(); + } + else + { + regAlloc = new HybridAllocator(); + } + + RegisterMasks regMasks = new( + CallingConvention.GetIntAvailableRegisters(), + CallingConvention.GetVecAvailableRegisters(), + CallingConvention.GetIntCallerSavedRegisters(), + CallingConvention.GetVecCallerSavedRegisters(), + CallingConvention.GetIntCalleeSavedRegisters(), + CallingConvention.GetVecCalleeSavedRegisters(), + RegistersCount); + + AllocationResult allocResult = regAlloc.RunPass(cfg, stackAlloc, regMasks); + + Logger.EndPass(PassName.RegisterAllocation, cfg); + + Logger.StartPass(PassName.CodeGeneration); + + bool relocatable = (cctx.Options & CompilerOptions.Relocatable) != 0; + + CodeGenContext context = new(allocResult, maxCallArgs, cfg.Blocks.Count, relocatable); + + UnwindInfo unwindInfo = WritePrologue(context); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + context.EnterBlock(block); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + GenerateOperation(context, node); + } + + if (block.SuccessorsCount == 0) + { + // The only blocks which can have 0 successors are exit blocks. + Operation last = block.Operations.Last; + + Debug.Assert(last.Instruction == Instruction.Tailcall || + last.Instruction == Instruction.Return); + } + else + { + BasicBlock succ = block.GetSuccessor(0); + + if (succ != block.ListNext) + { + context.JumpTo(succ); + } + } + } + + (byte[] code, RelocInfo relocInfo) = context.Assembler.GetCode(); + + Logger.EndPass(PassName.CodeGeneration); + + return new CompiledFunction(code, unwindInfo, relocInfo); + } + + private static void GenerateOperation(CodeGenContext context, Operation operation) + { + if (operation.Instruction == Instruction.Extended) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + switch (info.Type) + { + case IntrinsicType.Comis_: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + switch (operation.Intrinsic) + { + case Intrinsic.X86Comisdeq: + context.Assembler.Comisd(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Equal); + break; + + case Intrinsic.X86Comisdge: + context.Assembler.Comisd(src1, src2); + context.Assembler.Setcc(dest, X86Condition.AboveOrEqual); + break; + + case Intrinsic.X86Comisdlt: + context.Assembler.Comisd(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Below); + break; + + case Intrinsic.X86Comisseq: + context.Assembler.Comiss(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Equal); + break; + + case Intrinsic.X86Comissge: + context.Assembler.Comiss(src1, src2); + context.Assembler.Setcc(dest, X86Condition.AboveOrEqual); + break; + + case Intrinsic.X86Comisslt: + context.Assembler.Comiss(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Below); + break; + } + + context.Assembler.Movzx8(dest, dest, OperandType.I32); + + break; + } + + case IntrinsicType.Mxcsr: + { + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + Debug.Assert(offset.Type == OperandType.I32); + + int offs = offset.AsInt32() + context.CallArgsRegionSize; + + Operand rsp = Register(X86Register.Rsp); + Operand memOp = MemoryOp(OperandType.I32, rsp, default, Multiplier.x1, offs); + + Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding); + + if (operation.Intrinsic == Intrinsic.X86Ldmxcsr) + { + Operand bits = operation.GetSource(1); + Debug.Assert(bits.Type == OperandType.I32); + + context.Assembler.Mov(memOp, bits, OperandType.I32); + context.Assembler.Ldmxcsr(memOp); + } + else if (operation.Intrinsic == Intrinsic.X86Stmxcsr) + { + Operand dest = operation.Destination; + Debug.Assert(dest.Type == OperandType.I32); + + context.Assembler.Stmxcsr(memOp); + context.Assembler.Mov(dest, memOp, OperandType.I32); + } + + break; + } + + case IntrinsicType.PopCount: + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Popcnt(dest, source, dest.Type); + + break; + } + + case IntrinsicType.Unary: + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, source); + + break; + } + + case IntrinsicType.UnaryToGpr: + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && !source.Type.IsInteger()); + + if (operation.Intrinsic == Intrinsic.X86Cvtsi2si) + { + if (dest.Type == OperandType.I32) + { + context.Assembler.Movd(dest, source); // int _mm_cvtsi128_si32(__m128i a) + } + else /* if (dest.Type == OperandType.I64) */ + { + context.Assembler.Movq(dest, source); // __int64 _mm_cvtsi128_si64(__m128i a) + } + } + else + { + context.Assembler.WriteInstruction(info.Inst, dest, source, dest.Type); + } + + break; + } + + case IntrinsicType.Binary: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger()); + Debug.Assert(!src2.Type.IsInteger() || src2.Kind == OperandKind.Constant); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2); + + break; + } + + case IntrinsicType.BinaryGpr: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src2.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src2.Type); + + break; + } + + case IntrinsicType.Crc32: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameReg(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src1.Type.IsInteger() && src2.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, src2, dest.Type); + + break; + } + + case IntrinsicType.BinaryImm: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src2.Kind == OperandKind.Constant); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2.AsByte()); + + break; + } + + case IntrinsicType.Ternary: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(dest, src1, src2, src3); + + Debug.Assert(!dest.Type.IsInteger()); + + if (info.Inst == X86Instruction.Blendvpd && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vblendvpd, dest, src1, src2, src3); + } + else if (info.Inst == X86Instruction.Blendvps && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vblendvps, dest, src1, src2, src3); + } + else if (info.Inst == X86Instruction.Pblendvb && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vpblendvb, dest, src1, src2, src3); + } + else + { + EnsureSameReg(dest, src1); + + Debug.Assert(src3.GetRegister().Index == 0); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2); + } + + break; + } + + case IntrinsicType.TernaryImm: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(dest, src1, src2); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src3.Kind == OperandKind.Constant); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src3.AsByte()); + + break; + } + + case IntrinsicType.Fma: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + Debug.Assert(HardwareCapabilities.SupportsVexEncoding); + + Debug.Assert(dest.Kind == OperandKind.Register && src1.Kind == OperandKind.Register && src2.Kind == OperandKind.Register); + Debug.Assert(src3.Kind == OperandKind.Register || src3.Kind == OperandKind.Memory); + + EnsureSameType(dest, src1, src2, src3); + Debug.Assert(dest.Type == OperandType.V128); + + Debug.Assert(dest.Value == src1.Value); + + context.Assembler.WriteInstruction(info.Inst, dest, src2, src3); + + break; + } + } + } + else + { + Action func = _instTable[(int)operation.Instruction]; + + if (func != null) + { + func(context, operation); + } + else + { + throw new ArgumentException($"Invalid instruction \"{operation.Instruction}\"."); + } + } + } + + private static void GenerateAdd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + if (dest.Type.IsInteger()) + { + // If Destination and Source 1 Operands are the same, perform a standard add as there are no benefits to using LEA. + if (dest.Kind == src1.Kind && dest.Value == src1.Value) + { + ValidateBinOp(dest, src1, src2); + + context.Assembler.Add(dest, src2, dest.Type); + } + else + { + EnsureSameType(dest, src1, src2); + + int offset; + Operand index; + + if (src2.Kind == OperandKind.Constant) + { + offset = src2.AsInt32(); + index = default; + } + else + { + offset = 0; + index = src2; + } + + Operand memOp = MemoryOp(dest.Type, src1, index, Multiplier.x1, offset); + + context.Assembler.Lea(dest, memOp, dest.Type); + } + } + else + { + ValidateBinOp(dest, src1, src2); + + if (dest.Type == OperandType.FP32) + { + context.Assembler.Addss(dest, src1, src2); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Addsd(dest, src1, src2); + } + } + } + + private static void GenerateBitwiseAnd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + // Note: GenerateCompareCommon makes the assumption that BitwiseAnd will emit only a single `and` + // instruction. + context.Assembler.And(dest, src2, dest.Type); + } + + private static void GenerateBitwiseExclusiveOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Xor(dest, src2, dest.Type); + } + else + { + context.Assembler.Xorps(dest, src1, src2); + } + } + + private static void GenerateBitwiseNot(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Not(dest); + } + + private static void GenerateBitwiseOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Or(dest, src2, dest.Type); + } + + private static void GenerateBranchIf(CodeGenContext context, Operation operation) + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToX86Condition(); + + GenerateCompareCommon(context, operation); + + context.JumpTo(cond, context.CurrBlock.GetSuccessor(1)); + } + + private static void GenerateByteSwap(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Bswap(dest); + } + + private static void GenerateCall(CodeGenContext context, Operation operation) + { + context.Assembler.Call(operation.GetSource(0)); + } + + private static void GenerateClobber(CodeGenContext context, Operation operation) + { + // This is only used to indicate that a register is clobbered to the + // register allocator, we don't need to produce any code. + } + + private static void GenerateCompare(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand comp = operation.GetSource(2); + + Debug.Assert(dest.Type == OperandType.I32); + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToX86Condition(); + + GenerateCompareCommon(context, operation); + + context.Assembler.Setcc(dest, cond); + context.Assembler.Movzx8(dest, dest, OperandType.I32); + } + + private static void GenerateCompareCommon(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(src1, src2); + + Debug.Assert(src1.Type.IsInteger()); + + if (src2.Kind == OperandKind.Constant && src2.Value == 0) + { + if (MatchOperation(operation.ListPrevious, Instruction.BitwiseAnd, src1.Type, src1.GetRegister())) + { + // Since the `test` and `and` instruction set the status flags in the same way, we can omit the + // `test r,r` instruction when it is immediately preceded by an `and r,*` instruction. + // + // For example: + // + // and eax, 0x3 + // test eax, eax + // jz .L0 + // + // => + // + // and eax, 0x3 + // jz .L0 + } + else + { + context.Assembler.Test(src1, src1, src1.Type); + } + } + else + { + context.Assembler.Cmp(src1, src2, src1.Type); + } + } + + private static void GenerateCompareAndSwap(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + + if (operation.SourcesCount == 5) // CompareAndSwap128 has 5 sources, compared to CompareAndSwap64/32's 3. + { + Operand memOp = MemoryOp(OperandType.I64, src1); + + context.Assembler.Cmpxchg16b(memOp); + } + else + { + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(src2, src3); + + Operand memOp = MemoryOp(src3.Type, src1); + + context.Assembler.Cmpxchg(memOp, src3); + } + } + + private static void GenerateCompareAndSwap16(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(src2, src3); + + Operand memOp = MemoryOp(src3.Type, src1); + + context.Assembler.Cmpxchg16(memOp, src3); + } + + private static void GenerateCompareAndSwap8(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(src2, src3); + + Operand memOp = MemoryOp(src3.Type, src1); + + context.Assembler.Cmpxchg8(memOp, src3); + } + + private static void GenerateConditionalSelect(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameReg(dest, src3); + EnsureSameType(dest, src2, src3); + + Debug.Assert(dest.Type.IsInteger()); + Debug.Assert(src1.Type == OperandType.I32); + + context.Assembler.Test(src1, src1, src1.Type); + context.Assembler.Cmovcc(dest, src2, dest.Type, X86Condition.NotEqual); + } + + private static void GenerateConvertI64ToI32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.I32 && source.Type == OperandType.I64); + + context.Assembler.Mov(dest, source, OperandType.I32); + } + + private static void GenerateConvertToFP(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); + + if (dest.Type == OperandType.FP32) + { + Debug.Assert(source.Type.IsInteger() || source.Type == OperandType.FP64); + + if (source.Type.IsInteger()) + { + context.Assembler.Xorps(dest, dest, dest); + context.Assembler.Cvtsi2ss(dest, dest, source, source.Type); + } + else /* if (source.Type == OperandType.FP64) */ + { + context.Assembler.Cvtsd2ss(dest, dest, source); + + GenerateZeroUpper96(context, dest, dest); + } + } + else /* if (dest.Type == OperandType.FP64) */ + { + Debug.Assert(source.Type.IsInteger() || source.Type == OperandType.FP32); + + if (source.Type.IsInteger()) + { + context.Assembler.Xorps(dest, dest, dest); + context.Assembler.Cvtsi2sd(dest, dest, source, source.Type); + } + else /* if (source.Type == OperandType.FP32) */ + { + context.Assembler.Cvtss2sd(dest, dest, source); + + GenerateZeroUpper64(context, dest, dest); + } + } + } + + private static void GenerateCopy(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger() || source.Kind != OperandKind.Constant); + + // Moves to the same register are useless. + if (dest.Kind == source.Kind && dest.Value == source.Value) + { + return; + } + + if (dest.Kind == OperandKind.Register && + source.Kind == OperandKind.Constant && source.Value == 0) + { + // Assemble "mov reg, 0" as "xor reg, reg" as the later is more efficient. + context.Assembler.Xor(dest, dest, OperandType.I32); + } + else if (dest.Type.IsInteger()) + { + context.Assembler.Mov(dest, source, dest.Type); + } + else + { + context.Assembler.Movdqu(dest, source); + } + } + + private static void GenerateCountLeadingZeros(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Bsr(dest, source, dest.Type); + + int operandSize = dest.Type == OperandType.I32 ? 32 : 64; + int operandMask = operandSize - 1; + + // When the input operand is 0, the result is undefined, however the + // ZF flag is set. We are supposed to return the operand size on that + // case. So, add an additional jump to handle that case, by moving the + // operand size constant to the destination register. + Operand neLabel = Label(); + + context.Assembler.Jcc(X86Condition.NotEqual, neLabel); + + context.Assembler.Mov(dest, Const(operandSize | operandMask), OperandType.I32); + + context.Assembler.MarkLabel(neLabel); + + // BSR returns the zero based index of the last bit set on the operand, + // starting from the least significant bit. However we are supposed to + // return the number of 0 bits on the high end. So, we invert the result + // of the BSR using XOR to get the correct value. + context.Assembler.Xor(dest, Const(operandMask), OperandType.I32); + } + + private static void GenerateDivide(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand dividend = operation.GetSource(0); + Operand divisor = operation.GetSource(1); + + if (!dest.Type.IsInteger()) + { + ValidateBinOp(dest, dividend, divisor); + } + + if (dest.Type.IsInteger()) + { + divisor = operation.GetSource(2); + + EnsureSameType(dest, divisor); + + if (divisor.Type == OperandType.I32) + { + context.Assembler.Cdq(); + } + else + { + context.Assembler.Cqo(); + } + + context.Assembler.Idiv(divisor); + } + else if (dest.Type == OperandType.FP32) + { + context.Assembler.Divss(dest, dividend, divisor); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Divsd(dest, dividend, divisor); + } + } + + private static void GenerateDivideUI(CodeGenContext context, Operation operation) + { + Operand divisor = operation.GetSource(2); + + Operand rdx = Register(X86Register.Rdx); + + Debug.Assert(divisor.Type.IsInteger()); + + context.Assembler.Xor(rdx, rdx, OperandType.I32); + context.Assembler.Div(divisor); + } + + private static void GenerateFill(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize; + + Operand rsp = Register(X86Register.Rsp); + + Operand memOp = MemoryOp(dest.Type, rsp, default, Multiplier.x1, offs); + + GenerateLoad(context, memOp, dest); + } + + private static void GenerateLoad(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = Memory(operation.GetSource(0), value.Type); + + GenerateLoad(context, address, value); + } + + private static void GenerateLoad16(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Movzx16(value, address, value.Type); + } + + private static void GenerateLoad8(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Movzx8(value, address, value.Type); + } + + private static void GenerateMemoryBarrier(CodeGenContext context, Operation operation) + { + context.Assembler.LockOr(MemoryOp(OperandType.I64, Register(X86Register.Rsp)), Const(0), OperandType.I32); + } + + private static void GenerateMultiply(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + if (src2.Kind != OperandKind.Constant) + { + EnsureSameReg(dest, src1); + } + + EnsureSameType(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + if (src2.Kind == OperandKind.Constant) + { + context.Assembler.Imul(dest, src1, src2, dest.Type); + } + else + { + context.Assembler.Imul(dest, src2, dest.Type); + } + } + else if (dest.Type == OperandType.FP32) + { + context.Assembler.Mulss(dest, src1, src2); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Mulsd(dest, src1, src2); + } + } + + private static void GenerateMultiply64HighSI(CodeGenContext context, Operation operation) + { + Operand source = operation.GetSource(1); + + Debug.Assert(source.Type == OperandType.I64); + + context.Assembler.Imul(source); + } + + private static void GenerateMultiply64HighUI(CodeGenContext context, Operation operation) + { + Operand source = operation.GetSource(1); + + Debug.Assert(source.Type == OperandType.I64); + + context.Assembler.Mul(source); + } + + private static void GenerateNegate(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Neg(dest); + } + + private static void GenerateReturn(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Return(); + } + + private static void GenerateRotateRight(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Ror(dest, src2, dest.Type); + } + + private static void GenerateShiftLeft(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Shl(dest, src2, dest.Type); + } + + private static void GenerateShiftRightSI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Sar(dest, src2, dest.Type); + } + + private static void GenerateShiftRightUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Shr(dest, src2, dest.Type); + } + + private static void GenerateSignExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movsx16(dest, source, dest.Type); + } + + private static void GenerateSignExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movsx32(dest, source, dest.Type); + } + + private static void GenerateSignExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movsx8(dest, source, dest.Type); + } + + private static void GenerateSpill(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, context.CallArgsRegionSize); + } + + private static void GenerateSpillArg(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, 0); + } + + private static void GenerateSpill(CodeGenContext context, Operation operation, int baseOffset) + { + Operand offset = operation.GetSource(0); + Operand source = operation.GetSource(1); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + baseOffset; + + Operand rsp = Register(X86Register.Rsp); + + Operand memOp = MemoryOp(source.Type, rsp, default, Multiplier.x1, offs); + + GenerateStore(context, memOp, source); + } + + private static void GenerateStackAlloc(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize; + + Operand rsp = Register(X86Register.Rsp); + + Operand memOp = MemoryOp(OperandType.I64, rsp, default, Multiplier.x1, offs); + + context.Assembler.Lea(dest, memOp, OperandType.I64); + } + + private static void GenerateStore(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = Memory(operation.GetSource(0), value.Type); + + GenerateStore(context, address, value); + } + + private static void GenerateStore16(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Mov16(address, value); + } + + private static void GenerateStore8(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Mov8(address, value); + } + + private static void GenerateSubtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Sub(dest, src2, dest.Type); + } + else if (dest.Type == OperandType.FP32) + { + context.Assembler.Subss(dest, src1, src2); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Subsd(dest, src1, src2); + } + } + + private static void GenerateTailcall(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Jmp(operation.GetSource(0)); + } + + private static void GenerateVectorCreateScalar(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(!dest.Type.IsInteger() && source.Type.IsInteger()); + + if (source.Type == OperandType.I32) + { + context.Assembler.Movd(dest, source); // (__m128i _mm_cvtsi32_si128(int a)) + } + else /* if (source.Type == OperandType.I64) */ + { + context.Assembler.Movq(dest, source); // (__m128i _mm_cvtsi64_si128(__int64 a)) + } + } + + private static void GenerateVectorExtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; //Value + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < OperandType.V128.GetSizeInBytes() / dest.Type.GetSizeInBytes()); + + if (dest.Type == OperandType.I32) + { + if (index == 0) + { + context.Assembler.Movd(dest, src1); + } + else if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrd(dest, src1, index); + } + else + { + int mask0 = 0b11_10_01_00; + int mask1 = 0b11_10_01_00; + + mask0 = BitUtils.RotateRight(mask0, index * 2, 8); + mask1 = BitUtils.RotateRight(mask1, 8 - index * 2, 8); + + context.Assembler.Pshufd(src1, src1, (byte)mask0); + context.Assembler.Movd(dest, src1); + context.Assembler.Pshufd(src1, src1, (byte)mask1); + } + } + else if (dest.Type == OperandType.I64) + { + if (index == 0) + { + context.Assembler.Movq(dest, src1); + } + else if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrq(dest, src1, index); + } + else + { + const byte Mask = 0b01_00_11_10; + + context.Assembler.Pshufd(src1, src1, Mask); + context.Assembler.Movq(dest, src1); + context.Assembler.Pshufd(src1, src1, Mask); + } + } + else + { + // Floating-point types. + if ((index >= 2 && dest.Type == OperandType.FP32) || + (index == 1 && dest.Type == OperandType.FP64)) + { + context.Assembler.Movhlps(dest, dest, src1); + context.Assembler.Movq(dest, dest); + } + else + { + context.Assembler.Movq(dest, src1); + } + + if (dest.Type == OperandType.FP32) + { + context.Assembler.Pshufd(dest, dest, (byte)(0xfc | (index & 1))); + } + } + } + + private static void GenerateVectorExtract16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; //Value + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 8); + + context.Assembler.Pextrw(dest, src1, index); + } + + private static void GenerateVectorExtract8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; //Value + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 16); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrb(dest, src1, index); + } + else + { + context.Assembler.Pextrw(dest, src1, (byte)(index >> 1)); + + if ((index & 1) != 0) + { + context.Assembler.Shr(dest, Const(8), OperandType.I32); + } + else + { + context.Assembler.Movzx8(dest, dest, OperandType.I32); + } + } + } + + private static void GenerateVectorInsert(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Value + Operand src3 = operation.GetSource(2); //Index + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + void InsertIntSse2(int words) + { + if (dest.GetRegister() != src1.GetRegister()) + { + context.Assembler.Movdqu(dest, src1); + } + + for (int word = 0; word < words; word++) + { + // Insert lower 16-bits. + context.Assembler.Pinsrw(dest, dest, src2, (byte)(index * words + word)); + + // Move next word down. + context.Assembler.Ror(src2, Const(16), src2.Type); + } + } + + if (src2.Type == OperandType.I32) + { + Debug.Assert(index < 4); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pinsrd(dest, src1, src2, index); + } + else + { + InsertIntSse2(2); + } + } + else if (src2.Type == OperandType.I64) + { + Debug.Assert(index < 2); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pinsrq(dest, src1, src2, index); + } + else + { + InsertIntSse2(4); + } + } + else if (src2.Type == OperandType.FP32) + { + Debug.Assert(index < 4); + + if (index != 0) + { + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Insertps(dest, src1, src2, (byte)(index << 4)); + } + else + { + if (src1.GetRegister() == src2.GetRegister()) + { + int mask = 0b11_10_01_00; + + mask &= ~(0b11 << index * 2); + + context.Assembler.Pshufd(dest, src1, (byte)mask); + } + else + { + int mask0 = 0b11_10_01_00; + int mask1 = 0b11_10_01_00; + + mask0 = BitUtils.RotateRight(mask0, index * 2, 8); + mask1 = BitUtils.RotateRight(mask1, 8 - index * 2, 8); + + context.Assembler.Pshufd(src1, src1, (byte)mask0); // Lane to be inserted in position 0. + context.Assembler.Movss(dest, src1, src2); // dest[127:0] = src1[127:32] | src2[31:0] + context.Assembler.Pshufd(dest, dest, (byte)mask1); // Inserted lane in original position. + + if (dest.GetRegister() != src1.GetRegister()) + { + context.Assembler.Pshufd(src1, src1, (byte)mask1); // Restore src1. + } + } + } + } + else + { + context.Assembler.Movss(dest, src1, src2); + } + } + else /* if (src2.Type == OperandType.FP64) */ + { + Debug.Assert(index < 2); + + if (index != 0) + { + context.Assembler.Movlhps(dest, src1, src2); + } + else + { + context.Assembler.Movsd(dest, src1, src2); + } + } + } + + private static void GenerateVectorInsert16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Value + Operand src3 = operation.GetSource(2); //Index + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Pinsrw(dest, src1, src2, index); + } + + private static void GenerateVectorInsert8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Value + Operand src3 = operation.GetSource(2); //Index + + // It's not possible to emulate this instruction without + // SSE 4.1 support without the use of a temporary register, + // so we instead handle that case on the pre-allocator when + // SSE 4.1 is not supported on the CPU. + Debug.Assert(HardwareCapabilities.SupportsSse41); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Pinsrb(dest, src1, src2, index); + } + + private static void GenerateVectorOne(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.Pcmpeqw(dest, dest, dest); + } + + private static void GenerateVectorZero(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.Xorps(dest, dest, dest); + } + + private static void GenerateVectorZeroUpper64(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + GenerateZeroUpper64(context, dest, source); + } + + private static void GenerateVectorZeroUpper96(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + GenerateZeroUpper96(context, dest, source); + } + + private static void GenerateZeroExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movzx16(dest, source, OperandType.I32); + } + + private static void GenerateZeroExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + // We can eliminate the move if source is already 32-bit and the registers are the same. + if (dest.Value == source.Value && source.Type == OperandType.I32) + { + return; + } + + context.Assembler.Mov(dest, source, OperandType.I32); + } + + private static void GenerateZeroExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movzx8(dest, source, OperandType.I32); + } + + private static void GenerateLoad(CodeGenContext context, Operand address, Operand value) + { + switch (value.Type) + { + case OperandType.I32: + context.Assembler.Mov(value, address, OperandType.I32); + break; + case OperandType.I64: + context.Assembler.Mov(value, address, OperandType.I64); + break; + case OperandType.FP32: + context.Assembler.Movd(value, address); + break; + case OperandType.FP64: + context.Assembler.Movq(value, address); + break; + case OperandType.V128: + context.Assembler.Movdqu(value, address); + break; + + default: + Debug.Assert(false); + break; + } + } + + private static void GenerateStore(CodeGenContext context, Operand address, Operand value) + { + switch (value.Type) + { + case OperandType.I32: + context.Assembler.Mov(address, value, OperandType.I32); + break; + case OperandType.I64: + context.Assembler.Mov(address, value, OperandType.I64); + break; + case OperandType.FP32: + context.Assembler.Movd(address, value); + break; + case OperandType.FP64: + context.Assembler.Movq(address, value); + break; + case OperandType.V128: + context.Assembler.Movdqu(address, value); + break; + + default: + Debug.Assert(false); + break; + } + } + + private static void GenerateZeroUpper64(CodeGenContext context, Operand dest, Operand source) + { + context.Assembler.Movq(dest, source); + } + + private static void GenerateZeroUpper96(CodeGenContext context, Operand dest, Operand source) + { + context.Assembler.Movq(dest, source); + context.Assembler.Pshufd(dest, dest, 0xfc); + } + + private static bool MatchOperation(Operation node, Instruction inst, OperandType destType, Register destReg) + { + if (node == default || node.DestinationsCount == 0) + { + return false; + } + + if (node.Instruction != inst) + { + return false; + } + + Operand dest = node.Destination; + + return dest.Kind == OperandKind.Register && + dest.Type == destType && + dest.GetRegister() == destReg; + } + + [Conditional("DEBUG")] + private static void ValidateUnOp(Operand dest, Operand source) + { + EnsureSameReg(dest, source); + EnsureSameType(dest, source); + } + + [Conditional("DEBUG")] + private static void ValidateBinOp(Operand dest, Operand src1, Operand src2) + { + EnsureSameReg(dest, src1); + EnsureSameType(dest, src1, src2); + } + + [Conditional("DEBUG")] + private static void ValidateShift(Operand dest, Operand src1, Operand src2) + { + EnsureSameReg(dest, src1); + EnsureSameType(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src2.Type == OperandType.I32); + } + + private static void EnsureSameReg(Operand op1, Operand op2) + { + if (!op1.Type.IsInteger() && HardwareCapabilities.SupportsVexEncoding) + { + return; + } + + Debug.Assert(op1.Kind == OperandKind.Register || op1.Kind == OperandKind.Memory); + Debug.Assert(op1.Kind == op2.Kind); + Debug.Assert(op1.Value == op2.Value); + } + + private static void EnsureSameType(Operand op1, Operand op2) + { + Debug.Assert(op1.Type == op2.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3, Operand op4) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + Debug.Assert(op1.Type == op4.Type); + } + + private static UnwindInfo WritePrologue(CodeGenContext context) + { + List pushEntries = new(); + + Operand rsp = Register(X86Register.Rsp); + + int mask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + context.Assembler.Push(Register((X86Register)bit)); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: bit)); + + mask &= ~(1 << bit); + } + + int reservedStackSize = context.CallArgsRegionSize + context.AllocResult.SpillRegionSize; + + reservedStackSize += context.XmmSaveRegionSize; + + if (reservedStackSize >= StackGuardSize) + { + GenerateInlineStackProbe(context, reservedStackSize); + } + + if (reservedStackSize != 0) + { + context.Assembler.Sub(rsp, Const(reservedStackSize), OperandType.I64); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.AllocStack, context.StreamOffset, stackOffsetOrAllocSize: reservedStackSize)); + } + + int offset = reservedStackSize; + + mask = CallingConvention.GetVecCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + offset -= 16; + + Operand memOp = MemoryOp(OperandType.V128, rsp, default, Multiplier.x1, offset); + + context.Assembler.Movdqu(memOp, Xmm((X86Register)bit)); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.SaveXmm128, context.StreamOffset, bit, offset)); + + mask &= ~(1 << bit); + } + + return new UnwindInfo(pushEntries.ToArray(), context.StreamOffset); + } + + private static void WriteEpilogue(CodeGenContext context) + { + Operand rsp = Register(X86Register.Rsp); + + int reservedStackSize = context.CallArgsRegionSize + context.AllocResult.SpillRegionSize; + + reservedStackSize += context.XmmSaveRegionSize; + + int offset = reservedStackSize; + + int mask = CallingConvention.GetVecCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + offset -= 16; + + Operand memOp = MemoryOp(OperandType.V128, rsp, default, Multiplier.x1, offset); + + context.Assembler.Movdqu(Xmm((X86Register)bit), memOp); + + mask &= ~(1 << bit); + } + + if (reservedStackSize != 0) + { + context.Assembler.Add(rsp, Const(reservedStackSize), OperandType.I64); + } + + mask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + + while (mask != 0) + { + int bit = BitUtils.HighestBitSet(mask); + + context.Assembler.Pop(Register((X86Register)bit)); + + mask &= ~(1 << bit); + } + } + + private static void GenerateInlineStackProbe(CodeGenContext context, int size) + { + // Windows does lazy stack allocation, and there are just 2 + // guard pages on the end of the stack. So, if the allocation + // size we make is greater than this guard size, we must ensure + // that the OS will map all pages that we'll use. We do that by + // doing a dummy read on those pages, forcing a page fault and + // the OS to map them. If they are already mapped, nothing happens. + const int PageMask = PageSize - 1; + + size = (size + PageMask) & ~PageMask; + + Operand rsp = Register(X86Register.Rsp); + Operand temp = Register(CallingConvention.GetIntReturnRegister()); + + for (int offset = PageSize; offset < size; offset += PageSize) + { + Operand memOp = MemoryOp(OperandType.I32, rsp, default, Multiplier.x1, -offset); + + context.Assembler.Mov(temp, memOp, OperandType.I32); + } + } + + private static Operand Memory(Operand operand, OperandType type) + { + if (operand.Kind == OperandKind.Memory) + { + return operand; + } + + return MemoryOp(type, operand); + } + + private static Operand Register(X86Register register, OperandType type = OperandType.I64) + { + return Operand.Factory.Register((int)register, RegisterType.Integer, type); + } + + private static Operand Xmm(X86Register register) + { + return Operand.Factory.Register((int)register, RegisterType.Vector, OperandType.V128); + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs b/src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs new file mode 100644 index 000000000..4f6f1e87b --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs @@ -0,0 +1,144 @@ +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; + +namespace ARMeilleure.CodeGen.X86 +{ + static class HardwareCapabilities + { + private delegate uint GetXcr0(); + + 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; + } + + Xcr0InfoEax = (Xcr0FlagsEax)GetXcr0Eax(); + } + + private static uint GetXcr0Eax() + { + if (!FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave)) + { + // XSAVE feature required for xgetbv + return 0; + } + + ReadOnlySpan asmGetXcr0 = new byte[] + { + 0x31, 0xc9, // xor ecx, ecx + 0xf, 0x01, 0xd0, // xgetbv + 0xc3, // ret + }; + + using MemoryBlock memGetXcr0 = new((ulong)asmGetXcr0.Length); + + memGetXcr0.Write(0, asmGetXcr0); + + memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute); + + var fGetXcr0 = Marshal.GetDelegateForFunctionPointer(memGetXcr0.Pointer); + + return fGetXcr0(); + } + + [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, + Xsave = 1 << 26, + Osxsave = 1 << 27, + Avx = 1 << 28, + F16c = 1 << 29, + } + + [Flags] + public enum FeatureFlags7Ebx + { + Avx2 = 1 << 5, + Avx512f = 1 << 16, + Avx512dq = 1 << 17, + Sha = 1 << 29, + Avx512bw = 1 << 30, + Avx512vl = 1 << 31, + } + + [Flags] + public enum FeatureFlags7Ecx + { + Gfni = 1 << 8, + } + + [Flags] + public enum Xcr0FlagsEax + { + Sse = 1 << 1, + YmmHi128 = 1 << 2, + Opmask = 1 << 5, + ZmmHi256 = 1 << 6, + Hi16Zmm = 1 << 7, + } + + public static FeatureFlags1Edx FeatureInfo1Edx { get; } + public static FeatureFlags1Ecx FeatureInfo1Ecx { get; } + public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0; + public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0; + public static Xcr0FlagsEax Xcr0InfoEax { 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 | FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128); + public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx; + public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) + && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128 | Xcr0FlagsEax.Opmask | Xcr0FlagsEax.ZmmHi256 | Xcr0FlagsEax.Hi16Zmm); + public static bool SupportsAvx512Vl => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512vl) && SupportsAvx512F; + public static bool SupportsAvx512Bw => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512bw) && SupportsAvx512F; + public static bool SupportsAvx512Dq => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512dq) && SupportsAvx512F; + 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; + public static bool SupportsEvexEncoding => SupportsAvx512F && !ForceLegacySse; + } +} diff --git a/src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs b/src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs new file mode 100644 index 000000000..16054c616 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.X86 +{ + readonly struct IntrinsicInfo + { + public X86Instruction Inst { get; } + public IntrinsicType Type { get; } + + public IntrinsicInfo(X86Instruction inst, IntrinsicType type) + { + Inst = inst; + Type = type; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs b/src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs new file mode 100644 index 000000000..daa1f8f60 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs @@ -0,0 +1,202 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.X86 +{ + static class IntrinsicTable + { + private static readonly IntrinsicInfo[] _intrinTable; + + static IntrinsicTable() + { + _intrinTable = new IntrinsicInfo[EnumUtils.GetCount(typeof(Intrinsic))]; + +#pragma warning disable IDE0055 // Disable formatting + 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.X86Ldmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); + 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.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.X86Stmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); + 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.X86Vfmadd231pd, new IntrinsicInfo(X86Instruction.Vfmadd231pd, IntrinsicType.Fma)); + 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.X86Vfnmadd231pd, new IntrinsicInfo(X86Instruction.Vfnmadd231pd, 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.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary)); +#pragma warning restore IDE0055 + } + + private static void Add(Intrinsic intrin, IntrinsicInfo info) + { + _intrinTable[(int)intrin] = info; + } + + public static IntrinsicInfo GetInfo(Intrinsic intrin) + { + return _intrinTable[(int)intrin]; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/IntrinsicType.cs b/src/ARMeilleure/CodeGen/X86/IntrinsicType.cs new file mode 100644 index 000000000..7c3ef354d --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/IntrinsicType.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum IntrinsicType + { + Comis_, + Mxcsr, + PopCount, + Unary, + UnaryToGpr, + Binary, + BinaryGpr, + BinaryImm, + Crc32, + Ternary, + TernaryImm, + Fma, + } +} diff --git a/src/ARMeilleure/CodeGen/X86/Mxcsr.cs b/src/ARMeilleure/CodeGen/X86/Mxcsr.cs new file mode 100644 index 000000000..719afe59e --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/Mxcsr.cs @@ -0,0 +1,15 @@ +using System; + +namespace ARMeilleure.CodeGen.X86 +{ + [Flags] + enum Mxcsr + { + Ftz = 1 << 15, // Flush To Zero. + Rhi = 1 << 14, // Round Mode high bit. + Rlo = 1 << 13, // Round Mode low bit. + Um = 1 << 11, // Underflow Mask. + Dm = 1 << 8, // Denormal Mask. + Daz = 1 << 6, // Denormals Are Zero. + } +} diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocator.cs b/src/ARMeilleure/CodeGen/X86/PreAllocator.cs new file mode 100644 index 000000000..590c35c7b --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/PreAllocator.cs @@ -0,0 +1,788 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + class PreAllocator + { + public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs) + { + maxCallArgs = -1; + + Span buffer = default; + + CallConvName callConv = CallingConvention.GetCurrentCallConv(); + + Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()]; + + for (BasicBlock block = cctx.Cfg.Blocks.First; block != null; block = block.ListNext) + { + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + if (node.Instruction == Instruction.Phi) + { + continue; + } + + InsertConstantRegCopies(block.Operations, node); + InsertDestructiveRegCopies(block.Operations, node); + InsertConstrainedRegCopies(block.Operations, node); + + switch (node.Instruction) + { + case Instruction.Call: + // Get the maximum number of arguments used on a call. + // On windows, when a struct is returned from the call, + // we also need to pass the pointer where the struct + // should be written on the first argument. + int argsCount = node.SourcesCount - 1; + + if (node.Destination != default && node.Destination.Type == OperandType.V128) + { + argsCount++; + } + + if (maxCallArgs < argsCount) + { + maxCallArgs = argsCount; + } + + // Copy values to registers expected by the function + // being called, as mandated by the ABI. + if (callConv == CallConvName.Windows) + { + PreAllocatorWindows.InsertCallCopies(block.Operations, stackAlloc, node); + } + else /* if (callConv == CallConvName.SystemV) */ + { + PreAllocatorSystemV.InsertCallCopies(block.Operations, node); + } + break; + + case Instruction.ConvertToFPUI: + GenerateConvertToFPUI(block.Operations, node); + break; + + case Instruction.LoadArgument: + if (callConv == CallConvName.Windows) + { + nextNode = PreAllocatorWindows.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node); + } + else /* if (callConv == CallConvName.SystemV) */ + { + nextNode = PreAllocatorSystemV.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node); + } + break; + + case Instruction.Negate: + if (!node.GetSource(0).Type.IsInteger()) + { + GenerateNegate(block.Operations, node); + } + break; + + case Instruction.Return: + if (callConv == CallConvName.Windows) + { + PreAllocatorWindows.InsertReturnCopy(cctx, block.Operations, preservedArgs, node); + } + else /* if (callConv == CallConvName.SystemV) */ + { + PreAllocatorSystemV.InsertReturnCopy(block.Operations, node); + } + break; + + case Instruction.Tailcall: + if (callConv == CallConvName.Windows) + { + PreAllocatorWindows.InsertTailcallCopies(block.Operations, node); + } + else + { + PreAllocatorSystemV.InsertTailcallCopies(block.Operations, node); + } + break; + + case Instruction.VectorInsert8: + if (!HardwareCapabilities.SupportsSse41) + { + GenerateVectorInsert8(block.Operations, node); + } + break; + + case Instruction.Extended: + if (node.Intrinsic == Intrinsic.X86Ldmxcsr) + { + int stackOffset = stackAlloc.Allocate(OperandType.I32); + + node.SetSources(new Operand[] { Const(stackOffset), node.GetSource(0) }); + } + else if (node.Intrinsic == Intrinsic.X86Stmxcsr) + { + int stackOffset = stackAlloc.Allocate(OperandType.I32); + + node.SetSources(new Operand[] { Const(stackOffset) }); + } + break; + } + } + } + } + + protected static void InsertConstantRegCopies(IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0 || IsXmmIntrinsic(node)) + { + return; + } + + Instruction inst = node.Instruction; + + Operand src1 = node.GetSource(0); + Operand src2; + + if (src1.Kind == OperandKind.Constant) + { + if (!src1.Type.IsInteger()) + { + // Handle non-integer types (FP32, FP64 and V128). + // For instructions without an immediate operand, we do the following: + // - Insert a copy with the constant value (as integer) to a GPR. + // - Insert a copy from the GPR to a XMM register. + // - Replace the constant use with the XMM register. + src1 = AddXmmCopy(nodes, node, src1); + + node.SetSource(0, src1); + } + else if (!HasConstSrc1(inst)) + { + // Handle integer types. + // Most ALU instructions accepts a 32-bits immediate on the second operand. + // We need to ensure the following: + // - If the constant is on operand 1, we need to move it. + // -- But first, we try to swap operand 1 and 2 if the instruction is commutative. + // -- Doing so may allow us to encode the constant as operand 2 and avoid a copy. + // - If the constant is on operand 2, we check if the instruction supports it, + // if not, we also add a copy. 64-bits constants are usually not supported. + if (IsCommutative(node)) + { + src2 = node.GetSource(1); + + (src2, src1) = (src1, src2); + + node.SetSource(0, src1); + node.SetSource(1, src2); + } + + if (src1.Kind == OperandKind.Constant) + { + src1 = AddCopy(nodes, node, src1); + + node.SetSource(0, src1); + } + } + } + + if (node.SourcesCount < 2) + { + return; + } + + src2 = node.GetSource(1); + + if (src2.Kind == OperandKind.Constant) + { + if (!src2.Type.IsInteger()) + { + src2 = AddXmmCopy(nodes, node, src2); + + node.SetSource(1, src2); + } + else if (!HasConstSrc2(inst) || CodeGenCommon.IsLongConst(src2)) + { + src2 = AddCopy(nodes, node, src2); + + node.SetSource(1, src2); + } + } + } + + protected static void InsertConstrainedRegCopies(IntrusiveList nodes, Operation node) + { + Operand dest = node.Destination; + + switch (node.Instruction) + { + case Instruction.CompareAndSwap: + case Instruction.CompareAndSwap16: + case Instruction.CompareAndSwap8: + { + OperandType type = node.GetSource(1).Type; + + if (type == OperandType.V128) + { + // Handle the many restrictions of the compare and exchange (16 bytes) instruction: + // - The expected value should be in RDX:RAX. + // - The new value to be written should be in RCX:RBX. + // - The value at the memory location is loaded to RDX:RAX. + void SplitOperand(Operand source, Operand lr, Operand hr) + { + nodes.AddBefore(node, Operation(Instruction.VectorExtract, lr, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, hr, source, Const(1))); + } + + Operand rax = Gpr(X86Register.Rax, OperandType.I64); + Operand rbx = Gpr(X86Register.Rbx, OperandType.I64); + Operand rcx = Gpr(X86Register.Rcx, OperandType.I64); + Operand rdx = Gpr(X86Register.Rdx, OperandType.I64); + + SplitOperand(node.GetSource(1), rax, rdx); + SplitOperand(node.GetSource(2), rbx, rcx); + + Operation operation = node; + + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, rax)); + nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, rdx, Const(1))); + + operation.SetDestinations(new Operand[] { rdx, rax }); + operation.SetSources(new Operand[] { operation.GetSource(0), rdx, rax, rcx, rbx }); + } + else + { + // Handle the many restrictions of the compare and exchange (32/64) instruction: + // - The expected value should be in (E/R)AX. + // - The value at the memory location is loaded to (E/R)AX. + Operand expected = node.GetSource(1); + Operand newValue = node.GetSource(2); + + Operand rax = Gpr(X86Register.Rax, expected.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, rax, expected)); + + // We need to store the new value into a temp, since it may + // be a constant, and this instruction does not support immediate operands. + Operand temp = Local(newValue.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, newValue)); + + node.SetSources(new Operand[] { node.GetSource(0), rax, temp }); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, rax)); + + node.Destination = rax; + } + + break; + } + + case Instruction.Divide: + case Instruction.DivideUI: + { + // Handle the many restrictions of the division instructions: + // - The dividend is always in RDX:RAX. + // - The result is always in RAX. + // - Additionally it also writes the remainder in RDX. + if (dest.Type.IsInteger()) + { + Operand src1 = node.GetSource(0); + + Operand rax = Gpr(X86Register.Rax, src1.Type); + Operand rdx = Gpr(X86Register.Rdx, src1.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, rax, src1)); + nodes.AddBefore(node, Operation(Instruction.Clobber, rdx)); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, rax)); + + node.SetSources(new Operand[] { rdx, rax, node.GetSource(1) }); + node.Destination = rax; + } + + break; + } + + case Instruction.Extended: + { + bool isBlend = node.Intrinsic == Intrinsic.X86Blendvpd || + node.Intrinsic == Intrinsic.X86Blendvps || + node.Intrinsic == Intrinsic.X86Pblendvb; + + // BLENDVPD, BLENDVPS, PBLENDVB last operand is always implied to be XMM0 when VEX is not supported. + // SHA256RNDS2 always has an implied XMM0 as a last operand. + if ((isBlend && !HardwareCapabilities.SupportsVexEncoding) || node.Intrinsic == Intrinsic.X86Sha256Rnds2) + { + Operand xmm0 = Xmm(X86Register.Xmm0, OperandType.V128); + + nodes.AddBefore(node, Operation(Instruction.Copy, xmm0, node.GetSource(2))); + + node.SetSource(2, xmm0); + } + + break; + } + + case Instruction.Multiply64HighSI: + case Instruction.Multiply64HighUI: + { + // Handle the many restrictions of the i64 * i64 = i128 multiply instructions: + // - The multiplicand is always in RAX. + // - The lower 64-bits of the result is always in RAX. + // - The higher 64-bits of the result is always in RDX. + Operand src1 = node.GetSource(0); + + Operand rax = Gpr(X86Register.Rax, src1.Type); + Operand rdx = Gpr(X86Register.Rdx, src1.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, rax, src1)); + + node.SetSource(0, rax); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, rdx)); + + node.SetDestinations(new Operand[] { rdx, rax }); + + break; + } + + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + { + // The shift register is always implied to be CL (low 8-bits of RCX or ECX). + if (node.GetSource(1).Kind == OperandKind.LocalVariable) + { + Operand rcx = Gpr(X86Register.Rcx, OperandType.I32); + + nodes.AddBefore(node, Operation(Instruction.Copy, rcx, node.GetSource(1))); + + node.SetSource(1, rcx); + } + + break; + } + } + } + + protected static void InsertDestructiveRegCopies(IntrusiveList nodes, Operation node) + { + if (node.Destination == default || node.SourcesCount == 0) + { + return; + } + + Instruction inst = node.Instruction; + + Operand dest = node.Destination; + Operand src1 = node.GetSource(0); + + // The multiply instruction (that maps to IMUL) is somewhat special, it has + // a three operand form where the second source is a immediate value. + bool threeOperandForm = inst == Instruction.Multiply && node.GetSource(1).Kind == OperandKind.Constant; + + if (IsSameOperandDestSrc1(node) && src1.Kind == OperandKind.LocalVariable && !threeOperandForm) + { + bool useNewLocal = false; + + for (int srcIndex = 1; srcIndex < node.SourcesCount; srcIndex++) + { + if (node.GetSource(srcIndex) == dest) + { + useNewLocal = true; + + break; + } + } + + if (useNewLocal) + { + // Dest is being used as some source already, we need to use a new + // local to store the temporary value, otherwise the value on dest + // local would be overwritten. + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, src1)); + + node.SetSource(0, temp); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, temp)); + + node.Destination = temp; + } + else + { + nodes.AddBefore(node, Operation(Instruction.Copy, dest, src1)); + + node.SetSource(0, dest); + } + } + else if (inst == Instruction.ConditionalSelect) + { + Operand src2 = node.GetSource(1); + Operand src3 = node.GetSource(2); + + if (src1 == dest || src2 == dest) + { + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, src3)); + + node.SetSource(2, temp); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, temp)); + + node.Destination = temp; + } + else + { + nodes.AddBefore(node, Operation(Instruction.Copy, dest, src3)); + + node.SetSource(2, dest); + } + } + } + + private static void GenerateConvertToFPUI(IntrusiveList nodes, Operation node) + { + // Unsigned integer to FP conversions are not supported on X86. + // We need to turn them into signed integer to FP conversions, and + // adjust the final result. + Operand dest = node.Destination; + Operand source = node.GetSource(0); + + Debug.Assert(source.Type.IsInteger(), $"Invalid source type \"{source.Type}\"."); + + Operation currentNode = node; + + if (source.Type == OperandType.I32) + { + // For 32-bits integers, we can just zero-extend to 64-bits, + // and then use the 64-bits signed conversion instructions. + Operand zex = Local(OperandType.I64); + + node = nodes.AddAfter(node, Operation(Instruction.ZeroExtend32, zex, source)); + nodes.AddAfter(node, Operation(Instruction.ConvertToFP, dest, zex)); + } + else /* if (source.Type == OperandType.I64) */ + { + // For 64-bits integers, we need to do the following: + // - Ensure that the integer has the most significant bit clear. + // -- This can be done by shifting the value right by 1, that is, dividing by 2. + // -- The least significant bit is lost in this case though. + // - We can then convert the shifted value with a signed integer instruction. + // - The result still needs to be corrected after that. + // -- First, we need to multiply the result by 2, as we divided it by 2 before. + // --- This can be done efficiently by adding the result to itself. + // -- Then, we need to add the least significant bit that was shifted out. + // --- We can convert the least significant bit to float, and add it to the result. + Operand lsb = Local(OperandType.I64); + Operand half = Local(OperandType.I64); + + Operand lsbF = Local(dest.Type); + + node = nodes.AddAfter(node, Operation(Instruction.Copy, lsb, source)); + node = nodes.AddAfter(node, Operation(Instruction.Copy, half, source)); + + node = nodes.AddAfter(node, Operation(Instruction.BitwiseAnd, lsb, lsb, Const(1L))); + node = nodes.AddAfter(node, Operation(Instruction.ShiftRightUI, half, half, Const(1))); + + node = nodes.AddAfter(node, Operation(Instruction.ConvertToFP, lsbF, lsb)); + node = nodes.AddAfter(node, Operation(Instruction.ConvertToFP, dest, half)); + + node = nodes.AddAfter(node, Operation(Instruction.Add, dest, dest, dest)); + nodes.AddAfter(node, Operation(Instruction.Add, dest, dest, lsbF)); + } + + Delete(nodes, currentNode); + } + + private static void GenerateNegate(IntrusiveList nodes, Operation node) + { + // There's no SSE FP negate instruction, so we need to transform that into + // a XOR of the value to be negated with a mask with the highest bit set. + // This also produces -0 for a negation of the value 0. + Operand dest = node.Destination; + Operand source = node.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || + dest.Type == OperandType.FP64, $"Invalid destination type \"{dest.Type}\"."); + + Operation currentNode = node; + + Operand res = Local(dest.Type); + + node = nodes.AddAfter(node, Operation(Instruction.VectorOne, res)); + + if (dest.Type == OperandType.FP32) + { + node = nodes.AddAfter(node, Operation(Intrinsic.X86Pslld, res, res, Const(31))); + } + else /* if (dest.Type == OperandType.FP64) */ + { + node = nodes.AddAfter(node, Operation(Intrinsic.X86Psllq, res, res, Const(63))); + } + + node = nodes.AddAfter(node, Operation(Intrinsic.X86Xorps, res, res, source)); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, res)); + + Delete(nodes, currentNode); + } + + private static void GenerateVectorInsert8(IntrusiveList nodes, Operation node) + { + // Handle vector insertion, when SSE 4.1 is not supported. + Operand dest = node.Destination; + Operand src1 = node.GetSource(0); // Vector + Operand src2 = node.GetSource(1); // Value + Operand src3 = node.GetSource(2); // Index + + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + Debug.Assert(index < 16); + + Operation currentNode = node; + + Operand temp1 = Local(OperandType.I32); + Operand temp2 = Local(OperandType.I32); + + node = nodes.AddAfter(node, Operation(Instruction.Copy, temp2, src2)); + + Operation vextOp = Operation(Instruction.VectorExtract16, temp1, src1, Const(index >> 1)); + + node = nodes.AddAfter(node, vextOp); + + if ((index & 1) != 0) + { + node = nodes.AddAfter(node, Operation(Instruction.ZeroExtend8, temp1, temp1)); + node = nodes.AddAfter(node, Operation(Instruction.ShiftLeft, temp2, temp2, Const(8))); + node = nodes.AddAfter(node, Operation(Instruction.BitwiseOr, temp1, temp1, temp2)); + } + else + { + node = nodes.AddAfter(node, Operation(Instruction.ZeroExtend8, temp2, temp2)); + node = nodes.AddAfter(node, Operation(Instruction.BitwiseAnd, temp1, temp1, Const(0xff00))); + node = nodes.AddAfter(node, Operation(Instruction.BitwiseOr, temp1, temp1, temp2)); + } + + Operation vinsOp = Operation(Instruction.VectorInsert16, dest, src1, temp1, Const(index >> 1)); + + nodes.AddAfter(node, vinsOp); + + Delete(nodes, currentNode); + } + + protected static Operand AddXmmCopy(IntrusiveList nodes, Operation node, Operand source) + { + Operand temp = Local(source.Type); + Operand intConst = AddCopy(nodes, node, GetIntConst(source)); + + Operation copyOp = Operation(Instruction.VectorCreateScalar, temp, intConst); + + nodes.AddBefore(node, copyOp); + + return temp; + } + + protected static Operand AddCopy(IntrusiveList nodes, Operation node, Operand source) + { + Operand temp = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, temp, source); + + nodes.AddBefore(node, copyOp); + + return temp; + } + + private static Operand GetIntConst(Operand value) + { + if (value.Type == OperandType.FP32) + { + return Const(value.AsInt32()); + } + else if (value.Type == OperandType.FP64) + { + return Const(value.AsInt64()); + } + + return value; + } + + protected static void Delete(IntrusiveList nodes, Operation node) + { + node.Destination = default; + + for (int index = 0; index < node.SourcesCount; index++) + { + node.SetSource(index, default); + } + + nodes.Remove(node); + } + + protected static Operand Gpr(X86Register register, OperandType type) + { + return Register((int)register, RegisterType.Integer, type); + } + + protected static Operand Xmm(X86Register register, OperandType type) + { + return Register((int)register, RegisterType.Vector, type); + } + + private static bool IsSameOperandDestSrc1(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger(); + case Instruction.Multiply: + case Instruction.Subtract: + return !HardwareCapabilities.SupportsVexEncoding || operation.Destination.Type.IsInteger(); + + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + case Instruction.ByteSwap: + case Instruction.Negate: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + return true; + + case Instruction.Divide: + return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger(); + + case Instruction.VectorInsert: + case Instruction.VectorInsert16: + case Instruction.VectorInsert8: + return !HardwareCapabilities.SupportsVexEncoding; + + case Instruction.Extended: + return IsIntrinsicSameOperandDestSrc1(operation); + } + + return IsVexSameOperandDestSrc1(operation); + } + + private static bool IsIntrinsicSameOperandDestSrc1(Operation operation) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + return info.Type == IntrinsicType.Crc32 || info.Type == IntrinsicType.Fma || IsVexSameOperandDestSrc1(operation); + } + + private static bool IsVexSameOperandDestSrc1(Operation operation) + { + if (IsIntrinsic(operation.Instruction)) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + bool hasVex = HardwareCapabilities.SupportsVexEncoding && Assembler.SupportsVexPrefix(info.Inst); + + bool isUnary = operation.SourcesCount < 2; + + bool hasVecDest = operation.Destination != default && operation.Destination.Type == OperandType.V128; + + return !hasVex && !isUnary && hasVecDest; + } + + return false; + } + + private static bool HasConstSrc1(Instruction inst) + { + return inst switch + { + Instruction.Copy or Instruction.LoadArgument or Instruction.Spill or Instruction.SpillArg => true, + _ => false, + }; + } + + private static bool HasConstSrc2(Instruction inst) + { + switch (inst) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.BranchIf: + case Instruction.Compare: + case Instruction.Multiply: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.Store: + case Instruction.Store16: + case Instruction.Store8: + case Instruction.Subtract: + case Instruction.VectorExtract: + case Instruction.VectorExtract16: + case Instruction.VectorExtract8: + return true; + } + + return false; + } + + private static bool IsCommutative(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.Multiply: + return true; + + case Instruction.BranchIf: + case Instruction.Compare: + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var compType = (Comparison)comp.AsInt32(); + + return compType == Comparison.Equal || compType == Comparison.NotEqual; + } + } + + return false; + } + + private static bool IsIntrinsic(Instruction inst) + { + return inst == Instruction.Extended; + } + + private static bool IsXmmIntrinsic(Operation operation) + { + if (operation.Instruction != Instruction.Extended) + { + return false; + } + + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + return info.Type != IntrinsicType.Crc32; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs b/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs new file mode 100644 index 000000000..e754cb09b --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs @@ -0,0 +1,333 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + class PreAllocatorSystemV : PreAllocator + { + public static void InsertCallCopies(IntrusiveList nodes, Operation node) + { + Operand dest = node.Destination; + + List sources = new() + { + node.GetSource(0), + }; + + int argsCount = node.SourcesCount - 1; + + int intMax = CallingConvention.GetIntArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetVecArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + int stackOffset = 0; + + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(index + 1); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < intMax; + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + Operand offset = Const(stackOffset); + + Operation spillOp = Operation(Instruction.SpillArg, default, offset, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp)); + + stackOffset += source.Type.GetSizeInBytes(); + } + } + + node.SetSources(sources.ToArray()); + + if (dest != default) + { + if (dest.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + Operation operation = node; + + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg)); + nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1))); + + operation.Destination = default; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, dest, retReg); + + nodes.AddAfter(node, copyOp); + + node.Destination = retReg; + } + } + } + + public static void InsertTailcallCopies(IntrusiveList nodes, Operation node) + { + List sources = new() + { + node.GetSource(0), + }; + + int argsCount = node.SourcesCount - 1; + + int intMax = CallingConvention.GetIntArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetVecArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(1 + index); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)"); + } + } + + // The target address must be on the return registers, since we + // don't return anything and it is guaranteed to not be a + // callee saved register (which would be trashed on the epilogue). + Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + + Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0)); + + nodes.AddBefore(node, addrCopyOp); + + sources[0] = retReg; + + node.SetSources(sources.ToArray()); + } + + public static Operation InsertLoadArgumentCopy( + CompilerContext cctx, + ref Span buffer, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + Operand source = node.GetSource(0); + + Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind."); + + int index = source.AsInt32(); + + int intCount = 0; + int vecCount = 0; + + for (int cIndex = 0; cIndex < index; cIndex++) + { + OperandType argType = cctx.FuncArgTypes[cIndex]; + + if (argType.IsInteger()) + { + intCount++; + } + else if (argType == OperandType.V128) + { + intCount += 2; + } + else + { + vecCount++; + } + } + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount(); + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount(); + } + else + { + passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount(); + } + + if (passOnReg) + { + Operand dest = node.Destination; + + if (preservedArgs[index] == default) + { + if (dest.Type == OperandType.V128) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand pArg = Local(OperandType.V128); + + Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64); + Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64); + + Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg); + Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1)); + + cctx.Cfg.Entry.Operations.AddFirst(copyH); + cctx.Cfg.Entry.Operations.AddFirst(copyL); + + preservedArgs[index] = pArg; + } + else + { + Operand pArg = Local(dest.Type); + + Operand argReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + } + + Operation nextNode; + + if (dest.AssignmentsCount == 1) + { + // Let's propagate the argument if we can to avoid copies. + PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]); + nextNode = node.ListNext; + } + else + { + Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]); + nextNode = nodes.AddBefore(node, argCopyOp); + } + + Delete(nodes, node); + return nextNode; + } + else + { + // TODO: Pass on stack. + return node; + } + } + + public static void InsertReturnCopy(IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0) + { + return; + } + + Operand source = node.GetSource(0); + + if (source.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1))); + } + else + { + Operand retReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), source.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), source.Type); + + Operation retCopyOp = Operation(Instruction.Copy, retReg, source); + + nodes.AddBefore(node, retCopyOp); + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs b/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs new file mode 100644 index 000000000..10a2bd129 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs @@ -0,0 +1,327 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + class PreAllocatorWindows : PreAllocator + { + public static void InsertCallCopies(IntrusiveList nodes, StackAllocator stackAlloc, Operation node) + { + Operand dest = node.Destination; + + // Handle struct arguments. + int retArgs = 0; + int stackAllocOffset = 0; + + int AllocateOnStack(int size) + { + // We assume that the stack allocator is initially empty (TotalSize = 0). + // Taking that into account, we can reuse the space allocated for other + // calls by keeping track of our own allocated size (stackAllocOffset). + // If the space allocated is not big enough, then we just expand it. + int offset = stackAllocOffset; + + if (stackAllocOffset + size > stackAlloc.TotalSize) + { + stackAlloc.Allocate((stackAllocOffset + size) - stackAlloc.TotalSize); + } + + stackAllocOffset += size; + + return offset; + } + + Operand arg0Reg = default; + + if (dest != default && dest.Type == OperandType.V128) + { + int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes()); + + arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64); + + Operation allocOp = Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset)); + + nodes.AddBefore(node, allocOp); + + retArgs = 1; + } + + int argsCount = node.SourcesCount - 1; + int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs; + + if (argsCount > maxArgs) + { + argsCount = maxArgs; + } + + Operand[] sources = new Operand[1 + retArgs + argsCount]; + + sources[0] = node.GetSource(0); + + if (arg0Reg != default) + { + sources[1] = arg0Reg; + } + + for (int index = 1; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (source.Type == OperandType.V128) + { + Operand stackAddr = Local(OperandType.I64); + + int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes()); + + nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset))); + + Operation storeOp = Operation(Instruction.Store, default, stackAddr, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, storeOp)); + + node.SetSource(index, stackAddr); + } + } + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(index + 1); + Operand argReg; + + int argIndex = index + retArgs; + + if (source.Type.IsInteger()) + { + argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type); + } + else + { + argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type); + } + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources[1 + retArgs + index] = argReg; + } + + // The remaining arguments (those that are not passed on registers) + // should be passed on the stack, we write them to the stack with "SpillArg". + for (int index = argsCount; index < node.SourcesCount - 1; index++) + { + Operand source = node.GetSource(index + 1); + Operand offset = Const((index + retArgs) * 8); + + Operation spillOp = Operation(Instruction.SpillArg, default, offset, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp)); + } + + if (dest != default) + { + if (dest.Type == OperandType.V128) + { + Operand retValueAddr = Local(OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.Copy, retValueAddr, arg0Reg)); + + Operation loadOp = Operation(Instruction.Load, dest, retValueAddr); + + nodes.AddAfter(node, loadOp); + + node.Destination = default; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, dest, retReg); + + nodes.AddAfter(node, copyOp); + + node.Destination = retReg; + } + } + + node.SetSources(sources); + } + + public static void InsertTailcallCopies(IntrusiveList nodes, Operation node) + { + int argsCount = node.SourcesCount - 1; + int maxArgs = CallingConvention.GetArgumentsOnRegsCount(); + + if (argsCount > maxArgs) + { + throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)"); + } + + Operand[] sources = new Operand[1 + argsCount]; + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(1 + index); + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources[1 + index] = argReg; + } + + // The target address must be on the return registers, since we + // don't return anything and it is guaranteed to not be a + // callee saved register (which would be trashed on the epilogue). + Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + + Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0)); + + nodes.AddBefore(node, addrCopyOp); + + sources[0] = retReg; + + node.SetSources(sources); + } + + public static Operation InsertLoadArgumentCopy( + CompilerContext cctx, + ref Span buffer, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + Operand source = node.GetSource(0); + + Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind."); + + int retArgs = cctx.FuncReturnType == OperandType.V128 ? 1 : 0; + + int index = source.AsInt32() + retArgs; + + if (index < CallingConvention.GetArgumentsOnRegsCount()) + { + Operand dest = node.Destination; + + if (preservedArgs[index] == default) + { + Operand argReg, pArg; + + if (dest.Type.IsInteger()) + { + argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type); + pArg = Local(dest.Type); + } + else if (dest.Type == OperandType.V128) + { + argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64); + pArg = Local(OperandType.I64); + } + else + { + argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type); + pArg = Local(dest.Type); + } + + Operation copyOp = Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + + Operation nextNode; + + if (dest.Type != OperandType.V128 && dest.AssignmentsCount == 1) + { + // Let's propagate the argument if we can to avoid copies. + PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]); + nextNode = node.ListNext; + } + else + { + Operation argCopyOp = Operation(dest.Type == OperandType.V128 + ? Instruction.Load + : Instruction.Copy, dest, preservedArgs[index]); + + nextNode = nodes.AddBefore(node, argCopyOp); + } + + Delete(nodes, node); + return nextNode; + } + else + { + // TODO: Pass on stack. + return node; + } + } + + public static void InsertReturnCopy( + CompilerContext cctx, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + if (node.SourcesCount == 0) + { + return; + } + + Operand source = node.GetSource(0); + Operand retReg; + + if (source.Type.IsInteger()) + { + retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type); + } + else if (source.Type == OperandType.V128) + { + if (preservedArgs[0] == default) + { + Operand preservedArg = Local(OperandType.I64); + Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64); + + Operation copyOp = Operation(Instruction.Copy, preservedArg, arg0); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[0] = preservedArg; + } + + retReg = preservedArgs[0]; + } + else + { + retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type); + } + + if (source.Type == OperandType.V128) + { + Operation retStoreOp = Operation(Instruction.Store, default, retReg, source); + + nodes.AddBefore(node, retStoreOp); + } + else + { + Operation retCopyOp = Operation(Instruction.Copy, retReg, source); + + nodes.AddBefore(node, retCopyOp); + } + + node.SetSources(Array.Empty()); + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/X86Condition.cs b/src/ARMeilleure/CodeGen/X86/X86Condition.cs new file mode 100644 index 000000000..70699a207 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Condition.cs @@ -0,0 +1,49 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.CodeGen.X86 +{ + enum X86Condition + { + Overflow = 0x0, + NotOverflow = 0x1, + Below = 0x2, + AboveOrEqual = 0x3, + Equal = 0x4, + NotEqual = 0x5, + BelowOrEqual = 0x6, + Above = 0x7, + Sign = 0x8, + NotSign = 0x9, + ParityEven = 0xa, + ParityOdd = 0xb, + Less = 0xc, + GreaterOrEqual = 0xd, + LessOrEqual = 0xe, + Greater = 0xf, + } + + static class ComparisonX86Extensions + { + public static X86Condition ToX86Condition(this Comparison comp) + { + return comp switch + { +#pragma warning disable IDE0055 // Disable formatting + Comparison.Equal => X86Condition.Equal, + Comparison.NotEqual => X86Condition.NotEqual, + Comparison.Greater => X86Condition.Greater, + Comparison.LessOrEqual => X86Condition.LessOrEqual, + Comparison.GreaterUI => X86Condition.Above, + Comparison.LessOrEqualUI => X86Condition.BelowOrEqual, + Comparison.GreaterOrEqual => X86Condition.GreaterOrEqual, + Comparison.Less => X86Condition.Less, + Comparison.GreaterOrEqualUI => X86Condition.AboveOrEqual, + Comparison.LessUI => X86Condition.Below, +#pragma warning restore IDE0055 + + _ => throw new ArgumentException(null, nameof(comp)), + }; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/X86Instruction.cs b/src/ARMeilleure/CodeGen/X86/X86Instruction.cs new file mode 100644 index 000000000..e1979011d --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Instruction.cs @@ -0,0 +1,231 @@ +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, + Vfmadd231pd, + Vfmadd231ps, + Vfmadd231sd, + Vfmadd231ss, + Vfmsub231sd, + Vfmsub231ss, + Vfnmadd231pd, + Vfnmadd231ps, + Vfnmadd231sd, + Vfnmadd231ss, + Vfnmsub231sd, + Vfnmsub231ss, + Vpblendvb, + Vpternlogd, + Xor, + Xorpd, + Xorps, + + Count, + } +} diff --git a/src/ARMeilleure/CodeGen/X86/X86Optimizer.cs b/src/ARMeilleure/CodeGen/X86/X86Optimizer.cs new file mode 100644 index 000000000..690ca5043 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Optimizer.cs @@ -0,0 +1,259 @@ +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + static class X86Optimizer + { + private const int MaxConstantUses = 10000; + + public static void RunPass(ControlFlowGraph cfg) + { + var constants = new Dictionary(); + + Operand GetConstantCopy(BasicBlock block, Operation operation, Operand source) + { + // If the constant has many uses, we also force a new constant mov to be added, in order + // to avoid overflow of the counts field (that is limited to 16 bits). + if (!constants.TryGetValue(source.Value, out var constant) || constant.UsesCount > MaxConstantUses) + { + constant = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, constant, source); + + block.Operations.AddBefore(operation, copyOp); + + constants[source.Value] = constant; + } + + return constant; + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + constants.Clear(); + + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + // Insert copies for constants that can't fit on a 32-bits immediate. + // Doing this early unblocks a few optimizations. + if (node.Instruction == Instruction.Add) + { + Operand src1 = node.GetSource(0); + Operand src2 = node.GetSource(1); + + if (src1.Kind == OperandKind.Constant && (src1.Relocatable || CodeGenCommon.IsLongConst(src1))) + { + node.SetSource(0, GetConstantCopy(block, node, src1)); + } + + if (src2.Kind == OperandKind.Constant && (src2.Relocatable || CodeGenCommon.IsLongConst(src2))) + { + node.SetSource(1, GetConstantCopy(block, node, src2)); + } + } + + // Try to fold something like: + // shl rbx, 2 + // add rax, rbx + // add rax, 0xcafe + // mov rax, [rax] + // Into: + // mov rax, [rax+rbx*4+0xcafe] + if (IsMemoryLoadOrStore(node.Instruction)) + { + OperandType type; + + if (node.Destination != default) + { + type = node.Destination.Type; + } + else + { + type = node.GetSource(1).Type; + } + + Operand memOp = GetMemoryOperandOrNull(node.GetSource(0), type); + + if (memOp != default) + { + node.SetSource(0, memOp); + } + } + } + } + + Optimizer.RemoveUnusedNodes(cfg); + } + + private static Operand GetMemoryOperandOrNull(Operand addr, OperandType type) + { + Operand baseOp = addr; + + // First we check if the address is the result of a local X with 32-bits immediate + // addition. If that is the case, then the baseOp is X, and the memory operand immediate + // becomes the addition immediate. Otherwise baseOp keeps being the address. + int imm = GetConstOp(ref baseOp); + + // Now we check if the baseOp is the result of a local Y with a local Z addition. + // If that is the case, we now set baseOp to Y and indexOp to Z. We further check + // if Z is the result of a left shift of local W by a value >= 0 and <= 3, if that + // is the case, we set indexOp to W and adjust the scale value of the memory operand + // to match that of the left shift. + // There is one missed case, which is the address being a shift result, but this is + // probably not worth optimizing as it should never happen. + (Operand indexOp, Multiplier scale) = GetIndexOp(ref baseOp); + + // If baseOp is still equal to address, then there's nothing that can be optimized. + if (baseOp == addr) + { + return default; + } + + if (imm == 0 && scale == Multiplier.x1 && indexOp != default) + { + imm = GetConstOp(ref indexOp); + } + + return MemoryOp(type, baseOp, indexOp, scale, imm); + } + + private static int GetConstOp(ref Operand baseOp) + { + Operation operation = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (operation == default) + { + return 0; + } + + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + Operand constOp; + Operand otherOp; + + if (src1.Kind == OperandKind.Constant && src2.Kind == OperandKind.LocalVariable) + { + constOp = src1; + otherOp = src2; + } + else if (src1.Kind == OperandKind.LocalVariable && src2.Kind == OperandKind.Constant) + { + constOp = src2; + otherOp = src1; + } + else + { + return 0; + } + + // If we have addition by 64-bits constant, then we can't optimize it further, + // as we can't encode a 64-bits immediate on the memory operand. + if (CodeGenCommon.IsLongConst(constOp)) + { + return 0; + } + + baseOp = otherOp; + + return constOp.AsInt32(); + } + + private static (Operand, Multiplier) GetIndexOp(ref Operand baseOp) + { + Operand indexOp = default; + + Multiplier scale = Multiplier.x1; + + Operation addOp = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (addOp == default) + { + return (indexOp, scale); + } + + Operand src1 = addOp.GetSource(0); + Operand src2 = addOp.GetSource(1); + + if (src1.Kind != OperandKind.LocalVariable || src2.Kind != OperandKind.LocalVariable) + { + return (indexOp, scale); + } + + baseOp = src1; + indexOp = src2; + + Operation shlOp = GetAsgOpWithInst(src1, Instruction.ShiftLeft); + + bool indexOnSrc2 = false; + + if (shlOp == default) + { + shlOp = GetAsgOpWithInst(src2, Instruction.ShiftLeft); + + indexOnSrc2 = true; + } + + if (shlOp != default) + { + Operand shSrc = shlOp.GetSource(0); + Operand shift = shlOp.GetSource(1); + + if (shSrc.Kind == OperandKind.LocalVariable && shift.Kind == OperandKind.Constant && shift.Value <= 3) + { + scale = shift.Value switch + { + 1 => Multiplier.x2, + 2 => Multiplier.x4, + 3 => Multiplier.x8, + _ => Multiplier.x1, + }; + + baseOp = indexOnSrc2 ? src1 : src2; + indexOp = shSrc; + } + } + + return (indexOp, scale); + } + + private static Operation GetAsgOpWithInst(Operand op, Instruction inst) + { + // If we have multiple assignments, folding is not safe + // as the value may be different depending on the + // control flow path. + if (op.AssignmentsCount != 1) + { + return default; + } + + Operation asgOp = op.Assignments[0]; + + if (asgOp.Instruction != inst) + { + return default; + } + + return asgOp; + } + + private static bool IsMemoryLoadOrStore(Instruction inst) + { + return inst == Instruction.Load || + inst == Instruction.Load16 || + inst == Instruction.Load8 || + inst == Instruction.Store || + inst == Instruction.Store16 || + inst == Instruction.Store8; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/X86Register.cs b/src/ARMeilleure/CodeGen/X86/X86Register.cs new file mode 100644 index 000000000..0a6563663 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Register.cs @@ -0,0 +1,44 @@ +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.CodeGen.X86 +{ + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum X86Register + { + Invalid = -1, + + Rax = 0, + Rcx = 1, + Rdx = 2, + Rbx = 3, + Rsp = 4, + Rbp = 5, + Rsi = 6, + Rdi = 7, + R8 = 8, + R9 = 9, + R10 = 10, + R11 = 11, + R12 = 12, + R13 = 13, + R14 = 14, + R15 = 15, + + Xmm0 = 0, + Xmm1 = 1, + Xmm2 = 2, + Xmm3 = 3, + Xmm4 = 4, + Xmm5 = 5, + Xmm6 = 6, + Xmm7 = 7, + Xmm8 = 8, + Xmm9 = 9, + Xmm10 = 10, + Xmm11 = 11, + Xmm12 = 12, + Xmm13 = 13, + Xmm14 = 14, + Xmm15 = 15, + } +} diff --git a/src/ARMeilleure/Common/AddressTable.cs b/src/ARMeilleure/Common/AddressTable.cs new file mode 100644 index 000000000..c9b0062b5 --- /dev/null +++ b/src/ARMeilleure/Common/AddressTable.cs @@ -0,0 +1,252 @@ +using ARMeilleure.Diagnostics; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Common +{ + /// + /// Represents a table of guest address to a value. + /// + /// Type of the value + unsafe class AddressTable : IDisposable where TEntry : unmanaged + { + /// + /// Represents a level in an . + /// + public readonly struct Level + { + /// + /// Gets the index of the in the guest address. + /// + public int Index { get; } + + /// + /// Gets the length of the in the guest address. + /// + public int Length { get; } + + /// + /// Gets the mask which masks the bits used by the . + /// + public ulong Mask => ((1ul << Length) - 1) << Index; + + /// + /// Initializes a new instance of the structure with the specified + /// and . + /// + /// Index of the + /// Length of the + public Level(int index, int length) + { + (Index, Length) = (index, length); + } + + /// + /// Gets the value of the from the specified guest . + /// + /// Guest address + /// Value of the from the specified guest + public int GetValue(ulong address) + { + return (int)((address & Mask) >> Index); + } + } + + private bool _disposed; + private TEntry** _table; + private readonly List _pages; + + /// + /// Gets the bits used by the of the instance. + /// + public ulong Mask { get; } + + /// + /// Gets the s used by the instance. + /// + public Level[] Levels { get; } + + /// + /// Gets or sets the default fill value of newly created leaf pages. + /// + public TEntry Fill { get; set; } + + /// + /// Gets the base address of the . + /// + /// instance was disposed + public IntPtr Base + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_pages) + { + return (IntPtr)GetRootPage(); + } + } + } + + /// + /// Constructs a new instance of the class with the specified list of + /// . + /// + /// is null + /// Length of is less than 2 + public AddressTable(Level[] levels) + { + ArgumentNullException.ThrowIfNull(levels); + + if (levels.Length < 2) + { + throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels)); + } + + _pages = new List(capacity: 16); + + Levels = levels; + Mask = 0; + + foreach (var level in Levels) + { + Mask |= level.Mask; + } + } + + /// + /// Determines if the specified is in the range of the + /// . + /// + /// Guest address + /// if is valid; otherwise + public bool IsValid(ulong address) + { + return (address & ~Mask) == 0; + } + + /// + /// Gets a reference to the value at the specified guest . + /// + /// Guest address + /// Reference to the value at the specified guest + /// instance was disposed + /// is not mapped + public ref TEntry GetValue(ulong address) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (!IsValid(address)) + { + throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address)); + } + + lock (_pages) + { + return ref GetPage(address)[Levels[^1].GetValue(address)]; + } + } + + /// + /// Gets the leaf page for the specified guest . + /// + /// Guest address + /// Leaf page for the specified guest + private TEntry* GetPage(ulong address) + { + TEntry** page = GetRootPage(); + + for (int i = 0; i < Levels.Length - 1; i++) + { + ref Level level = ref Levels[i]; + ref TEntry* nextPage = ref page[level.GetValue(address)]; + + if (nextPage == null) + { + ref Level nextLevel = ref Levels[i + 1]; + + nextPage = i == Levels.Length - 2 ? + (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) : + (TEntry*)Allocate(1 << nextLevel.Length, IntPtr.Zero, leaf: false); + } + + page = (TEntry**)nextPage; + } + + return (TEntry*)page; + } + + /// + /// Lazily initialize and get the root page of the . + /// + /// Root page of the + private TEntry** GetRootPage() + { + if (_table == null) + { + _table = (TEntry**)Allocate(1 << Levels[0].Length, fill: IntPtr.Zero, leaf: false); + } + + return _table; + } + + /// + /// Allocates a block of memory of the specified type and length. + /// + /// Type of elements + /// Number of elements + /// Fill value + /// if leaf; otherwise + /// Allocated block + private IntPtr Allocate(int length, T fill, bool leaf) where T : unmanaged + { + var size = sizeof(T) * length; + var page = (IntPtr)NativeAllocator.Instance.Allocate((uint)size); + var span = new Span((void*)page, length); + + span.Fill(fill); + + _pages.Add(page); + + TranslatorEventSource.Log.AddressTableAllocated(size, leaf); + + return page; + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the + /// instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + foreach (var page in _pages) + { + Marshal.FreeHGlobal(page); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~AddressTable() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/Allocator.cs b/src/ARMeilleure/Common/Allocator.cs new file mode 100644 index 000000000..6905a614f --- /dev/null +++ b/src/ARMeilleure/Common/Allocator.cs @@ -0,0 +1,24 @@ +using System; + +namespace ARMeilleure.Common +{ + unsafe abstract class Allocator : IDisposable + { + public T* Allocate(ulong count = 1) where T : unmanaged + { + return (T*)Allocate(count * (uint)sizeof(T)); + } + + public abstract void* Allocate(ulong size); + + public abstract void Free(void* block); + + protected virtual void Dispose(bool disposing) { } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/ARMeilleure/Common/ArenaAllocator.cs b/src/ARMeilleure/Common/ArenaAllocator.cs new file mode 100644 index 000000000..ce8e33913 --- /dev/null +++ b/src/ARMeilleure/Common/ArenaAllocator.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.Common +{ + unsafe sealed class ArenaAllocator : Allocator + { + private class PageInfo + { + public byte* Pointer; + public byte Unused; + public int UnusedCounter; + } + + private int _lastReset; + private ulong _index; + private int _pageIndex; + private PageInfo _page; + private List _pages; + private readonly ulong _pageSize; + private readonly uint _pageCount; + private readonly List _extras; + + public ArenaAllocator(uint pageSize, uint pageCount) + { + _lastReset = Environment.TickCount; + + // Set _index to pageSize so that the first allocation goes through the slow path. + _index = pageSize; + _pageIndex = -1; + + _page = null; + _pages = new List(); + _pageSize = pageSize; + _pageCount = pageCount; + + _extras = new List(); + } + + public Span AllocateSpan(ulong count) where T : unmanaged + { + return new Span(Allocate(count), (int)count); + } + + public override void* Allocate(ulong size) + { + if (_index + size <= _pageSize) + { + byte* result = _page.Pointer + _index; + + _index += size; + + return result; + } + + return AllocateSlow(size); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void* AllocateSlow(ulong size) + { + if (size > _pageSize) + { + void* extra = NativeAllocator.Instance.Allocate(size); + + _extras.Add((IntPtr)extra); + + return extra; + } + + if (_index + size > _pageSize) + { + _index = 0; + _pageIndex++; + } + + if (_pageIndex < _pages.Count) + { + _page = _pages[_pageIndex]; + _page.Unused = 0; + } + else + { + _page = new PageInfo + { + Pointer = (byte*)NativeAllocator.Instance.Allocate(_pageSize), + }; + + _pages.Add(_page); + } + + byte* result = _page.Pointer + _index; + + _index += size; + + return result; + } + + public override void Free(void* block) { } + + public void Reset() + { + _index = _pageSize; + _pageIndex = -1; + _page = null; + + // Free excess pages that was allocated. + while (_pages.Count > _pageCount) + { + NativeAllocator.Instance.Free(_pages[^1].Pointer); + + _pages.RemoveAt(_pages.Count - 1); + } + + // Free extra blocks that are not page-sized + foreach (IntPtr ptr in _extras) + { + NativeAllocator.Instance.Free((void*)ptr); + } + + _extras.Clear(); + + // Free pooled pages that has not been used in a while. Remove pages at the back first, because we try to + // keep the pages at the front alive, since they're more likely to be hot and in the d-cache. + bool removing = true; + + // If arena is used frequently, keep pages for longer. Otherwise keep pages for a shorter amount of time. + int now = Environment.TickCount; + int count = (now - _lastReset) switch + { + >= 5000 => 0, + >= 2500 => 50, + >= 1000 => 100, + >= 10 => 1500, + _ => 5000, + }; + + for (int i = _pages.Count - 1; i >= 0; i--) + { + PageInfo page = _pages[i]; + + if (page.Unused == 0) + { + page.UnusedCounter = 0; + } + + page.UnusedCounter += page.Unused; + page.Unused = 1; + + // If page not used after `count` resets, remove it. + if (removing && page.UnusedCounter >= count) + { + NativeAllocator.Instance.Free(page.Pointer); + + _pages.RemoveAt(i); + } + else + { + removing = false; + } + } + + _lastReset = now; + } + + protected override void Dispose(bool disposing) + { + if (_pages != null) + { + foreach (PageInfo info in _pages) + { + NativeAllocator.Instance.Free(info.Pointer); + } + + foreach (IntPtr ptr in _extras) + { + NativeAllocator.Instance.Free((void*)ptr); + } + + _pages = null; + } + } + + ~ArenaAllocator() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/BitMap.cs b/src/ARMeilleure/Common/BitMap.cs new file mode 100644 index 000000000..94d47ea59 --- /dev/null +++ b/src/ARMeilleure/Common/BitMap.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.Common +{ + unsafe class BitMap : IEnumerable, IDisposable + { + private const int IntSize = 64; + private const int IntMask = IntSize - 1; + + private int _count; + private long* _masks; + private readonly Allocator _allocator; + + public BitMap(Allocator allocator) + { + _allocator = allocator; + } + + public BitMap(Allocator allocator, int capacity) : this(allocator) + { + EnsureCapacity(capacity); + } + + public bool Set(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + if ((_masks[wordIndex] & wordMask) != 0) + { + return false; + } + + _masks[wordIndex] |= wordMask; + + return true; + } + + public void Clear(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + _masks[wordIndex] &= ~wordMask; + } + + public bool IsSet(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + return (_masks[wordIndex] & (1L << wordBit)) != 0; + } + + public int FindFirstUnset() + { + for (int index = 0; index < _count; index++) + { + long mask = _masks[index]; + + if (mask != -1L) + { + return BitOperations.TrailingZeroCount(~mask) + index * IntSize; + } + } + + return _count * IntSize; + } + + public bool Set(BitMap map) + { + EnsureCapacity(map._count * IntSize); + + bool modified = false; + + for (int index = 0; index < _count; index++) + { + long newValue = _masks[index] | map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + public bool Clear(BitMap map) + { + EnsureCapacity(map._count * IntSize); + + bool modified = false; + + for (int index = 0; index < _count; index++) + { + long newValue = _masks[index] & ~map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + private void EnsureCapacity(int size) + { + int count = (size + IntMask) / IntSize; + + if (count > _count) + { + var oldMask = _masks; + var oldSpan = new Span(_masks, _count); + + _masks = _allocator.Allocate((uint)count); + _count = count; + + var newSpan = new Span(_masks, _count); + + oldSpan.CopyTo(newSpan); + newSpan[oldSpan.Length..].Clear(); + + _allocator.Free(oldMask); + } + } + + public void Dispose() + { + if (_masks != null) + { + _allocator.Free(_masks); + + _masks = null; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public struct Enumerator : IEnumerator + { + private long _index; + private long _mask; + private int _bit; + private readonly BitMap _map; + + public readonly int Current => (int)_index * IntSize + _bit; + readonly object IEnumerator.Current => Current; + + public Enumerator(BitMap map) + { + _index = -1; + _mask = 0; + _bit = 0; + _map = map; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + if (_mask != 0) + { + _mask &= ~(1L << _bit); + } + + // Manually hoist these loads, because RyuJIT does not. + long count = (uint)_map._count; + long* masks = _map._masks; + + while (_mask == 0) + { + if (++_index >= count) + { + return false; + } + + _mask = masks[_index]; + } + + _bit = BitOperations.TrailingZeroCount(_mask); + + return true; + } + + public readonly void Reset() { } + + public readonly void Dispose() { } + } + } +} diff --git a/src/ARMeilleure/Common/BitUtils.cs b/src/ARMeilleure/Common/BitUtils.cs new file mode 100644 index 000000000..e7697ff31 --- /dev/null +++ b/src/ARMeilleure/Common/BitUtils.cs @@ -0,0 +1,57 @@ +using System; +using System.Numerics; + +namespace ARMeilleure.Common +{ + static class BitUtils + { + private static ReadOnlySpan HbsNibbleLut => new sbyte[] { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 }; + + public static long FillWithOnes(int bits) + { + return bits == 64 ? -1L : (1L << bits) - 1; + } + + public static int HighestBitSet(int value) + { + return 31 - BitOperations.LeadingZeroCount((uint)value); + } + + public static int HighestBitSetNibble(int value) + { + return HbsNibbleLut[value]; + } + + public static long Replicate(long bits, int size) + { + long output = 0; + + for (int bit = 0; bit < 64; bit += size) + { + output |= bits << bit; + } + + return output; + } + + public static int RotateRight(int bits, int shift, int size) + { + return (int)RotateRight((uint)bits, shift, size); + } + + public static uint RotateRight(uint bits, int shift, int size) + { + return (bits >> shift) | (bits << (size - shift)); + } + + public static long RotateRight(long bits, int shift, int size) + { + return (long)RotateRight((ulong)bits, shift, size); + } + + public static ulong RotateRight(ulong bits, int shift, int size) + { + return (bits >> shift) | (bits << (size - shift)); + } + } +} diff --git a/src/ARMeilleure/Common/Counter.cs b/src/ARMeilleure/Common/Counter.cs new file mode 100644 index 000000000..6db9561ca --- /dev/null +++ b/src/ARMeilleure/Common/Counter.cs @@ -0,0 +1,98 @@ +using System; + +namespace ARMeilleure.Common +{ + /// + /// Represents a numeric counter which can be used for instrumentation of compiled code. + /// + /// Type of the counter + class Counter : IDisposable where T : unmanaged + { + private bool _disposed; + /// + /// Index in the + /// + private readonly int _index; + private readonly EntryTable _countTable; + + /// + /// Initializes a new instance of the class from the specified + /// instance and index. + /// + /// instance + /// is + /// is unsupported + public Counter(EntryTable countTable) + { + if (typeof(T) != typeof(byte) && typeof(T) != typeof(sbyte) && + typeof(T) != typeof(short) && typeof(T) != typeof(ushort) && + typeof(T) != typeof(int) && typeof(T) != typeof(uint) && + typeof(T) != typeof(long) && typeof(T) != typeof(ulong) && + typeof(T) != typeof(nint) && typeof(T) != typeof(nuint) && + typeof(T) != typeof(float) && typeof(T) != typeof(double)) + { + throw new ArgumentException("Counter does not support the specified type."); + } + + _countTable = countTable ?? throw new ArgumentNullException(nameof(countTable)); + _index = countTable.Allocate(); + } + + /// + /// Gets a reference to the value of the counter. + /// + /// instance was disposed + /// + /// This can refer to freed memory if the owning is disposed. + /// + public ref T Value + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return ref _countTable.GetValue(_index); + } + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resources + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + try + { + // The index into the EntryTable is essentially an unmanaged resource since we allocate and free the + // resource ourselves. + _countTable.Free(_index); + } + catch (ObjectDisposedException) + { + // Can happen because _countTable may be disposed before the Counter instance. + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~Counter() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/EntryTable.cs b/src/ARMeilleure/Common/EntryTable.cs new file mode 100644 index 000000000..625e3f73f --- /dev/null +++ b/src/ARMeilleure/Common/EntryTable.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace ARMeilleure.Common +{ + /// + /// Represents an expandable table of the type , whose entries will remain at the same + /// address through out the table's lifetime. + /// + /// Type of the entry in the table + class EntryTable : IDisposable where TEntry : unmanaged + { + private bool _disposed; + private int _freeHint; + private readonly int _pageCapacity; // Number of entries per page. + private readonly int _pageLogCapacity; + private readonly Dictionary _pages; + private readonly BitMap _allocated; + + /// + /// Initializes a new instance of the class with the desired page size in + /// bytes. + /// + /// Desired page size in bytes + /// is less than 0 + /// 's size is zero + /// + /// The actual page size may be smaller or larger depending on the size of . + /// + public unsafe EntryTable(int pageSize = 4096) + { + if (pageSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize), "Page size cannot be negative."); + } + + if (sizeof(TEntry) == 0) + { + throw new ArgumentException("Size of TEntry cannot be zero."); + } + + _allocated = new BitMap(NativeAllocator.Instance); + _pages = new Dictionary(); + _pageLogCapacity = BitOperations.Log2((uint)(pageSize / sizeof(TEntry))); + _pageCapacity = 1 << _pageLogCapacity; + } + + /// + /// Allocates an entry in the . + /// + /// Index of entry allocated in the table + /// instance was disposed + public int Allocate() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (_allocated.IsSet(_freeHint)) + { + _freeHint = _allocated.FindFirstUnset(); + } + + int index = _freeHint++; + var page = GetPage(index); + + _allocated.Set(index); + + GetValue(page, index) = default; + + return index; + } + } + + /// + /// Frees the entry at the specified . + /// + /// Index of entry to free + /// instance was disposed + public void Free(int index) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (_allocated.IsSet(index)) + { + _allocated.Clear(index); + + _freeHint = index; + } + } + } + + /// + /// Gets a reference to the entry at the specified allocated . + /// + /// Index of the entry + /// Reference to the entry at the specified + /// instance was disposed + /// Entry at is not allocated + public ref TEntry GetValue(int index) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (!_allocated.IsSet(index)) + { + throw new ArgumentException("Entry at the specified index was not allocated", nameof(index)); + } + + var page = GetPage(index); + + return ref GetValue(page, index); + } + } + + /// + /// Gets a reference to the entry at using the specified from the specified + /// . + /// + /// Page to use + /// Index to use + /// Reference to the entry + private ref TEntry GetValue(Span page, int index) + { + return ref page[index & (_pageCapacity - 1)]; + } + + /// + /// Gets the page for the specified . + /// + /// Index to use + /// Page for the specified + private unsafe Span GetPage(int index) + { + var pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity); + + if (!_pages.TryGetValue(pageIndex, out IntPtr page)) + { + page = (IntPtr)NativeAllocator.Instance.Allocate((uint)sizeof(TEntry) * (uint)_pageCapacity); + + _pages.Add(pageIndex, page); + } + + return new Span((void*)page, _pageCapacity); + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the + /// instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected unsafe virtual void Dispose(bool disposing) + { + if (!_disposed) + { + _allocated.Dispose(); + + foreach (var page in _pages.Values) + { + NativeAllocator.Instance.Free((void*)page); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~EntryTable() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/EnumUtils.cs b/src/ARMeilleure/Common/EnumUtils.cs new file mode 100644 index 000000000..2a4aa645b --- /dev/null +++ b/src/ARMeilleure/Common/EnumUtils.cs @@ -0,0 +1,12 @@ +using System; + +namespace ARMeilleure.Common +{ + static class EnumUtils + { + public static int GetCount(Type enumType) + { + return Enum.GetNames(enumType).Length; + } + } +} diff --git a/src/ARMeilleure/Common/NativeAllocator.cs b/src/ARMeilleure/Common/NativeAllocator.cs new file mode 100644 index 000000000..93c48adda --- /dev/null +++ b/src/ARMeilleure/Common/NativeAllocator.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Common +{ + unsafe sealed class NativeAllocator : Allocator + { + public static NativeAllocator Instance { get; } = new(); + + public override void* Allocate(ulong size) + { + void* result = (void*)Marshal.AllocHGlobal((IntPtr)size); + + if (result == null) + { + throw new OutOfMemoryException(); + } + + return result; + } + + public override void Free(void* block) + { + Marshal.FreeHGlobal((IntPtr)block); + } + } +} diff --git a/src/ARMeilleure/Decoders/Block.cs b/src/ARMeilleure/Decoders/Block.cs new file mode 100644 index 000000000..bb88170da --- /dev/null +++ b/src/ARMeilleure/Decoders/Block.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Decoders +{ + class Block + { + public ulong Address { get; set; } + public ulong EndAddress { get; set; } + + public Block Next { get; set; } + public Block Branch { get; set; } + + public bool Exit { get; set; } + + public List OpCodes { get; } + + public Block() + { + OpCodes = new List(); + } + + public Block(ulong address) : this() + { + Address = address; + } + + public void Split(Block rightBlock) + { + int splitIndex = BinarySearch(OpCodes, rightBlock.Address); + + if (OpCodes[splitIndex].Address < rightBlock.Address) + { + splitIndex++; + } + + int splitCount = OpCodes.Count - splitIndex; + + if (splitCount <= 0) + { + throw new ArgumentException("Can't split at right block address."); + } + + rightBlock.EndAddress = EndAddress; + + rightBlock.Next = Next; + rightBlock.Branch = Branch; + + rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); + + EndAddress = rightBlock.Address; + + Next = rightBlock; + Branch = null; + + OpCodes.RemoveRange(splitIndex, splitCount); + } + + private static int BinarySearch(List opCodes, ulong address) + { + int left = 0; + int middle = 0; + int right = opCodes.Count - 1; + + while (left <= right) + { + int size = right - left; + + middle = left + (size >> 1); + + OpCode opCode = opCodes[middle]; + + if (address == (ulong)opCode.Address) + { + break; + } + + if (address < (ulong)opCode.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return middle; + } + + public OpCode GetLastOp() + { + if (OpCodes.Count > 0) + { + return OpCodes[^1]; + } + + return null; + } + } +} diff --git a/src/ARMeilleure/Decoders/Condition.cs b/src/ARMeilleure/Decoders/Condition.cs new file mode 100644 index 000000000..961825a10 --- /dev/null +++ b/src/ARMeilleure/Decoders/Condition.cs @@ -0,0 +1,32 @@ +namespace ARMeilleure.Decoders +{ + enum Condition + { + Eq = 0, + Ne = 1, + GeUn = 2, + LtUn = 3, + Mi = 4, + Pl = 5, + Vs = 6, + Vc = 7, + GtUn = 8, + LeUn = 9, + Ge = 10, + Lt = 11, + Gt = 12, + Le = 13, + Al = 14, + Nv = 15, + } + + static class ConditionExtensions + { + public static Condition Invert(this Condition cond) + { + // Bit 0 of all conditions is basically a negation bit, so + // inverting this bit has the effect of inverting the condition. + return (Condition)((int)cond ^ 1); + } + } +} diff --git a/src/ARMeilleure/Decoders/DataOp.cs b/src/ARMeilleure/Decoders/DataOp.cs new file mode 100644 index 000000000..f99fd5e70 --- /dev/null +++ b/src/ARMeilleure/Decoders/DataOp.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + enum DataOp + { + Adr = 0, + Arithmetic = 1, + Logical = 2, + BitField = 3, + } +} diff --git a/src/ARMeilleure/Decoders/Decoder.cs b/src/ARMeilleure/Decoders/Decoder.cs new file mode 100644 index 000000000..66d286928 --- /dev/null +++ b/src/ARMeilleure/Decoders/Decoder.cs @@ -0,0 +1,393 @@ +using ARMeilleure.Decoders.Optimizations; +using ARMeilleure.Instructions; +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.Decoders +{ + static class Decoder + { + // We define a limit on the number of instructions that a function may have, + // this prevents functions being potentially too large, which would + // take too long to compile and use too much memory. + private const int MaxInstsPerFunction = 2500; + + // For lower code quality translation, we set a lower limit since we're blocking execution. + private const int MaxInstsPerFunctionLowCq = 500; + + public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, DecoderMode dMode) + { + List blocks = new(); + + Queue workQueue = new(); + + Dictionary visited = new(); + + Debug.Assert(MaxInstsPerFunctionLowCq <= MaxInstsPerFunction); + + int opsCount = 0; + + int instructionLimit = highCq ? MaxInstsPerFunction : MaxInstsPerFunctionLowCq; + + Block GetBlock(ulong blkAddress) + { + if (!visited.TryGetValue(blkAddress, out Block block)) + { + block = new Block(blkAddress); + + if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || + opsCount > instructionLimit || + (visited.Count > 0 && !memory.IsMapped(blkAddress))) + { + block.Exit = true; + block.EndAddress = blkAddress; + } + + workQueue.Enqueue(block); + + visited.Add(blkAddress, block); + } + + return block; + } + + GetBlock(address); + + while (workQueue.TryDequeue(out Block currBlock)) + { + // Check if the current block is inside another block. + if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex)) + { + Block nBlock = blocks[nBlkIndex]; + + if (nBlock.Address == currBlock.Address) + { + throw new InvalidOperationException("Found duplicate block address on the list."); + } + + currBlock.Exit = false; + + nBlock.Split(currBlock); + + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + if (!currBlock.Exit) + { + // If we have a block after the current one, set the limit address. + ulong limitAddress = ulong.MaxValue; + + if (nBlkIndex != blocks.Count) + { + Block nBlock = blocks[nBlkIndex]; + + int nextIndex = nBlkIndex + 1; + + if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count) + { + limitAddress = blocks[nextIndex].Address; + } + else if (nBlock.Address > currBlock.Address) + { + limitAddress = blocks[nBlkIndex].Address; + } + } + + if (dMode == DecoderMode.SingleInstruction) + { + // Only read at most one instruction + limitAddress = currBlock.Address + 1; + } + + FillBlock(memory, mode, currBlock, limitAddress); + + opsCount += currBlock.OpCodes.Count; + + if (currBlock.OpCodes.Count != 0) + { + // Set child blocks. "Branch" is the block the branch instruction + // points to (when taken), "Next" is the block at the next address, + // executed when the branch is not taken. For Unconditional Branches + // (except BL/BLR that are sub calls) or end of executable, Next is null. + OpCode lastOp = currBlock.GetLastOp(); + + bool isCall = IsCall(lastOp); + + if (lastOp is IOpCodeBImm op && !isCall) + { + currBlock.Branch = GetBlock((ulong)op.Immediate); + } + + if (isCall || !(IsUnconditionalBranch(lastOp) || IsTrap(lastOp))) + { + currBlock.Next = GetBlock(currBlock.EndAddress); + } + } + } + + // Insert the new block on the list (sorted by address). + if (blocks.Count != 0) + { + Block nBlock = blocks[nBlkIndex]; + + blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock); + } + else + { + blocks.Add(currBlock); + } + } + + if (blocks.Count == 1 && blocks[0].OpCodes.Count == 0) + { + Debug.Assert(blocks[0].Exit); + Debug.Assert(blocks[0].Address == blocks[0].EndAddress); + + throw new InvalidOperationException($"Decoded a single empty exit block. Entry point = 0x{address:X}."); + } + + if (dMode == DecoderMode.MultipleBlocks) + { + return TailCallRemover.RunPass(address, blocks); + } + else + { + return blocks.ToArray(); + } + } + + public static bool BinarySearch(List blocks, ulong address, out int index) + { + index = 0; + + int left = 0; + int right = blocks.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Block block = blocks[middle]; + + index = middle; + + if (address >= block.Address && address < block.EndAddress) + { + return true; + } + + if (address < block.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return false; + } + + private static void FillBlock( + IMemoryManager memory, + ExecutionMode mode, + Block block, + ulong limitAddress) + { + ulong address = block.Address; + int itBlockSize = 0; + + OpCode opCode; + + do + { + if (address >= limitAddress && itBlockSize == 0) + { + break; + } + + opCode = DecodeOpCode(memory, address, mode); + + block.OpCodes.Add(opCode); + + address += (ulong)opCode.OpCodeSizeInBytes; + + if (opCode is OpCodeT16IfThen it) + { + itBlockSize = it.IfThenBlockSize; + } + else if (itBlockSize > 0) + { + itBlockSize--; + } + } + while (!(IsBranch(opCode) || IsException(opCode))); + + block.EndAddress = address; + } + + private static bool IsBranch(OpCode opCode) + { + return opCode is OpCodeBImm || + opCode is OpCodeBReg || IsAarch32Branch(opCode); + } + + private static bool IsUnconditionalBranch(OpCode opCode) + { + return opCode is OpCodeBImmAl || + opCode is OpCodeBReg || IsAarch32UnconditionalBranch(opCode); + } + + private static bool IsAarch32UnconditionalBranch(OpCode opCode) + { + if (opCode is not OpCode32 op) + { + return false; + } + + // Compare and branch instructions are always conditional. + if (opCode.Instruction.Name == InstName.Cbz || + opCode.Instruction.Name == InstName.Cbnz) + { + return false; + } + + // Note: On ARM32, most instructions have conditional execution, + // so there's no "Always" (unconditional) branch like on ARM64. + // We need to check if the condition is "Always" instead. + return IsAarch32Branch(op) && op.Cond >= Condition.Al; + } + + private static bool IsAarch32Branch(OpCode opCode) + { + // Note: On ARM32, most ALU operations can write to R15 (PC), + // so we must consider such operations as a branch in potential aswell. + if (opCode is IOpCode32Alu opAlu && opAlu.Rd == RegisterAlias.Aarch32Pc) + { + if (opCode is OpCodeT32) + { + return opCode.Instruction.Name != InstName.Tst && opCode.Instruction.Name != InstName.Teq && + opCode.Instruction.Name != InstName.Cmp && opCode.Instruction.Name != InstName.Cmn; + } + return true; + } + + // Same thing for memory operations. We have the cases where PC is a target + // register (Rt == 15 or (mask & (1 << 15)) != 0), and cases where there is + // a write back to PC (wback == true && Rn == 15), however the later may + // be "undefined" depending on the CPU, so compilers should not produce that. + if (opCode is IOpCode32Mem || opCode is IOpCode32MemMult) + { + int rt, rn; + + bool wBack, isLoad; + + if (opCode is IOpCode32Mem opMem) + { + rt = opMem.Rt; + rn = opMem.Rn; + wBack = opMem.WBack; + isLoad = opMem.IsLoad; + + // For the dual load, we also need to take into account the + // case were Rt2 == 15 (PC). + if (rt == 14 && opMem.Instruction.Name == InstName.Ldrd) + { + rt = RegisterAlias.Aarch32Pc; + } + } + else if (opCode is IOpCode32MemMult opMemMult) + { + const int PCMask = 1 << RegisterAlias.Aarch32Pc; + + rt = (opMemMult.RegisterMask & PCMask) != 0 ? RegisterAlias.Aarch32Pc : 0; + rn = opMemMult.Rn; + wBack = opMemMult.PostOffset != 0; + isLoad = opMemMult.IsLoad; + } + else + { + throw new NotImplementedException($"The type \"{opCode.GetType().Name}\" is not implemented on the decoder."); + } + + if ((rt == RegisterAlias.Aarch32Pc && isLoad) || + (rn == RegisterAlias.Aarch32Pc && wBack)) + { + return true; + } + } + + // Explicit branch instructions. + return opCode is IOpCode32BImm || + opCode is IOpCode32BReg; + } + + private static bool IsCall(OpCode opCode) + { + return opCode.Instruction.Name == InstName.Bl || + opCode.Instruction.Name == InstName.Blr || + opCode.Instruction.Name == InstName.Blx; + } + + private static bool IsException(OpCode opCode) + { + return IsTrap(opCode) || opCode.Instruction.Name == InstName.Svc; + } + + private static bool IsTrap(OpCode opCode) + { + return opCode.Instruction.Name == InstName.Brk || + opCode.Instruction.Name == InstName.Trap || + opCode.Instruction.Name == InstName.Und; + } + + public static OpCode DecodeOpCode(IMemoryManager memory, ulong address, ExecutionMode mode) + { + int opCode = memory.Read(address); + + InstDescriptor inst; + + OpCodeTable.MakeOp makeOp; + + if (mode == ExecutionMode.Aarch64) + { + (inst, makeOp) = OpCodeTable.GetInstA64(opCode); + } + else + { + if (mode == ExecutionMode.Aarch32Arm) + { + (inst, makeOp) = OpCodeTable.GetInstA32(opCode); + } + else /* if (mode == ExecutionMode.Aarch32Thumb) */ + { + (inst, makeOp) = OpCodeTable.GetInstT32(opCode); + } + } + + if (makeOp != null) + { + return makeOp(inst, address, opCode); + } + else + { + if (mode == ExecutionMode.Aarch32Thumb) + { + return new OpCodeT16(inst, address, opCode); + } + else + { + return new OpCode(inst, address, opCode); + } + } + } + } +} diff --git a/src/ARMeilleure/Decoders/DecoderHelper.cs b/src/ARMeilleure/Decoders/DecoderHelper.cs new file mode 100644 index 000000000..35e573955 --- /dev/null +++ b/src/ARMeilleure/Decoders/DecoderHelper.cs @@ -0,0 +1,167 @@ +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 < tbl.Length; idx++) + { + tbl[idx] = ExpandImm8ToFP32((uint)idx); + } + + return tbl; + } + + private static ulong[] BuildImm8ToFP64Table() + { + ulong[] tbl = new ulong[256]; + + for (int idx = 0; idx < tbl.Length; idx++) + { + tbl[idx] = ExpandImm8ToFP64((ulong)idx); + } + + return tbl; + } + + // abcdefgh -> aBbbbbbc defgh000 00000000 00000000 (B = ~b) + private static uint ExpandImm8ToFP32(uint imm) + { + static 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) + { + static 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() { 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; + } + } +} diff --git a/src/ARMeilleure/Decoders/DecoderMode.cs b/src/ARMeilleure/Decoders/DecoderMode.cs new file mode 100644 index 000000000..708d5c8fb --- /dev/null +++ b/src/ARMeilleure/Decoders/DecoderMode.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + enum DecoderMode + { + MultipleBlocks, + SingleBlock, + SingleInstruction, + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode.cs b/src/ARMeilleure/Decoders/IOpCode.cs new file mode 100644 index 000000000..9d5e3bf7a --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode.cs @@ -0,0 +1,17 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.Decoders +{ + interface IOpCode + { + ulong Address { get; } + + InstDescriptor Instruction { get; } + + RegisterSize RegisterSize { get; } + + int GetBitsCount(); + + OperandType GetOperandType(); + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32.cs b/src/ARMeilleure/Decoders/IOpCode32.cs new file mode 100644 index 000000000..578925dee --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32 : IOpCode + { + Condition Cond { get; } + + uint GetPc(); + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Adr.cs b/src/ARMeilleure/Decoders/IOpCode32Adr.cs new file mode 100644 index 000000000..40a4f526e --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Adr.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Adr + { + int Rd { get; } + + int Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Alu.cs b/src/ARMeilleure/Decoders/IOpCode32Alu.cs new file mode 100644 index 000000000..a85ef44ad --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Alu.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Alu : IOpCode32, IOpCode32HasSetFlags + { + int Rd { get; } + int Rn { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluBf.cs b/src/ARMeilleure/Decoders/IOpCode32AluBf.cs new file mode 100644 index 000000000..d1fe59039 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluBf.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluBf + { + int Rd { get; } + int Rn { get; } + + int Msb { get; } + int Lsb { get; } + + int SourceMask => (int)(0xFFFFFFFF >> (31 - Msb)); + int DestMask => SourceMask & (int)(0xFFFFFFFF << Lsb); + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluImm.cs b/src/ARMeilleure/Decoders/IOpCode32AluImm.cs new file mode 100644 index 000000000..b89990187 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluImm.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluImm : IOpCode32Alu + { + int Immediate { get; } + + bool IsRotated { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluImm16.cs b/src/ARMeilleure/Decoders/IOpCode32AluImm16.cs new file mode 100644 index 000000000..dd42a70b1 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluImm16.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluImm16 : IOpCode32Alu + { + int Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluMla.cs b/src/ARMeilleure/Decoders/IOpCode32AluMla.cs new file mode 100644 index 000000000..79b164257 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluMla.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluMla : IOpCode32AluReg + { + int Ra { get; } + + bool NHigh { get; } + bool MHigh { get; } + bool R { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluReg.cs b/src/ARMeilleure/Decoders/IOpCode32AluReg.cs new file mode 100644 index 000000000..1a35e664c --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluReg.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluReg : IOpCode32Alu + { + int Rm { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs b/src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs new file mode 100644 index 000000000..37a2c1000 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluRsImm : IOpCode32Alu + { + int Rm { get; } + int Immediate { get; } + + ShiftType ShiftType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs b/src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs new file mode 100644 index 000000000..ed9859fc0 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluRsReg : IOpCode32Alu + { + int Rm { get; } + int Rs { get; } + + ShiftType ShiftType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluUmull.cs b/src/ARMeilleure/Decoders/IOpCode32AluUmull.cs new file mode 100644 index 000000000..79d2bb9ba --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluUmull.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluUmull : IOpCode32, IOpCode32HasSetFlags + { + int RdLo { get; } + int RdHi { get; } + int Rn { get; } + int Rm { get; } + + bool NHigh { get; } + bool MHigh { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluUx.cs b/src/ARMeilleure/Decoders/IOpCode32AluUx.cs new file mode 100644 index 000000000..d390f6b81 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluUx.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluUx : IOpCode32AluReg + { + int RotateBits { get; } + bool Add { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32BImm.cs b/src/ARMeilleure/Decoders/IOpCode32BImm.cs new file mode 100644 index 000000000..8d22d5c40 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32BImm.cs @@ -0,0 +1,4 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32BImm : IOpCode32, IOpCodeBImm { } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32BReg.cs b/src/ARMeilleure/Decoders/IOpCode32BReg.cs new file mode 100644 index 000000000..9badc9858 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32BReg.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32BReg : IOpCode32 + { + int Rm { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Exception.cs b/src/ARMeilleure/Decoders/IOpCode32Exception.cs new file mode 100644 index 000000000..c38af9078 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Exception.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Exception + { + int Id { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs b/src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs new file mode 100644 index 000000000..fd9337d9b --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32HasSetFlags + { + bool? SetFlags { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Mem.cs b/src/ARMeilleure/Decoders/IOpCode32Mem.cs new file mode 100644 index 000000000..a34bc0e2a --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Mem.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Mem : IOpCode32 + { + int Rt { get; } + int Rt2 => Rt | 1; + int Rn { get; } + + bool WBack { get; } + bool IsLoad { get; } + bool Index { get; } + bool Add { get; } + + int Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32MemEx.cs b/src/ARMeilleure/Decoders/IOpCode32MemEx.cs new file mode 100644 index 000000000..5f6b9321e --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemEx.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemEx : IOpCode32Mem + { + int Rd { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32MemMult.cs b/src/ARMeilleure/Decoders/IOpCode32MemMult.cs new file mode 100644 index 000000000..0c5e48f22 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemMult.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemMult : IOpCode32 + { + int Rn { get; } + + int RegisterMask { get; } + + int PostOffset { get; } + + bool IsLoad { get; } + + int Offset { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32MemReg.cs b/src/ARMeilleure/Decoders/IOpCode32MemReg.cs new file mode 100644 index 000000000..6a63f7f69 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemReg.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemReg : IOpCode32Mem + { + int Rm { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs b/src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs new file mode 100644 index 000000000..3407e98ac --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemRsImm : IOpCode32Mem + { + int Rm { get; } + ShiftType ShiftType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Simd.cs b/src/ARMeilleure/Decoders/IOpCode32Simd.cs new file mode 100644 index 000000000..0dccd2679 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Simd.cs @@ -0,0 +1,4 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Simd : IOpCode32, IOpCodeSimd { } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32SimdImm.cs b/src/ARMeilleure/Decoders/IOpCode32SimdImm.cs new file mode 100644 index 000000000..a8e646092 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32SimdImm.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32SimdImm : IOpCode32Simd + { + int Vd { get; } + long Immediate { get; } + int Elems { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeAlu.cs b/src/ARMeilleure/Decoders/IOpCodeAlu.cs new file mode 100644 index 000000000..059769ba9 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAlu.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAlu : IOpCode + { + int Rd { get; } + int Rn { get; } + + DataOp DataOp { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeAluImm.cs b/src/ARMeilleure/Decoders/IOpCodeAluImm.cs new file mode 100644 index 000000000..40a69cc90 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAluImm.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAluImm : IOpCodeAlu + { + long Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeAluRs.cs b/src/ARMeilleure/Decoders/IOpCodeAluRs.cs new file mode 100644 index 000000000..eec956982 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAluRs.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAluRs : IOpCodeAlu + { + int Shift { get; } + int Rm { get; } + + ShiftType ShiftType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeAluRx.cs b/src/ARMeilleure/Decoders/IOpCodeAluRx.cs new file mode 100644 index 000000000..e5a8559d8 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAluRx.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAluRx : IOpCodeAlu + { + int Shift { get; } + int Rm { get; } + + IntType IntType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeBImm.cs b/src/ARMeilleure/Decoders/IOpCodeBImm.cs new file mode 100644 index 000000000..9ce7512a1 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeBImm.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeBImm : IOpCode + { + long Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeCond.cs b/src/ARMeilleure/Decoders/IOpCodeCond.cs new file mode 100644 index 000000000..6604f19a2 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeCond.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeCond : IOpCode + { + Condition Cond { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeLit.cs b/src/ARMeilleure/Decoders/IOpCodeLit.cs new file mode 100644 index 000000000..434e4da88 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeLit.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeLit : IOpCode + { + int Rt { get; } + long Immediate { get; } + int Size { get; } + bool Signed { get; } + bool Prefetch { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeSimd.cs b/src/ARMeilleure/Decoders/IOpCodeSimd.cs new file mode 100644 index 000000000..598d9d7f8 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeSimd.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeSimd : IOpCode + { + int Size { get; } + } +} diff --git a/src/ARMeilleure/Decoders/InstDescriptor.cs b/src/ARMeilleure/Decoders/InstDescriptor.cs new file mode 100644 index 000000000..c35c754a9 --- /dev/null +++ b/src/ARMeilleure/Decoders/InstDescriptor.cs @@ -0,0 +1,18 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + readonly struct InstDescriptor + { + public static InstDescriptor Undefined => new(InstName.Und, InstEmit.Und); + + public InstName Name { get; } + public InstEmitter Emitter { get; } + + public InstDescriptor(InstName name, InstEmitter emitter) + { + Name = name; + Emitter = emitter; + } + } +} diff --git a/src/ARMeilleure/Decoders/InstEmitter.cs b/src/ARMeilleure/Decoders/InstEmitter.cs new file mode 100644 index 000000000..43bfcdca3 --- /dev/null +++ b/src/ARMeilleure/Decoders/InstEmitter.cs @@ -0,0 +1,6 @@ +using ARMeilleure.Translation; + +namespace ARMeilleure.Decoders +{ + delegate void InstEmitter(ArmEmitterContext context); +} diff --git a/src/ARMeilleure/Decoders/IntType.cs b/src/ARMeilleure/Decoders/IntType.cs new file mode 100644 index 000000000..937a569aa --- /dev/null +++ b/src/ARMeilleure/Decoders/IntType.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + enum IntType + { + UInt8 = 0, + UInt16 = 1, + UInt32 = 2, + UInt64 = 3, + Int8 = 4, + Int16 = 5, + Int32 = 6, + Int64 = 7, + } +} diff --git a/src/ARMeilleure/Decoders/OpCode.cs b/src/ARMeilleure/Decoders/OpCode.cs new file mode 100644 index 000000000..c8123308b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode.cs @@ -0,0 +1,48 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.Decoders +{ + class OpCode : IOpCode + { + public ulong Address { get; } + public int RawOpCode { get; } + + public int OpCodeSizeInBytes { get; protected set; } = 4; + + public InstDescriptor Instruction { get; protected set; } + + public RegisterSize RegisterSize { get; protected set; } + + public static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new(inst, address, opCode); + + public OpCode(InstDescriptor inst, ulong address, int opCode) + { + Instruction = inst; + Address = address; + RawOpCode = opCode; + + RegisterSize = RegisterSize.Int64; + } + + public int GetPairsCount() => GetBitsCount() / 16; + public int GetBytesCount() => GetBitsCount() / 8; + + public int GetBitsCount() + { + return RegisterSize switch + { + RegisterSize.Int32 => 32, + RegisterSize.Int64 => 64, + RegisterSize.Simd64 => 64, + RegisterSize.Simd128 => 128, + _ => throw new InvalidOperationException(), + }; + } + + public OperandType GetOperandType() + { + return RegisterSize == RegisterSize.Int32 ? OperandType.I32 : OperandType.I64; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32.cs b/src/ARMeilleure/Decoders/OpCode32.cs new file mode 100644 index 000000000..a2be01e9a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32.cs @@ -0,0 +1,34 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32 : OpCode + { + public Condition Cond { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32(inst, address, opCode); + + public OpCode32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RegisterSize = RegisterSize.Int32; + + Cond = (Condition)((uint)opCode >> 28); + } + + public bool IsThumb { get; protected init; } = false; + + public uint GetPc() + { + // Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline, + // the PC actually points 2 instructions ahead. + if (IsThumb) + { + // PC is ahead by 4 in thumb mode whether or not the current instruction + // is 16 or 32 bit. + return (uint)Address + 4u; + } + else + { + return (uint)Address + 8u; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Alu.cs b/src/ARMeilleure/Decoders/OpCode32Alu.cs new file mode 100644 index 000000000..8634f5ce9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Alu.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Alu : OpCode32, IOpCode32Alu + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Alu(inst, address, opCode); + + public OpCode32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + SetFlags = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluBf.cs b/src/ARMeilleure/Decoders/OpCode32AluBf.cs new file mode 100644 index 000000000..c34784428 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluBf.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluBf : OpCode32, IOpCode32AluBf + { + public int Rd { get; } + public int Rn { get; } + + public int Msb { get; } + public int Lsb { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluBf(inst, address, opCode); + + public OpCode32AluBf(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 12) & 0xf; + Rn = (opCode >> 0) & 0xf; + + Msb = (opCode >> 16) & 0x1f; + Lsb = (opCode >> 7) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluImm.cs b/src/ARMeilleure/Decoders/OpCode32AluImm.cs new file mode 100644 index 000000000..c8b05e6bc --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluImm.cs @@ -0,0 +1,23 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + class OpCode32AluImm : OpCode32Alu, IOpCode32AluImm + { + public int Immediate { get; } + + public bool IsRotated { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluImm(inst, address, opCode); + + public OpCode32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int value = (opCode >> 0) & 0xff; + int shift = (opCode >> 8) & 0xf; + + Immediate = BitUtils.RotateRight(value, shift * 2, 32); + + IsRotated = shift != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluImm16.cs b/src/ARMeilleure/Decoders/OpCode32AluImm16.cs new file mode 100644 index 000000000..2af35bd51 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluImm16.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluImm16 : OpCode32Alu, IOpCode32AluImm16 + { + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluImm16(inst, address, opCode); + + public OpCode32AluImm16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm12 = opCode & 0xfff; + int imm4 = (opCode >> 16) & 0xf; + + Immediate = (imm4 << 12) | imm12; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluMla.cs b/src/ARMeilleure/Decoders/OpCode32AluMla.cs new file mode 100644 index 000000000..bc5d23908 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluMla.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluMla : OpCode32, IOpCode32AluMla + { + public int Rn { get; } + public int Rm { get; } + public int Ra { get; } + public int Rd { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + public bool R { get; } + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluMla(inst, address, opCode); + + public OpCode32AluMla(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0xf; + Rm = (opCode >> 8) & 0xf; + Ra = (opCode >> 12) & 0xf; + Rd = (opCode >> 16) & 0xf; + R = (opCode & (1 << 5)) != 0; + + NHigh = ((opCode >> 5) & 0x1) == 1; + MHigh = ((opCode >> 6) & 0x1) == 1; + SetFlags = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluReg.cs b/src/ARMeilleure/Decoders/OpCode32AluReg.cs new file mode 100644 index 000000000..9ef7571cf --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluReg : OpCode32Alu, IOpCode32AluReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluReg(inst, address, opCode); + + public OpCode32AluReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluRsImm.cs b/src/ARMeilleure/Decoders/OpCode32AluRsImm.cs new file mode 100644 index 000000000..4b2c5897a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluRsImm.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluRsImm : OpCode32Alu, IOpCode32AluRsImm + { + public int Rm { get; } + public int Immediate { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluRsImm(inst, address, opCode); + + public OpCode32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Immediate = (opCode >> 7) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluRsReg.cs b/src/ARMeilleure/Decoders/OpCode32AluRsReg.cs new file mode 100644 index 000000000..6379b3bde --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluRsReg.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluRsReg : OpCode32Alu, IOpCode32AluRsReg + { + public int Rm { get; } + public int Rs { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluRsReg(inst, address, opCode); + + public OpCode32AluRsReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rs = (opCode >> 8) & 0xf; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluUmull.cs b/src/ARMeilleure/Decoders/OpCode32AluUmull.cs new file mode 100644 index 000000000..44b7ea154 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluUmull.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluUmull : OpCode32, IOpCode32AluUmull + { + public int RdLo { get; } + public int RdHi { get; } + public int Rn { get; } + public int Rm { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluUmull(inst, address, opCode); + + public OpCode32AluUmull(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RdLo = (opCode >> 12) & 0xf; + RdHi = (opCode >> 16) & 0xf; + Rm = (opCode >> 8) & 0xf; + Rn = (opCode >> 0) & 0xf; + + NHigh = ((opCode >> 5) & 0x1) == 1; + MHigh = ((opCode >> 6) & 0x1) == 1; + + SetFlags = ((opCode >> 20) & 0x1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluUx.cs b/src/ARMeilleure/Decoders/OpCode32AluUx.cs new file mode 100644 index 000000000..68da302fc --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluUx.cs @@ -0,0 +1,18 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32AluUx : OpCode32AluReg, IOpCode32AluUx + { + public int Rotate { get; } + public int RotateBits => Rotate * 8; + public bool Add => Rn != RegisterAlias.Aarch32Pc; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluUx(inst, address, opCode); + + public OpCode32AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rotate = (opCode >> 10) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32BImm.cs b/src/ARMeilleure/Decoders/OpCode32BImm.cs new file mode 100644 index 000000000..e7f5d6db1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32BImm.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32BImm : OpCode32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32BImm(inst, address, opCode); + + public OpCode32BImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + // When the condition is never, the instruction is BLX to Thumb mode. + if (Cond != Condition.Nv) + { + pc &= ~3u; + } + + Immediate = pc + DecoderHelper.DecodeImm24_2(opCode); + + if (Cond == Condition.Nv) + { + long H = (opCode >> 23) & 2; + + Immediate |= H; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32BReg.cs b/src/ARMeilleure/Decoders/OpCode32BReg.cs new file mode 100644 index 000000000..8939c0de3 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32BReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32BReg : OpCode32, IOpCode32BReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32BReg(inst, address, opCode); + + public OpCode32BReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = opCode & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Exception.cs b/src/ARMeilleure/Decoders/OpCode32Exception.cs new file mode 100644 index 000000000..51a535e43 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Exception.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Exception : OpCode32, IOpCode32Exception + { + public int Id { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Exception(inst, address, opCode); + + public OpCode32Exception(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Id = opCode & 0xFFFFFF; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Mem.cs b/src/ARMeilleure/Decoders/OpCode32Mem.cs new file mode 100644 index 000000000..8a2421996 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Mem.cs @@ -0,0 +1,39 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + class OpCode32Mem : OpCode32, IOpCode32Mem + { + public int Rt { get; protected set; } + public int Rn { get; } + + public int Immediate { get; protected set; } + + public bool Index { get; } + public bool Add { get; } + public bool WBack { get; } + public bool Unprivileged { get; } + + public bool IsLoad { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Mem(inst, address, opCode); + + public OpCode32Mem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + Index = p; + Add = u; + WBack = !p || w; + Unprivileged = !p && w; + + IsLoad = isLoad || inst.Name == InstName.Ldrd; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemImm.cs b/src/ARMeilleure/Decoders/OpCode32MemImm.cs new file mode 100644 index 000000000..fa10e04ee --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemImm.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemImm : OpCode32Mem + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemImm(inst, address, opCode); + + public OpCode32MemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = opCode & 0xfff; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemImm8.cs b/src/ARMeilleure/Decoders/OpCode32MemImm8.cs new file mode 100644 index 000000000..248ee8e65 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemImm8.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemImm8 : OpCode32Mem + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemImm8(inst, address, opCode); + + public OpCode32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm4L = (opCode >> 0) & 0xf; + int imm4H = (opCode >> 8) & 0xf; + + Immediate = imm4L | (imm4H << 4); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemLdEx.cs b/src/ARMeilleure/Decoders/OpCode32MemLdEx.cs new file mode 100644 index 000000000..0f0b37eac --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemLdEx.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemLdEx : OpCode32Mem, IOpCode32MemEx + { + public int Rd { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemLdEx(inst, address, opCode); + + public OpCode32MemLdEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = opCode & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemMult.cs b/src/ARMeilleure/Decoders/OpCode32MemMult.cs new file mode 100644 index 000000000..6e39e3479 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemMult.cs @@ -0,0 +1,52 @@ +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCode32MemMult : OpCode32, IOpCode32MemMult + { + public int Rn { get; } + + public int RegisterMask { get; } + public int Offset { get; } + public int PostOffset { get; } + + public bool IsLoad { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemMult(inst, address, opCode); + + public OpCode32MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + RegisterMask = opCode & 0xffff; + + int regsSize = BitOperations.PopCount((uint)RegisterMask) * 4; + + if (!u) + { + Offset -= regsSize; + } + + if (u == p) + { + Offset += 4; + } + + if (w) + { + PostOffset = u ? regsSize : -regsSize; + } + else + { + PostOffset = 0; + } + + IsLoad = isLoad; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemReg.cs b/src/ARMeilleure/Decoders/OpCode32MemReg.cs new file mode 100644 index 000000000..d8f1c29b2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemReg : OpCode32Mem, IOpCode32MemReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemReg(inst, address, opCode); + + public OpCode32MemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemRsImm.cs b/src/ARMeilleure/Decoders/OpCode32MemRsImm.cs new file mode 100644 index 000000000..b0e5aa4b9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemRsImm.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemRsImm : OpCode32Mem, IOpCode32MemRsImm + { + public int Rm { get; } + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemRsImm(inst, address, opCode); + + public OpCode32MemRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Immediate = (opCode >> 7) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemStEx.cs b/src/ARMeilleure/Decoders/OpCode32MemStEx.cs new file mode 100644 index 000000000..180a9b5ac --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemStEx.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemStEx : OpCode32Mem, IOpCode32MemEx + { + public int Rd { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemStEx(inst, address, opCode); + + public OpCode32MemStEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 12) & 0xf; + Rt = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Mrs.cs b/src/ARMeilleure/Decoders/OpCode32Mrs.cs new file mode 100644 index 000000000..b681b54c0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Mrs.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Mrs : OpCode32 + { + public bool R { get; } + public int Rd { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Mrs(inst, address, opCode); + + public OpCode32Mrs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + R = ((opCode >> 22) & 1) != 0; + Rd = (opCode >> 12) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MsrReg.cs b/src/ARMeilleure/Decoders/OpCode32MsrReg.cs new file mode 100644 index 000000000..dcd06aa01 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MsrReg.cs @@ -0,0 +1,29 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32MsrReg : OpCode32 + { + public bool R { get; } + public int Mask { get; } + public int Rd { get; } + public bool Banked { get; } + public int Rn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MsrReg(inst, address, opCode); + + public OpCode32MsrReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + R = ((opCode >> 22) & 1) != 0; + Mask = (opCode >> 16) & 0xf; + Rd = (opCode >> 12) & 0xf; + Banked = ((opCode >> 9) & 1) != 0; + Rn = (opCode >> 0) & 0xf; + + if (Rn == RegisterAlias.Aarch32Pc || Mask == 0) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Sat.cs b/src/ARMeilleure/Decoders/OpCode32Sat.cs new file mode 100644 index 000000000..35c5cf47a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Sat.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Sat : OpCode32 + { + public int Rn { get; } + public int Imm5 { get; } + public int Rd { get; } + public int SatImm { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Sat(inst, address, opCode); + + public OpCode32Sat(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0xf; + Imm5 = (opCode >> 7) & 0x1f; + Rd = (opCode >> 12) & 0xf; + SatImm = (opCode >> 16) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 2); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Sat16.cs b/src/ARMeilleure/Decoders/OpCode32Sat16.cs new file mode 100644 index 000000000..01f4d3b23 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Sat16.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Sat16 : OpCode32 + { + public int Rn { get; } + public int Rd { get; } + public int SatImm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Sat16(inst, address, opCode); + + public OpCode32Sat16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0xf; + Rd = (opCode >> 12) & 0xf; + SatImm = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Simd.cs b/src/ARMeilleure/Decoders/OpCode32Simd.cs new file mode 100644 index 000000000..1e69b2341 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Simd.cs @@ -0,0 +1,33 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Simd : OpCode32SimdBase + { + public int Opc { get; protected set; } + public bool Q { get; protected set; } + public bool F { get; protected set; } + public bool U { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Simd(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32Simd(inst, address, opCode, true); + + public OpCode32Simd(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 20) & 0x3; + Q = ((opCode >> 6) & 0x1) != 0; + F = ((opCode >> 10) & 0x1) != 0; + U = ((opCode >> (isThumb ? 28 : 24)) & 0x1) != 0; + Opc = (opCode >> 7) & 0x3; + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32Simd) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdBase.cs b/src/ARMeilleure/Decoders/OpCode32SimdBase.cs new file mode 100644 index 000000000..d0634a0e1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdBase.cs @@ -0,0 +1,49 @@ +using System; + +namespace ARMeilleure.Decoders +{ + abstract class OpCode32SimdBase : OpCode32, IOpCode32Simd + { + public int Vd { get; protected set; } + public int Vm { get; protected set; } + public int Size { get; protected set; } + + // Helpers to index doublewords within quad words. Essentially, looping over the vector starts at quadword Q and index Fx or Ix within it, + // depending on instruction type. + // + // Qx: The quadword register that the target vector is contained in. + // Ix: The starting index of the target vector within the quadword, with size treated as integer. + // Fx: The starting index of the target vector within the quadword, with size treated as floating point. (16 or 32) + public int Qd => GetQuadwordIndex(Vd); + public int Id => GetQuadwordSubindex(Vd) << (3 - Size); + public int Fd => GetQuadwordSubindex(Vd) << (1 - (Size & 1)); // When the top bit is truncated, 1 is fp16 which is an optional extension in ARMv8.2. We always assume 64. + + public int Qm => GetQuadwordIndex(Vm); + public int Im => GetQuadwordSubindex(Vm) << (3 - Size); + public int Fm => GetQuadwordSubindex(Vm) << (1 - (Size & 1)); + + protected int GetQuadwordIndex(int index) + { + return RegisterSize switch + { + RegisterSize.Simd128 or RegisterSize.Simd64 => index >> 1, + _ => throw new InvalidOperationException(), + }; + } + + protected int GetQuadwordSubindex(int index) + { + return RegisterSize switch + { + RegisterSize.Simd128 => 0, + RegisterSize.Simd64 => index & 1, + _ => throw new InvalidOperationException(), + }; + } + + protected OpCode32SimdBase(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdBinary.cs b/src/ARMeilleure/Decoders/OpCode32SimdBinary.cs new file mode 100644 index 000000000..c0c8277a9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdBinary.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + /// + /// A special alias that always runs in 64 bit int, to speed up binary ops a little. + /// + class OpCode32SimdBinary : OpCode32SimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdBinary(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdBinary(inst, address, opCode, true); + + public OpCode32SimdBinary(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = 3; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm, Vn)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs b/src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs new file mode 100644 index 000000000..d8bc109e7 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCmpZ : OpCode32Simd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCmpZ(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCmpZ(inst, address, opCode, true); + + public OpCode32SimdCmpZ(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 18) & 0x3; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCvtFFixed.cs b/src/ARMeilleure/Decoders/OpCode32SimdCvtFFixed.cs new file mode 100644 index 000000000..200df73ad --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCvtFFixed.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCvtFFixed : OpCode32Simd + { + public int Fbits { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFFixed(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFFixed(inst, address, opCode, true); + + public OpCode32SimdCvtFFixed(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Opc = (opCode >> 8) & 0x1; + + Size = Opc == 1 ? 0 : 2; + Fbits = 64 - ((opCode >> 16) & 0x3f); + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs b/src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs new file mode 100644 index 000000000..ee8f94a4e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCvtFI : OpCode32SimdS + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFI(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFI(inst, address, opCode, true); + + public OpCode32SimdCvtFI(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Opc = (opCode >> 7) & 0x1; + + bool toInteger = (Opc2 & 0b100) != 0; + + if (toInteger) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs b/src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs new file mode 100644 index 000000000..d3beb4bfd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs @@ -0,0 +1,44 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCvtTB : OpCode32, IOpCode32Simd + { + public int Vd { get; } + public int Vm { get; } + public bool Op { get; } // Convert to Half / Convert from Half + public bool T { get; } // Top / Bottom + public int Size { get; } // Double / Single + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtTB(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtTB(inst, address, opCode, true); + + public OpCode32SimdCvtTB(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Op = ((opCode >> 16) & 0x1) != 0; + T = ((opCode >> 7) & 0x1) != 0; + Size = ((opCode >> 8) & 0x1); + + RegisterSize = Size == 1 ? RegisterSize.Int64 : RegisterSize.Int32; + + if (Size == 1) + { + if (Op) + { + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + } + else + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs b/src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs new file mode 100644 index 000000000..b6cdff088 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs @@ -0,0 +1,43 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdDupElem : OpCode32Simd + { + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupElem(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupElem(inst, address, opCode, true); + + public OpCode32SimdDupElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + var opc = (opCode >> 16) & 0xf; + + if ((opc & 0b1) == 1) + { + Size = 0; + Index = (opc >> 1) & 0x7; + } + else if ((opc & 0b11) == 0b10) + { + Size = 1; + Index = (opc >> 2) & 0x3; + } + else if ((opc & 0b111) == 0b100) + { + Size = 2; + Index = (opc >> 3) & 0x1; + } + else + { + Instruction = InstDescriptor.Undefined; + } + + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs b/src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs new file mode 100644 index 000000000..57adea5e6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdDupGP : OpCode32, IOpCode32Simd + { + public int Size { get; } + public int Vd { get; } + public int Rt { get; } + public bool Q { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupGP(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupGP(inst, address, opCode, true); + + public OpCode32SimdDupGP(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Size = 2 - (((opCode >> 21) & 0x2) | ((opCode >> 5) & 0x1)); // B:E - 0 for 32, 16 then 8. + if (Size == -1) + { + Instruction = InstDescriptor.Undefined; + return; + } + Q = ((opCode >> 21) & 0x1) != 0; + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + Vd = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + Rt = ((opCode >> 12) & 0xf); + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdExt.cs b/src/ARMeilleure/Decoders/OpCode32SimdExt.cs new file mode 100644 index 000000000..4fe9f25db --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdExt.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdExt : OpCode32SimdReg + { + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdExt(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdExt(inst, address, opCode, true); + + public OpCode32SimdExt(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Immediate = (opCode >> 8) & 0xf; + Size = 0; + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm, Vn) || (!Q && Immediate > 7)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdImm.cs b/src/ARMeilleure/Decoders/OpCode32SimdImm.cs new file mode 100644 index 000000000..9e931e791 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdImm.cs @@ -0,0 +1,38 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdImm : OpCode32SimdBase, IOpCode32SimdImm + { + public bool Q { get; } + public long Immediate { get; } + public int Elems => GetBytesCount() >> Size; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm(inst, address, opCode, true); + + public OpCode32SimdImm(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Vd = (opCode >> 12) & 0xf; + Vd |= (opCode >> 18) & 0x10; + + Q = ((opCode >> 6) & 0x1) > 0; + + int cMode = (opCode >> 8) & 0xf; + int op = (opCode >> 5) & 0x1; + + long imm; + + imm = ((uint)opCode >> 0) & 0xf; + imm |= ((uint)opCode >> 12) & 0x70; + imm |= ((uint)opCode >> (isThumb ? 21 : 17)) & 0x80; + + (Immediate, Size) = OpCodeSimdHelper.GetSimdImmediateAndSize(cMode, op, imm); + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdImm44.cs b/src/ARMeilleure/Decoders/OpCode32SimdImm44.cs new file mode 100644 index 000000000..55df1ba6e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdImm44.cs @@ -0,0 +1,41 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdImm44 : OpCode32, IOpCode32SimdImm + { + public int Vd { get; } + public long Immediate { get; } + public int Size { get; } + public int Elems { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm44(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm44(inst, address, opCode, true); + + public OpCode32SimdImm44(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Size = (opCode >> 8) & 0x3; + + bool single = Size != 3; + + if (single) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + + long imm; + + imm = ((uint)opCode >> 0) & 0xf; + imm |= ((uint)opCode >> 12) & 0xf0; + + Immediate = (Size == 3) ? (long)DecoderHelper.Imm8ToFP64Table[(int)imm] : DecoderHelper.Imm8ToFP32Table[(int)imm]; + + RegisterSize = (!single) ? RegisterSize.Int64 : RegisterSize.Int32; + Elems = 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdLong.cs new file mode 100644 index 000000000..5c068de16 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdLong.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdLong : OpCode32SimdBase + { + public bool U { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdLong(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdLong(inst, address, opCode, true); + + public OpCode32SimdLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + int imm3h = (opCode >> 19) & 0x7; + + // The value must be a power of 2, otherwise it is the encoding of another instruction. + switch (imm3h) + { + case 1: + Size = 0; + break; + case 2: + Size = 1; + break; + case 4: + Size = 2; + break; + } + + U = ((opCode >> (isThumb ? 28 : 24)) & 0x1) != 0; + + RegisterSize = RegisterSize.Simd64; + + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs new file mode 100644 index 000000000..86870dfea --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs @@ -0,0 +1,40 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemImm : OpCode32, IOpCode32Simd + { + public int Vd { get; } + public int Rn { get; } + public int Size { get; } + public bool Add { get; } + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemImm(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemImm(inst, address, opCode, true); + + public OpCode32SimdMemImm(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Immediate = opCode & 0xff; + + Rn = (opCode >> 16) & 0xf; + Size = (opCode >> 8) & 0x3; + + Immediate <<= (Size == 1) ? 1 : 2; + + bool u = (opCode & (1 << 23)) != 0; + Add = u; + + bool single = Size != 3; + + if (single) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs new file mode 100644 index 000000000..c3b8670f8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs @@ -0,0 +1,76 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemMult : OpCode32 + { + public int Rn { get; } + public int Vd { get; } + + public int RegisterRange { get; } + public int Offset { get; } + public int PostOffset { get; } + public bool IsLoad { get; } + public bool DoubleWidth { get; } + public bool Add { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemMult(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemMult(inst, address, opCode, true); + + public OpCode32SimdMemMult(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + if (p == u && w) + { + Instruction = InstDescriptor.Undefined; + return; + } + + DoubleWidth = (opCode & (1 << 8)) != 0; + + if (!DoubleWidth) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + + Add = u; + + RegisterRange = opCode & 0xff; + + int regsSize = RegisterRange * 4; // Double mode is still measured in single register size. + + if (!u) + { + Offset -= regsSize; + } + + if (w) + { + PostOffset = u ? regsSize : -regsSize; + } + else + { + PostOffset = 0; + } + + IsLoad = isLoad; + + int regs = DoubleWidth ? RegisterRange / 2 : RegisterRange; + + if (RegisterRange == 0 || RegisterRange > 32 || Vd + regs > 32) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs new file mode 100644 index 000000000..6a18211c6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs @@ -0,0 +1,50 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemPair : OpCode32, IOpCode32Simd + { + private static readonly int[] _regsMap = + { + 1, 1, 4, 2, + 1, 1, 3, 1, + 1, 1, 2, 1, + 1, 1, 1, 1, + }; + + public int Vd { get; } + public int Rn { get; } + public int Rm { get; } + public int Align { get; } + public bool WBack { get; } + public bool RegisterIndex { get; } + public int Size { get; } + public int Elems => 8 >> Size; + public int Regs { get; } + public int Increment { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemPair(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemPair(inst, address, opCode, true); + + public OpCode32SimdMemPair(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Vd = (opCode >> 12) & 0xf; + Vd |= (opCode >> 18) & 0x10; + + Size = (opCode >> 6) & 0x3; + + Align = (opCode >> 4) & 0x3; + Rm = (opCode >> 0) & 0xf; + Rn = (opCode >> 16) & 0xf; + + WBack = Rm != RegisterAlias.Aarch32Pc; + RegisterIndex = Rm != RegisterAlias.Aarch32Pc && Rm != RegisterAlias.Aarch32Sp; + + Regs = _regsMap[(opCode >> 8) & 0xf]; + + Increment = ((opCode >> 8) & 0x1) + 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs new file mode 100644 index 000000000..5df45000f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs @@ -0,0 +1,51 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemSingle : OpCode32, IOpCode32Simd + { + public int Vd { get; } + public int Rn { get; } + public int Rm { get; } + public int IndexAlign { get; } + public int Index { get; } + public bool WBack { get; } + public bool RegisterIndex { get; } + public int Size { get; } + public bool Replicate { get; } + public int Increment { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemSingle(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemSingle(inst, address, opCode, true); + + public OpCode32SimdMemSingle(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Vd = (opCode >> 12) & 0xf; + Vd |= (opCode >> 18) & 0x10; + + IndexAlign = (opCode >> 4) & 0xf; + + Size = (opCode >> 10) & 0x3; + Replicate = Size == 3; + if (Replicate) + { + Size = (opCode >> 6) & 0x3; + Increment = ((opCode >> 5) & 1) + 1; + Index = 0; + } + else + { + Increment = (((IndexAlign >> Size) & 1) == 0) ? 1 : 2; + Index = IndexAlign >> (1 + Size); + } + + Rm = (opCode >> 0) & 0xf; + Rn = (opCode >> 16) & 0xf; + + WBack = Rm != RegisterAlias.Aarch32Pc; + RegisterIndex = Rm != RegisterAlias.Aarch32Pc && Rm != RegisterAlias.Aarch32Sp; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs new file mode 100644 index 000000000..35b8cc9f1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovGp : OpCode32, IOpCode32Simd + { + public int Size => 2; + + public int Vn { get; } + public int Rt { get; } + public int Op { get; } + + public int Opc1 { get; } + public int Opc2 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGp(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGp(inst, address, opCode, true); + + public OpCode32SimdMovGp(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + // Which one is used is instruction dependant. + Op = (opCode >> 20) & 0x1; + + Opc1 = (opCode >> 21) & 0x3; + Opc2 = (opCode >> 5) & 0x3; + + Vn = ((opCode >> 7) & 0x1) | ((opCode >> 15) & 0x1e); + Rt = (opCode >> 12) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs new file mode 100644 index 000000000..4399fb3c0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovGpDouble : OpCode32, IOpCode32Simd + { + public int Size => 3; + + public int Vm { get; } + public int Rt { get; } + public int Rt2 { get; } + public int Op { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpDouble(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpDouble(inst, address, opCode, true); + + public OpCode32SimdMovGpDouble(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + // Which one is used is instruction dependant. + Op = (opCode >> 20) & 0x1; + + Rt = (opCode >> 12) & 0xf; + Rt2 = (opCode >> 16) & 0xf; + + bool single = (opCode & (1 << 8)) == 0; + if (single) + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + } + else + { + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs new file mode 100644 index 000000000..f6fce7d99 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs @@ -0,0 +1,51 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovGpElem : OpCode32, IOpCode32Simd + { + public int Size { get; } + + public int Vd { get; } + public int Rt { get; } + public int Op { get; } + public bool U { get; } + + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpElem(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpElem(inst, address, opCode, true); + + public OpCode32SimdMovGpElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Op = (opCode >> 20) & 0x1; + U = ((opCode >> 23) & 1) != 0; + + var opc = (((opCode >> 23) & 1) << 4) | (((opCode >> 21) & 0x3) << 2) | ((opCode >> 5) & 0x3); + + if ((opc & 0b01000) == 0b01000) + { + Size = 0; + Index = opc & 0x7; + } + else if ((opc & 0b01001) == 0b00001) + { + Size = 1; + Index = (opc >> 1) & 0x3; + } + else if ((opc & 0b11011) == 0) + { + Size = 2; + Index = (opc >> 2) & 0x1; + } + else + { + Instruction = InstDescriptor.Undefined; + return; + } + + Vd = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + Rt = (opCode >> 12) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovn.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovn.cs new file mode 100644 index 000000000..576e12cc0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovn.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovn : OpCode32Simd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovn(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovn(inst, address, opCode, true); + + public OpCode32SimdMovn(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 18) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdReg.cs b/src/ARMeilleure/Decoders/OpCode32SimdReg.cs new file mode 100644 index 000000000..eaf17b8c6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdReg.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdReg : OpCode32Simd + { + public int Vn { get; } + + public int Qn => GetQuadwordIndex(Vn); + public int In => GetQuadwordSubindex(Vn) << (3 - Size); + public int Fn => GetQuadwordSubindex(Vn) << (1 - (Size & 1)); + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdReg(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdReg(inst, address, opCode, true); + + public OpCode32SimdReg(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Vn = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32SimdReg) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm, Vn)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs new file mode 100644 index 000000000..147de44ba --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegElem : OpCode32SimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElem(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElem(inst, address, opCode, true); + + public OpCode32SimdRegElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = ((opCode >> (isThumb ? 28 : 24)) & 0x1) != 0; + F = ((opCode >> 8) & 0x1) != 0; + Size = (opCode >> 20) & 0x3; + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + if (Size == 1) + { + Vm = ((opCode >> 3) & 0x1) | ((opCode >> 4) & 0x2) | ((opCode << 2) & 0x1c); + } + else /* if (Size == 2) */ + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + } + + if (GetType() == typeof(OpCode32SimdRegElem) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vn) || Size == 0 || (Size == 1 && F)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs new file mode 100644 index 000000000..8aea44cb0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegElemLong : OpCode32SimdRegElem + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElemLong(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElemLong(inst, address, opCode, true); + + public OpCode32SimdRegElemLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + F = false; + + RegisterSize = RegisterSize.Simd64; + + // (Vd & 1) != 0 || Size == 3 are also invalid, but they are checked on encoding. + if (Size == 0) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs new file mode 100644 index 000000000..1349fb479 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegLong : OpCode32SimdReg + { + public bool Polynomial { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegLong(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegLong(inst, address, opCode, true); + + public OpCode32SimdRegLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + RegisterSize = RegisterSize.Simd64; + + Polynomial = ((opCode >> 9) & 0x1) != 0; + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32SimdRegLong) && DecoderHelper.VectorArgumentsInvalid(true, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegS.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegS.cs new file mode 100644 index 000000000..2dfb0074d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegS.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegS : OpCode32SimdS + { + public int Vn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegS(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegS(inst, address, opCode, true); + + public OpCode32SimdRegS(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + bool single = Size != 3; + if (single) + { + Vn = ((opCode >> 7) & 0x1) | ((opCode >> 15) & 0x1e); + } + else + { + Vn = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs new file mode 100644 index 000000000..6f9c639f9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegWide : OpCode32SimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegWide(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegWide(inst, address, opCode, true); + + public OpCode32SimdRegWide(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + RegisterSize = RegisterSize.Simd64; + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32SimdRegWide) && DecoderHelper.VectorArgumentsInvalid(true, Vd, Vn)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRev.cs b/src/ARMeilleure/Decoders/OpCode32SimdRev.cs new file mode 100644 index 000000000..26d8be2b9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRev.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRev : OpCode32SimdCmpZ + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRev(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRev(inst, address, opCode, true); + + public OpCode32SimdRev(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + if (Opc + Size >= 3) + { + Instruction = InstDescriptor.Undefined; + return; + } + + // Currently, this instruction is treated as though it's OPCODE is the true size, + // which lets us deal with reversing vectors on a single element basis (eg. math magic an I64 rather than insert lots of I8s). + int tempSize = Size; + Size = 3 - Opc; // Op 0 is 64 bit, 1 is 32 and so on. + Opc = tempSize; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdS.cs b/src/ARMeilleure/Decoders/OpCode32SimdS.cs new file mode 100644 index 000000000..0bb62cb52 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdS.cs @@ -0,0 +1,39 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdS : OpCode32, IOpCode32Simd + { + public int Vd { get; protected set; } + public int Vm { get; protected set; } + public int Opc { get; protected set; } // "with_zero" (Opc<1>) [Vcmp, Vcmpe]. + public int Opc2 { get; } // opc2 or RM (opc2<1:0>) [Vcvt, Vrint]. + public int Size { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdS(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdS(inst, address, opCode, true); + + public OpCode32SimdS(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Opc = (opCode >> 15) & 0x3; + Opc2 = (opCode >> 16) & 0x7; + + Size = (opCode >> 8) & 0x3; + + bool single = Size != 3; + + RegisterSize = single ? RegisterSize.Int32 : RegisterSize.Int64; + + if (single) + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdSel.cs b/src/ARMeilleure/Decoders/OpCode32SimdSel.cs new file mode 100644 index 000000000..a6667ba19 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdSel.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdSel : OpCode32SimdRegS + { + public OpCode32SimdSelMode Cc { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSel(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSel(inst, address, opCode, true); + + public OpCode32SimdSel(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Cc = (OpCode32SimdSelMode)((opCode >> 20) & 3); + } + } + + enum OpCode32SimdSelMode + { + Eq = 0, + Vs, + Ge, + Gt, + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdShImm.cs b/src/ARMeilleure/Decoders/OpCode32SimdShImm.cs new file mode 100644 index 000000000..040dce6f1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdShImm.cs @@ -0,0 +1,46 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdShImm : OpCode32Simd + { + public int Shift { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImm(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImm(inst, address, opCode, true); + + public OpCode32SimdShImm(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + int imm6 = (opCode >> 16) & 0x3f; + int limm6 = ((opCode >> 1) & 0x40) | imm6; + + if ((limm6 & 0x40) == 0b1000000) + { + Size = 3; + Shift = imm6; + } + else if ((limm6 & 0x60) == 0b0100000) + { + Size = 2; + Shift = imm6 - 32; + } + else if ((limm6 & 0x70) == 0b0010000) + { + Size = 1; + Shift = imm6 - 16; + } + else if ((limm6 & 0x78) == 0b0001000) + { + Size = 0; + Shift = imm6 - 8; + } + else + { + Instruction = InstDescriptor.Undefined; + } + + if (GetType() == typeof(OpCode32SimdShImm) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs new file mode 100644 index 000000000..13d89ca42 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs @@ -0,0 +1,43 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdShImmLong : OpCode32Simd + { + public int Shift { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmLong(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmLong(inst, address, opCode, true); + + public OpCode32SimdShImmLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + RegisterSize = RegisterSize.Simd64; + + int imm6 = (opCode >> 16) & 0x3f; + + if ((imm6 & 0x20) == 0b100000) + { + Size = 2; + Shift = imm6 - 32; + } + else if ((imm6 & 0x30) == 0b010000) + { + Size = 1; + Shift = imm6 - 16; + } + else if ((imm6 & 0x38) == 0b001000) + { + Size = 0; + Shift = imm6 - 8; + } + else + { + Instruction = InstDescriptor.Undefined; + } + + if (GetType() == typeof(OpCode32SimdShImmLong) && DecoderHelper.VectorArgumentsInvalid(true, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs b/src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs new file mode 100644 index 000000000..ce1e79069 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdShImmNarrow : OpCode32SimdShImm + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmNarrow(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmNarrow(inst, address, opCode, true); + + public OpCode32SimdShImmNarrow(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) { } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs b/src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs new file mode 100644 index 000000000..9b6f47321 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdSpecial : OpCode32 + { + public int Rt { get; } + public int Sreg { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSpecial(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSpecial(inst, address, opCode, true); + + public OpCode32SimdSpecial(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Rt = (opCode >> 12) & 0xf; + Sreg = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs b/src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs new file mode 100644 index 000000000..8f8fa4b03 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdSqrte : OpCode32Simd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSqrte(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSqrte(inst, address, opCode, true); + + public OpCode32SimdSqrte(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 18) & 0x1; + F = ((opCode >> 8) & 0x1) != 0; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdTbl.cs b/src/ARMeilleure/Decoders/OpCode32SimdTbl.cs new file mode 100644 index 000000000..fcac9e014 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdTbl.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdTbl : OpCode32SimdReg + { + public int Length { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdTbl(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdTbl(inst, address, opCode, true); + + public OpCode32SimdTbl(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Length = (opCode >> 8) & 3; + Size = 0; + Opc = Q ? 1 : 0; + Q = false; + RegisterSize = RegisterSize.Simd64; + + if (Vn + Length + 1 > 32) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32System.cs b/src/ARMeilleure/Decoders/OpCode32System.cs new file mode 100644 index 000000000..f6f5e0f96 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32System.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32System : OpCode32 + { + public int Opc1 { get; } + public int CRn { get; } + public int Rt { get; } + public int Opc2 { get; } + public int CRm { get; } + public int MrrcOp { get; } + + public int Coproc { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32System(inst, address, opCode); + + public OpCode32System(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Opc1 = (opCode >> 21) & 0x7; + CRn = (opCode >> 16) & 0xf; + Rt = (opCode >> 12) & 0xf; + Opc2 = (opCode >> 5) & 0x7; + CRm = (opCode >> 0) & 0xf; + MrrcOp = (opCode >> 4) & 0xf; + + Coproc = (opCode >> 8) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAdr.cs b/src/ARMeilleure/Decoders/OpCodeAdr.cs new file mode 100644 index 000000000..080280404 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAdr.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAdr : OpCode + { + public int Rd { get; } + + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAdr(inst, address, opCode); + + public OpCodeAdr(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = opCode & 0x1f; + + Immediate = DecoderHelper.DecodeImmS19_2(opCode); + Immediate |= ((long)opCode >> 29) & 3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAlu.cs b/src/ARMeilleure/Decoders/OpCodeAlu.cs new file mode 100644 index 000000000..1619ecd8e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAlu.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAlu : OpCode, IOpCodeAlu + { + public int Rd { get; protected set; } + public int Rn { get; } + + public DataOp DataOp { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAlu(inst, address, opCode); + + public OpCodeAlu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x1f; + Rn = (opCode >> 5) & 0x1f; + DataOp = (DataOp)((opCode >> 24) & 0x3); + + RegisterSize = (opCode >> 31) != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAluBinary.cs b/src/ARMeilleure/Decoders/OpCodeAluBinary.cs new file mode 100644 index 000000000..4413581ca --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluBinary.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluBinary : OpCodeAlu + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluBinary(inst, address, opCode); + + public OpCodeAluBinary(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAluImm.cs b/src/ARMeilleure/Decoders/OpCodeAluImm.cs new file mode 100644 index 000000000..0d2f7202f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluImm.cs @@ -0,0 +1,40 @@ +using System; + +namespace ARMeilleure.Decoders +{ + class OpCodeAluImm : OpCodeAlu, IOpCodeAluImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluImm(inst, address, opCode); + + public OpCodeAluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + if (DataOp == DataOp.Arithmetic) + { + Immediate = (opCode >> 10) & 0xfff; + + int shift = (opCode >> 22) & 3; + + Immediate <<= shift * 12; + } + else if (DataOp == DataOp.Logical) + { + var bm = DecoderHelper.DecodeBitMask(opCode, true); + + if (bm.IsUndefined) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Immediate = bm.WMask; + } + else + { + throw new ArgumentException($"Invalid data operation: {DataOp}", nameof(opCode)); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAluRs.cs b/src/ARMeilleure/Decoders/OpCodeAluRs.cs new file mode 100644 index 000000000..47a47e7d0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluRs.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluRs : OpCodeAlu, IOpCodeAluRs + { + public int Shift { get; } + public int Rm { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluRs(inst, address, opCode); + + public OpCodeAluRs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int shift = (opCode >> 10) & 0x3f; + + if (shift >= GetBitsCount()) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Shift = shift; + + Rm = (opCode >> 16) & 0x1f; + ShiftType = (ShiftType)((opCode >> 22) & 0x3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAluRx.cs b/src/ARMeilleure/Decoders/OpCodeAluRx.cs new file mode 100644 index 000000000..c21486788 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluRx.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluRx : OpCodeAlu, IOpCodeAluRx + { + public int Shift { get; } + public int Rm { get; } + + public IntType IntType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluRx(inst, address, opCode); + + public OpCodeAluRx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Shift = (opCode >> 10) & 0x7; + IntType = (IntType)((opCode >> 13) & 0x7); + Rm = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImm.cs b/src/ARMeilleure/Decoders/OpCodeBImm.cs new file mode 100644 index 000000000..2848c1409 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImm.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImm : OpCode, IOpCodeBImm + { + public long Immediate { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImm(inst, address, opCode); + + public OpCodeBImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImmAl.cs b/src/ARMeilleure/Decoders/OpCodeBImmAl.cs new file mode 100644 index 000000000..6c4b28c6c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmAl.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmAl : OpCodeBImm + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmAl(inst, address, opCode); + + public OpCodeBImmAl(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (long)address + DecoderHelper.DecodeImm26_2(opCode); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImmCmp.cs b/src/ARMeilleure/Decoders/OpCodeBImmCmp.cs new file mode 100644 index 000000000..c477ddecf --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmCmp.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmCmp : OpCodeBImm + { + public int Rt { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmCmp(inst, address, opCode); + + public OpCodeBImmCmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + + RegisterSize = (opCode >> 31) != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImmCond.cs b/src/ARMeilleure/Decoders/OpCodeBImmCond.cs new file mode 100644 index 000000000..7a51a0720 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmCond.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmCond : OpCodeBImm, IOpCodeCond + { + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmCond(inst, address, opCode); + + public OpCodeBImmCond(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int o0 = (opCode >> 4) & 1; + + if (o0 != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Cond = (Condition)(opCode & 0xf); + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImmTest.cs b/src/ARMeilleure/Decoders/OpCodeBImmTest.cs new file mode 100644 index 000000000..f989e59e4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmTest.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmTest : OpCodeBImm + { + public int Rt { get; } + public int Bit { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmTest(inst, address, opCode); + + public OpCodeBImmTest(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS14_2(opCode); + + Bit = (opCode >> 19) & 0x1f; + Bit |= (opCode >> 26) & 0x20; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBReg.cs b/src/ARMeilleure/Decoders/OpCodeBReg.cs new file mode 100644 index 000000000..3b84cf5c0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBReg.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBReg : OpCode + { + public int Rn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBReg(inst, address, opCode); + + public OpCodeBReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int op4 = (opCode >> 0) & 0x1f; + int op2 = (opCode >> 16) & 0x1f; + + if (op2 != 0b11111 || op4 != 0b00000) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Rn = (opCode >> 5) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBfm.cs b/src/ARMeilleure/Decoders/OpCodeBfm.cs new file mode 100644 index 000000000..d51efade2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBfm.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBfm : OpCodeAlu + { + public long WMask { get; } + public long TMask { get; } + public int Pos { get; } + public int Shift { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBfm(inst, address, opCode); + + public OpCodeBfm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + var bm = DecoderHelper.DecodeBitMask(opCode, false); + + if (bm.IsUndefined) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + WMask = bm.WMask; + TMask = bm.TMask; + Pos = bm.Pos; + Shift = bm.Shift; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeCcmp.cs b/src/ARMeilleure/Decoders/OpCodeCcmp.cs new file mode 100644 index 000000000..d40353486 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCcmp.cs @@ -0,0 +1,32 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeCcmp : OpCodeAlu, IOpCodeCond + { + public int Nzcv { get; } + protected int RmImm; + + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCcmp(inst, address, opCode); + + public OpCodeCcmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int o3 = (opCode >> 4) & 1; + + if (o3 != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Nzcv = (opCode >> 0) & 0xf; + Cond = (Condition)((opCode >> 12) & 0xf); + RmImm = (opCode >> 16) & 0x1f; + + Rd = RegisterAlias.Zr; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeCcmpImm.cs b/src/ARMeilleure/Decoders/OpCodeCcmpImm.cs new file mode 100644 index 000000000..9d6acf196 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCcmpImm.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCcmpImm : OpCodeCcmp, IOpCodeAluImm + { + public long Immediate => RmImm; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCcmpImm(inst, address, opCode); + + public OpCodeCcmpImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeCcmpReg.cs b/src/ARMeilleure/Decoders/OpCodeCcmpReg.cs new file mode 100644 index 000000000..349afa120 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCcmpReg.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCcmpReg : OpCodeCcmp, IOpCodeAluRs + { + public int Rm => RmImm; + + public int Shift => 0; + + public ShiftType ShiftType => ShiftType.Lsl; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCcmpReg(inst, address, opCode); + + public OpCodeCcmpReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeCsel.cs b/src/ARMeilleure/Decoders/OpCodeCsel.cs new file mode 100644 index 000000000..418962e08 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCsel.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCsel : OpCodeAlu, IOpCodeCond + { + public int Rm { get; } + + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCsel(inst, address, opCode); + + public OpCodeCsel(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 16) & 0x1f; + Cond = (Condition)((opCode >> 12) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeException.cs b/src/ARMeilleure/Decoders/OpCodeException.cs new file mode 100644 index 000000000..eee636405 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeException.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeException : OpCode + { + public int Id { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeException(inst, address, opCode); + + public OpCodeException(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Id = (opCode >> 5) & 0xffff; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMem.cs b/src/ARMeilleure/Decoders/OpCodeMem.cs new file mode 100644 index 000000000..9b4e5ff3e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMem.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMem : OpCode + { + public int Rt { get; protected set; } + public int Rn { get; protected set; } + public int Size { get; protected set; } + public bool Extend64 { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMem(inst, address, opCode); + + public OpCodeMem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 0x1f; + Rn = (opCode >> 5) & 0x1f; + Size = (opCode >> 30) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemEx.cs b/src/ARMeilleure/Decoders/OpCodeMemEx.cs new file mode 100644 index 000000000..1dc73140f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemEx.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemEx : OpCodeMem + { + public int Rt2 { get; } + public int Rs { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemEx(inst, address, opCode); + + public OpCodeMemEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 10) & 0x1f; + Rs = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemImm.cs b/src/ARMeilleure/Decoders/OpCodeMemImm.cs new file mode 100644 index 000000000..4d5eeb1ed --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemImm.cs @@ -0,0 +1,53 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemImm : OpCodeMem + { + public long Immediate { get; protected set; } + public bool WBack { get; protected set; } + public bool PostIdx { get; protected set; } + protected bool Unscaled { get; } + + private enum MemOp + { + Unscaled = 0, + PostIndexed = 1, + Unprivileged = 2, + PreIndexed = 3, + Unsigned, + } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemImm(inst, address, opCode); + + public OpCodeMemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Extend64 = ((opCode >> 22) & 3) == 2; + WBack = ((opCode >> 24) & 1) == 0; + + // The type is not valid for the Unsigned Immediate 12-bits encoding, + // because the bits 11:10 are used for the larger Immediate offset. + MemOp type = WBack ? (MemOp)((opCode >> 10) & 3) : MemOp.Unsigned; + + PostIdx = type == MemOp.PostIndexed; + Unscaled = type == MemOp.Unscaled || + type == MemOp.Unprivileged; + + // Unscaled and Unprivileged doesn't write back, + // but they do use the 9-bits Signed Immediate. + if (Unscaled) + { + WBack = false; + } + + if (WBack || Unscaled) + { + // 9-bits Signed Immediate. + Immediate = (opCode << 11) >> 23; + } + else + { + // 12-bits Unsigned Immediate. + Immediate = ((opCode >> 10) & 0xfff) << Size; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemLit.cs b/src/ARMeilleure/Decoders/OpCodeMemLit.cs new file mode 100644 index 000000000..8712a78e3 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemLit.cs @@ -0,0 +1,44 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemLit : OpCode, IOpCodeLit + { + public int Rt { get; } + public long Immediate { get; } + public int Size { get; } + public bool Signed { get; } + public bool Prefetch { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemLit(inst, address, opCode); + + public OpCodeMemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + + switch ((opCode >> 30) & 3) + { + case 0: + Size = 2; + Signed = false; + Prefetch = false; + break; + case 1: + Size = 3; + Signed = false; + Prefetch = false; + break; + case 2: + Size = 2; + Signed = true; + Prefetch = false; + break; + case 3: + Size = 0; + Signed = false; + Prefetch = true; + break; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemPair.cs b/src/ARMeilleure/Decoders/OpCodeMemPair.cs new file mode 100644 index 000000000..eb696cfeb --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemPair.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemPair : OpCodeMemImm + { + public int Rt2 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemPair(inst, address, opCode); + + public OpCodeMemPair(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 10) & 0x1f; + WBack = ((opCode >> 23) & 0x1) != 0; + PostIdx = ((opCode >> 23) & 0x3) == 1; + Extend64 = ((opCode >> 30) & 0x3) == 1; + Size = ((opCode >> 31) & 0x1) | 2; + + DecodeImm(opCode); + } + + protected void DecodeImm(int opCode) + { + Immediate = ((long)(opCode >> 15) << 57) >> (57 - Size); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemReg.cs b/src/ARMeilleure/Decoders/OpCodeMemReg.cs new file mode 100644 index 000000000..9b0d15959 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemReg.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemReg : OpCodeMem + { + public bool Shift { get; } + public int Rm { get; } + + public IntType IntType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemReg(inst, address, opCode); + + public OpCodeMemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Shift = ((opCode >> 12) & 0x1) != 0; + IntType = (IntType)((opCode >> 13) & 0x7); + Rm = (opCode >> 16) & 0x1f; + Extend64 = ((opCode >> 22) & 0x3) == 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMov.cs b/src/ARMeilleure/Decoders/OpCodeMov.cs new file mode 100644 index 000000000..a2914b71c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMov.cs @@ -0,0 +1,38 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMov : OpCode + { + public int Rd { get; } + + public long Immediate { get; } + + public int Bit { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMov(inst, address, opCode); + + public OpCodeMov(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int p1 = (opCode >> 22) & 1; + int sf = (opCode >> 31) & 1; + + if (sf == 0 && p1 != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Rd = (opCode >> 0) & 0x1f; + Immediate = (opCode >> 5) & 0xffff; + Bit = (opCode >> 21) & 0x3; + + Bit <<= 4; + + Immediate <<= Bit; + + RegisterSize = (opCode >> 31) != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMul.cs b/src/ARMeilleure/Decoders/OpCodeMul.cs new file mode 100644 index 000000000..9b1dd37b8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMul.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMul : OpCodeAlu + { + public int Rm { get; } + public int Ra { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMul(inst, address, opCode); + + public OpCodeMul(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Ra = (opCode >> 10) & 0x1f; + Rm = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimd.cs b/src/ARMeilleure/Decoders/OpCodeSimd.cs new file mode 100644 index 000000000..bd34d74d9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimd.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimd : OpCode, IOpCodeSimd + { + public int Rd { get; } + public int Rn { get; } + public int Opc { get; } + public int Size { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimd(inst, address, opCode); + + public OpCodeSimd(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x1f; + Rn = (opCode >> 5) & 0x1f; + Opc = (opCode >> 15) & 0x3; + Size = (opCode >> 22) & 0x3; + + RegisterSize = ((opCode >> 30) & 1) != 0 + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdCvt.cs b/src/ARMeilleure/Decoders/OpCodeSimdCvt.cs new file mode 100644 index 000000000..e50cf12e6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdCvt.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdCvt : OpCodeSimd + { + public int FBits { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdCvt(inst, address, opCode); + + public OpCodeSimdCvt(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int scale = (opCode >> 10) & 0x3f; + int sf = (opCode >> 31) & 0x1; + + FBits = 64 - scale; + + RegisterSize = sf != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdExt.cs b/src/ARMeilleure/Decoders/OpCodeSimdExt.cs new file mode 100644 index 000000000..0a3359e13 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdExt.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdExt : OpCodeSimdReg + { + public int Imm4 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdExt(inst, address, opCode); + + public OpCodeSimdExt(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Imm4 = (opCode >> 11) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdFcond.cs b/src/ARMeilleure/Decoders/OpCodeSimdFcond.cs new file mode 100644 index 000000000..510cd3101 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdFcond.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdFcond : OpCodeSimdReg, IOpCodeCond + { + public int Nzcv { get; } + + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdFcond(inst, address, opCode); + + public OpCodeSimdFcond(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Nzcv = (opCode >> 0) & 0xf; + Cond = (Condition)((opCode >> 12) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdFmov.cs b/src/ARMeilleure/Decoders/OpCodeSimdFmov.cs new file mode 100644 index 000000000..662abe284 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdFmov.cs @@ -0,0 +1,32 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdFmov : OpCode, IOpCodeSimd + { + public int Rd { get; } + public long Immediate { get; } + public int Size { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdFmov(inst, address, opCode); + + public OpCodeSimdFmov(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int type = (opCode >> 22) & 0x3; + + Size = type; + + long imm; + + Rd = (opCode >> 0) & 0x1f; + imm = (opCode >> 13) & 0xff; + + if (type == 0) + { + Immediate = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + } + else /* if (type == 1) */ + { + Immediate = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdHelper.cs b/src/ARMeilleure/Decoders/OpCodeSimdHelper.cs new file mode 100644 index 000000000..b006cc954 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdHelper.cs @@ -0,0 +1,91 @@ +namespace ARMeilleure.Decoders +{ + public static class OpCodeSimdHelper + { + public static (long Immediate, int Size) GetSimdImmediateAndSize(int cMode, int op, long imm) + { + int modeLow = cMode & 1; + int modeHigh = cMode >> 1; + int size = 0; + + if (modeHigh == 0b111) + { + switch (op | (modeLow << 1)) + { + case 0: + // 64-bits Immediate. + // Transform abcd efgh into abcd efgh abcd efgh ... + size = 3; + imm = (long)((ulong)imm * 0x0101010101010101); + break; + + case 1: + // 64-bits Immediate. + // Transform abcd efgh into aaaa aaaa bbbb bbbb ... + size = 3; + imm = (imm & 0xf0) >> 4 | (imm & 0x0f) << 4; + imm = (imm & 0xcc) >> 2 | (imm & 0x33) << 2; + imm = (imm & 0xaa) >> 1 | (imm & 0x55) << 1; + + imm = (long)((ulong)imm * 0x8040201008040201); + imm = (long)((ulong)imm & 0x8080808080808080); + + imm |= imm >> 4; + imm |= imm >> 2; + imm |= imm >> 1; + break; + + case 2: + // 2 x 32-bits floating point Immediate. + size = 3; + imm = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + imm |= imm << 32; + break; + + case 3: + // 64-bits floating point Immediate. + size = 3; + imm = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + break; + } + } + else if ((modeHigh & 0b110) == 0b100) + { + // 16-bits shifted Immediate. + size = 1; + imm <<= (modeHigh & 1) << 3; + } + else if ((modeHigh & 0b100) == 0b000) + { + // 32-bits shifted Immediate. + size = 2; + imm <<= modeHigh << 3; + } + else if ((modeHigh & 0b111) == 0b110) + { + // 32-bits shifted Immediate (fill with ones). + size = 2; + imm = ShlOnes(imm, 8 << modeLow); + } + else + { + // 8-bits without shift. + size = 0; + } + + return (imm, size); + } + + private static long ShlOnes(long value, int shift) + { + if (shift != 0) + { + return value << shift | (long)(ulong.MaxValue >> (64 - shift)); + } + else + { + return value; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdImm.cs b/src/ARMeilleure/Decoders/OpCodeSimdImm.cs new file mode 100644 index 000000000..3f4bad7f7 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdImm.cs @@ -0,0 +1,110 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdImm : OpCode, IOpCodeSimd + { + public int Rd { get; } + public long Immediate { get; } + public int Size { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdImm(inst, address, opCode); + + public OpCodeSimdImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = opCode & 0x1f; + + int cMode = (opCode >> 12) & 0xf; + int op = (opCode >> 29) & 0x1; + + int modeLow = cMode & 1; + int modeHigh = cMode >> 1; + + long imm; + + imm = ((uint)opCode >> 5) & 0x1f; + imm |= ((uint)opCode >> 11) & 0xe0; + + if (modeHigh == 0b111) + { + switch (op | (modeLow << 1)) + { + case 0: + // 64-bits Immediate. + // Transform abcd efgh into abcd efgh abcd efgh ... + Size = 3; + imm = (long)((ulong)imm * 0x0101010101010101); + break; + + case 1: + // 64-bits Immediate. + // Transform abcd efgh into aaaa aaaa bbbb bbbb ... + Size = 3; + imm = (imm & 0xf0) >> 4 | (imm & 0x0f) << 4; + imm = (imm & 0xcc) >> 2 | (imm & 0x33) << 2; + imm = (imm & 0xaa) >> 1 | (imm & 0x55) << 1; + + imm = (long)((ulong)imm * 0x8040201008040201); + imm = (long)((ulong)imm & 0x8080808080808080); + + imm |= imm >> 4; + imm |= imm >> 2; + imm |= imm >> 1; + break; + + case 2: + // 2 x 32-bits floating point Immediate. + Size = 0; + imm = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + imm |= imm << 32; + break; + + case 3: + // 64-bits floating point Immediate. + Size = 1; + imm = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + break; + } + } + else if ((modeHigh & 0b110) == 0b100) + { + // 16-bits shifted Immediate. + Size = 1; + imm <<= (modeHigh & 1) << 3; + } + else if ((modeHigh & 0b100) == 0b000) + { + // 32-bits shifted Immediate. + Size = 2; + imm <<= modeHigh << 3; + } + else if ((modeHigh & 0b111) == 0b110) + { + // 32-bits shifted Immediate (fill with ones). + Size = 2; + imm = ShlOnes(imm, 8 << modeLow); + } + else + { + // 8-bits without shift. + Size = 0; + } + + Immediate = imm; + + RegisterSize = ((opCode >> 30) & 1) != 0 + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + } + + private static long ShlOnes(long value, int shift) + { + if (shift != 0) + { + return value << shift | (long)(ulong.MaxValue >> (64 - shift)); + } + else + { + return value; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdIns.cs b/src/ARMeilleure/Decoders/OpCodeSimdIns.cs new file mode 100644 index 000000000..95436879c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdIns.cs @@ -0,0 +1,44 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdIns : OpCodeSimd + { + public int SrcIndex { get; } + public int DstIndex { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdIns(inst, address, opCode); + + public OpCodeSimdIns(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm4 = (opCode >> 11) & 0xf; + int imm5 = (opCode >> 16) & 0x1f; + + if (imm5 == 0b10000) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Size = imm5 & -imm5; + + switch (Size) + { + case 1: + Size = 0; + break; + case 2: + Size = 1; + break; + case 4: + Size = 2; + break; + case 8: + Size = 3; + break; + } + + SrcIndex = imm4 >> Size; + DstIndex = imm5 >> (Size + 1); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs new file mode 100644 index 000000000..14a9d7c9c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemImm : OpCodeMemImm, IOpCodeSimd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemImm(inst, address, opCode); + + public OpCodeSimdMemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size |= (opCode >> 21) & 4; + + if (Size > 4) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + // Base class already shifts the immediate, we only + // need to shift it if size (scale) is 4, since this value is only set here. + if (!WBack && !Unscaled && Size == 4) + { + Immediate <<= 4; + } + + Extend64 = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs new file mode 100644 index 000000000..efa558bf9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemLit : OpCode, IOpCodeSimd, IOpCodeLit + { + public int Rt { get; } + public long Immediate { get; } + public int Size { get; } + public bool Signed => false; + public bool Prefetch => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemLit(inst, address, opCode); + + public OpCodeSimdMemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int opc = (opCode >> 30) & 3; + + if (opc == 3) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + + Size = opc + 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs new file mode 100644 index 000000000..c05b52494 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs @@ -0,0 +1,71 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemMs : OpCodeMemReg, IOpCodeSimd + { + public int Reps { get; } + public int SElems { get; } + public int Elems { get; } + public bool WBack { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemMs(inst, address, opCode); + + public OpCodeSimdMemMs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + switch ((opCode >> 12) & 0xf) + { + case 0b0000: + Reps = 1; + SElems = 4; + break; + case 0b0010: + Reps = 4; + SElems = 1; + break; + case 0b0100: + Reps = 1; + SElems = 3; + break; + case 0b0110: + Reps = 3; + SElems = 1; + break; + case 0b0111: + Reps = 1; + SElems = 1; + break; + case 0b1000: + Reps = 1; + SElems = 2; + break; + case 0b1010: + Reps = 2; + SElems = 1; + break; + + default: + Instruction = InstDescriptor.Undefined; + return; + } + + Size = (opCode >> 10) & 3; + WBack = ((opCode >> 23) & 1) != 0; + + bool q = ((opCode >> 30) & 1) != 0; + + if (!q && Size == 3 && SElems != 1) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Extend64 = false; + + RegisterSize = q + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + + Elems = (GetBitsCount() >> 3) >> Size; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs new file mode 100644 index 000000000..697163896 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemPair : OpCodeMemPair, IOpCodeSimd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemPair(inst, address, opCode); + + public OpCodeSimdMemPair(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size = ((opCode >> 30) & 3) + 2; + + Extend64 = false; + + DecodeImm(opCode); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs new file mode 100644 index 000000000..be7b25b9d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemReg : OpCodeMemReg, IOpCodeSimd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemReg(inst, address, opCode); + + public OpCodeSimdMemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size |= (opCode >> 21) & 4; + + if (Size > 4) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Extend64 = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs new file mode 100644 index 000000000..5bc614e19 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs @@ -0,0 +1,97 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemSs : OpCodeMemReg, IOpCodeSimd + { + public int SElems { get; } + public int Index { get; } + public bool Replicate { get; } + public bool WBack { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemSs(inst, address, opCode); + + public OpCodeSimdMemSs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int size = (opCode >> 10) & 3; + int s = (opCode >> 12) & 1; + int sElems = (opCode >> 12) & 2; + int scale = (opCode >> 14) & 3; + int l = (opCode >> 22) & 1; + int q = (opCode >> 30) & 1; + + sElems |= (opCode >> 21) & 1; + + sElems++; + + int index = (q << 3) | (s << 2) | size; + + switch (scale) + { + case 1: + { + if ((size & 1) != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + index >>= 1; + + break; + } + + case 2: + { + if ((size & 2) != 0 || + ((size & 1) != 0 && s != 0)) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + if ((size & 1) != 0) + { + index >>= 3; + + scale = 3; + } + else + { + index >>= 2; + } + + break; + } + + case 3: + { + if (l == 0 || s != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + scale = size; + + Replicate = true; + + break; + } + } + + Index = index; + SElems = sElems; + Size = scale; + + Extend64 = false; + + WBack = ((opCode >> 23) & 1) != 0; + + RegisterSize = q != 0 + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdReg.cs b/src/ARMeilleure/Decoders/OpCodeSimdReg.cs new file mode 100644 index 000000000..40f9b1c53 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdReg.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdReg : OpCodeSimd + { + public bool Bit3 { get; } + public int Ra { get; } + public int Rm { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdReg(inst, address, opCode); + + public OpCodeSimdReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Bit3 = ((opCode >> 3) & 0x1) != 0; + Ra = (opCode >> 10) & 0x1f; + Rm = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs b/src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs new file mode 100644 index 000000000..bb248ab6b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs @@ -0,0 +1,33 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdRegElem : OpCodeSimdReg + { + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdRegElem(inst, address, opCode); + + public OpCodeSimdRegElem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + switch (Size) + { + case 1: + Index = (opCode >> 20) & 3 | + (opCode >> 9) & 4; + + Rm &= 0xf; + + break; + + case 2: + Index = (opCode >> 21) & 1 | + (opCode >> 10) & 2; + + break; + + default: + Instruction = InstDescriptor.Undefined; + break; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs b/src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs new file mode 100644 index 000000000..c97bd787e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs @@ -0,0 +1,35 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdRegElemF : OpCodeSimdReg + { + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdRegElemF(inst, address, opCode); + + public OpCodeSimdRegElemF(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + switch ((opCode >> 21) & 3) // sz:L + { + case 0: // H:0 + Index = (opCode >> 10) & 2; // 0, 2 + + break; + + case 1: // H:1 + Index = (opCode >> 10) & 2; + Index++; // 1, 3 + + break; + + case 2: // H + Index = (opCode >> 11) & 1; // 0, 1 + + break; + + default: + Instruction = InstDescriptor.Undefined; + break; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdShImm.cs b/src/ARMeilleure/Decoders/OpCodeSimdShImm.cs new file mode 100644 index 000000000..7064f1d2d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdShImm.cs @@ -0,0 +1,18 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + class OpCodeSimdShImm : OpCodeSimd + { + public int Imm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdShImm(inst, address, opCode); + + public OpCodeSimdShImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Imm = (opCode >> 16) & 0x7f; + + Size = BitUtils.HighestBitSetNibble(Imm >> 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdTbl.cs b/src/ARMeilleure/Decoders/OpCodeSimdTbl.cs new file mode 100644 index 000000000..3a7ef6aba --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdTbl.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdTbl : OpCodeSimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdTbl(inst, address, opCode); + + public OpCodeSimdTbl(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size = ((opCode >> 13) & 3) + 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSystem.cs b/src/ARMeilleure/Decoders/OpCodeSystem.cs new file mode 100644 index 000000000..215134153 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSystem.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSystem : OpCode + { + public int Rt { get; } + public int Op2 { get; } + public int CRm { get; } + public int CRn { get; } + public int Op1 { get; } + public int Op0 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSystem(inst, address, opCode); + + public OpCodeSystem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 0x1f; + Op2 = (opCode >> 5) & 0x7; + CRm = (opCode >> 8) & 0xf; + CRn = (opCode >> 12) & 0xf; + Op1 = (opCode >> 16) & 0x7; + Op0 = ((opCode >> 19) & 0x1) | 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16.cs b/src/ARMeilleure/Decoders/OpCodeT16.cs new file mode 100644 index 000000000..de946b961 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16 : OpCode32 + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16(inst, address, opCode); + + public OpCodeT16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = Condition.Al; + + IsThumb = true; + OpCodeSizeInBytes = 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs b/src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs new file mode 100644 index 000000000..683d638a8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AddSubImm3 : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public int Immediate { get; } + + public bool IsRotated { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubImm3(inst, address, opCode); + + public OpCodeT16AddSubImm3(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 3) & 0x7; + Immediate = (opCode >> 6) & 0x7; + IsRotated = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs b/src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs new file mode 100644 index 000000000..201fc8aab --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AddSubReg : OpCodeT16, IOpCode32AluReg + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubReg(inst, address, opCode); + + public OpCodeT16AddSubReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 3) & 0x7; + Rm = (opCode >> 6) & 0x7; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs b/src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs new file mode 100644 index 000000000..b66fe0cdf --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs @@ -0,0 +1,23 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16AddSubSp : OpCodeT16, IOpCode32AluImm + { + public int Rd => RegisterAlias.Aarch32Sp; + public int Rn => RegisterAlias.Aarch32Sp; + + public bool? SetFlags => false; + + public int Immediate { get; } + + public bool IsRotated => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubSp(inst, address, opCode); + + public OpCodeT16AddSubSp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = ((opCode >> 0) & 0x7f) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16Adr.cs b/src/ARMeilleure/Decoders/OpCodeT16Adr.cs new file mode 100644 index 000000000..03abd499d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16Adr.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16Adr : OpCodeT16, IOpCode32Adr + { + public int Rd { get; } + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16Adr(inst, address, opCode); + + public OpCodeT16Adr(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 7; + + int imm = (opCode & 0xff) << 2; + Immediate = (int)(GetPc() & 0xfffffffc) + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs b/src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs new file mode 100644 index 000000000..122698d7e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluImm8 : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public int Immediate { get; } + + public bool IsRotated { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluImm8(inst, address, opCode); + + public OpCodeT16AluImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0x7; + Rn = (opCode >> 8) & 0x7; + Immediate = (opCode >> 0) & 0xff; + IsRotated = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs b/src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs new file mode 100644 index 000000000..f67a75f96 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluImmZero : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public int Immediate { get; } + + public bool IsRotated { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluImmZero(inst, address, opCode); + + public OpCodeT16AluImmZero(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 3) & 0x7; + Immediate = 0; + IsRotated = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs b/src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs new file mode 100644 index 000000000..5458f65f2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluRegHigh : OpCodeT16, IOpCode32AluReg + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluRegHigh(inst, address, opCode); + + public OpCodeT16AluRegHigh(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = ((opCode >> 0) & 0x7) | ((opCode >> 4) & 0x8); + Rn = ((opCode >> 0) & 0x7) | ((opCode >> 4) & 0x8); + Rm = (opCode >> 3) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs b/src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs new file mode 100644 index 000000000..f86f48bd4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluRegLow : OpCodeT16, IOpCode32AluReg + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluRegLow(inst, address, opCode); + + public OpCodeT16AluRegLow(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 0) & 0x7; + Rm = (opCode >> 3) & 0x7; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluUx.cs b/src/ARMeilleure/Decoders/OpCodeT16AluUx.cs new file mode 100644 index 000000000..11d3a8fe8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluUx.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluUx : OpCodeT16, IOpCode32AluUx + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => false; + + public int RotateBits => 0; + public bool Add => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluUx(inst, address, opCode); + + public OpCodeT16AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rm = (opCode >> 3) & 0x7; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BImm11.cs b/src/ARMeilleure/Decoders/OpCodeT16BImm11.cs new file mode 100644 index 000000000..5ed8a4e6c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BImm11.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BImm11 : OpCodeT16, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImm11(inst, address, opCode); + + public OpCodeT16BImm11(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm = (opCode << 21) >> 20; + Immediate = GetPc() + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BImm8.cs b/src/ARMeilleure/Decoders/OpCodeT16BImm8.cs new file mode 100644 index 000000000..85318e5be --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BImm8.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BImm8 : OpCodeT16, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImm8(inst, address, opCode); + + public OpCodeT16BImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = (Condition)((opCode >> 8) & 0xf); + + int imm = (opCode << 24) >> 23; + Immediate = GetPc() + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs b/src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs new file mode 100644 index 000000000..68ebac75c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BImmCmp : OpCodeT16, IOpCode32BImm + { + public int Rn { get; } + + public long Immediate { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImmCmp(inst, address, opCode); + + public OpCodeT16BImmCmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0x7; + + int imm = ((opCode >> 2) & 0x3e) | ((opCode >> 3) & 0x40); + Immediate = (int)GetPc() + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BReg.cs b/src/ARMeilleure/Decoders/OpCodeT16BReg.cs new file mode 100644 index 000000000..da2a007a5 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BReg : OpCodeT16, IOpCode32BReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BReg(inst, address, opCode); + + public OpCodeT16BReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 3) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16Exception.cs b/src/ARMeilleure/Decoders/OpCodeT16Exception.cs new file mode 100644 index 000000000..8ccdf09ba --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16Exception.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16Exception : OpCodeT16, IOpCode32Exception + { + public int Id { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16Exception(inst, address, opCode); + + public OpCodeT16Exception(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Id = opCode & 0xFF; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16IfThen.cs b/src/ARMeilleure/Decoders/OpCodeT16IfThen.cs new file mode 100644 index 000000000..ea435a79b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16IfThen.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16IfThen : OpCodeT16 + { + public Condition[] IfThenBlockConds { get; } + + public int IfThenBlockSize { get { return IfThenBlockConds.Length; } } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16IfThen(inst, address, opCode); + + public OpCodeT16IfThen(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + List conds = new(); + + int cond = (opCode >> 4) & 0xf; + int mask = opCode & 0xf; + + conds.Add((Condition)cond); + + while ((mask & 7) != 0) + { + int newLsb = (mask >> 3) & 1; + cond = (cond & 0xe) | newLsb; + mask <<= 1; + conds.Add((Condition)cond); + } + + IfThenBlockConds = conds.ToArray(); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs b/src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs new file mode 100644 index 000000000..e9b383989 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs @@ -0,0 +1,48 @@ +using ARMeilleure.Instructions; +using System; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemImm5 : OpCodeT16, IOpCode32Mem + { + public int Rt { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemImm5(inst, address, opCode); + + public OpCodeT16MemImm5(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 7; + Rn = (opCode >> 3) & 7; + + switch (inst.Name) + { + case InstName.Ldr: + case InstName.Ldrb: + case InstName.Ldrh: + IsLoad = true; + break; + case InstName.Str: + case InstName.Strb: + case InstName.Strh: + IsLoad = false; + break; + } + + Immediate = inst.Name switch + { + InstName.Str or InstName.Ldr => ((opCode >> 6) & 0x1f) << 2, + InstName.Strb or InstName.Ldrb => ((opCode >> 6) & 0x1f), + InstName.Strh or InstName.Ldrh => ((opCode >> 6) & 0x1f) << 1, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemLit.cs b/src/ARMeilleure/Decoders/OpCodeT16MemLit.cs new file mode 100644 index 000000000..63a452ad3 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemLit.cs @@ -0,0 +1,26 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemLit : OpCodeT16, IOpCode32Mem + { + public int Rt { get; } + public int Rn => RegisterAlias.Aarch32Pc; + + public bool WBack => false; + public bool IsLoad => true; + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemLit(inst, address, opCode); + + public OpCodeT16MemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 8) & 7; + + Immediate = (opCode & 0xff) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemMult.cs b/src/ARMeilleure/Decoders/OpCodeT16MemMult.cs new file mode 100644 index 000000000..92b027a6e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemMult.cs @@ -0,0 +1,34 @@ +using ARMeilleure.Instructions; +using System; +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemMult : OpCodeT16, IOpCode32MemMult + { + public int Rn { get; } + public int RegisterMask { get; } + public int PostOffset { get; } + public bool IsLoad { get; } + public int Offset { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemMult(inst, address, opCode); + + public OpCodeT16MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RegisterMask = opCode & 0xff; + Rn = (opCode >> 8) & 7; + + int regCount = BitOperations.PopCount((uint)RegisterMask); + + Offset = 0; + PostOffset = 4 * regCount; + IsLoad = inst.Name switch + { + InstName.Ldm => true, + InstName.Stm => false, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemReg.cs b/src/ARMeilleure/Decoders/OpCodeT16MemReg.cs new file mode 100644 index 000000000..17d6966b2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemReg.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemReg : OpCodeT16, IOpCode32MemReg + { + public int Rm { get; } + public int Rt { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate => throw new System.InvalidOperationException(); + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemReg(inst, address, opCode); + + public OpCodeT16MemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 7; + Rn = (opCode >> 3) & 7; + Rm = (opCode >> 6) & 7; + + IsLoad = ((opCode >> 9) & 7) >= 3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemSp.cs b/src/ARMeilleure/Decoders/OpCodeT16MemSp.cs new file mode 100644 index 000000000..ed42679a5 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemSp.cs @@ -0,0 +1,28 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemSp : OpCodeT16, IOpCode32Mem + { + public int Rt { get; } + public int Rn => RegisterAlias.Aarch32Sp; + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemSp(inst, address, opCode); + + public OpCodeT16MemSp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 8) & 7; + + IsLoad = ((opCode >> 11) & 1) != 0; + + Immediate = ((opCode >> 0) & 0xff) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemStack.cs b/src/ARMeilleure/Decoders/OpCodeT16MemStack.cs new file mode 100644 index 000000000..28d5db4d9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemStack.cs @@ -0,0 +1,42 @@ +using ARMeilleure.Instructions; +using ARMeilleure.State; +using System; +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemStack : OpCodeT16, IOpCode32MemMult + { + public int Rn => RegisterAlias.Aarch32Sp; + public int RegisterMask { get; } + public int PostOffset { get; } + public bool IsLoad { get; } + public int Offset { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemStack(inst, address, opCode); + + public OpCodeT16MemStack(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int extra = (opCode >> 8) & 1; + int regCount = BitOperations.PopCount((uint)opCode & 0x1ff); + + switch (inst.Name) + { + case InstName.Push: + RegisterMask = (opCode & 0xff) | (extra << 14); + IsLoad = false; + Offset = -4 * regCount; + PostOffset = -4 * regCount; + break; + case InstName.Pop: + RegisterMask = (opCode & 0xff) | (extra << 15); + IsLoad = true; + Offset = 0; + PostOffset = 4 * regCount; + break; + default: + throw new InvalidOperationException(); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs b/src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs new file mode 100644 index 000000000..18e7b9e2e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16ShiftImm : OpCodeT16, IOpCode32AluRsImm + { + public int Rd { get; } + public int Rn { get; } + public int Rm { get; } + + public int Immediate { get; } + public ShiftType ShiftType { get; } + + public bool? SetFlags => null; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16ShiftImm(inst, address, opCode); + + public OpCodeT16ShiftImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rm = (opCode >> 3) & 0x7; + Immediate = (opCode >> 6) & 0x1F; + ShiftType = (ShiftType)((opCode >> 11) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs b/src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs new file mode 100644 index 000000000..ce47dfb5d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16ShiftReg : OpCodeT16, IOpCode32AluRsReg + { + public int Rm { get; } + public int Rs { get; } + public int Rd { get; } + + public int Rn { get; } + + public ShiftType ShiftType { get; } + + public bool? SetFlags => null; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16ShiftReg(inst, address, opCode); + + public OpCodeT16ShiftReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 7; + Rm = (opCode >> 0) & 7; + Rn = (opCode >> 3) & 7; + Rs = (opCode >> 3) & 7; + + ShiftType = (ShiftType)(((opCode >> 6) & 1) | ((opCode >> 7) & 2)); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16SpRel.cs b/src/ARMeilleure/Decoders/OpCodeT16SpRel.cs new file mode 100644 index 000000000..d737f5bdd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16SpRel.cs @@ -0,0 +1,24 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16SpRel : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn => RegisterAlias.Aarch32Sp; + + public bool? SetFlags => false; + + public int Immediate { get; } + + public bool IsRotated => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16SpRel(inst, address, opCode); + + public OpCodeT16SpRel(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0x7; + Immediate = ((opCode >> 0) & 0xff) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32.cs b/src/ARMeilleure/Decoders/OpCodeT32.cs new file mode 100644 index 000000000..87a0520d9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32 : OpCode32 + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32(inst, address, opCode); + + public OpCodeT32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = Condition.Al; + + IsThumb = true; + OpCodeSizeInBytes = 4; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32Alu.cs b/src/ARMeilleure/Decoders/OpCodeT32Alu.cs new file mode 100644 index 000000000..cdef007a9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32Alu.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32Alu : OpCodeT32, IOpCode32Alu + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Alu(inst, address, opCode); + + public OpCodeT32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0xf; + Rn = (opCode >> 16) & 0xf; + + SetFlags = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluBf.cs b/src/ARMeilleure/Decoders/OpCodeT32AluBf.cs new file mode 100644 index 000000000..57ad422fd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluBf.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluBf : OpCodeT32, IOpCode32AluBf + { + public int Rd { get; } + public int Rn { get; } + + public int Msb { get; } + public int Lsb { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluBf(inst, address, opCode); + + public OpCodeT32AluBf(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Msb = (opCode >> 0) & 0x1f; + Lsb = ((opCode >> 6) & 0x3) | ((opCode >> 10) & 0x1c); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluImm.cs b/src/ARMeilleure/Decoders/OpCodeT32AluImm.cs new file mode 100644 index 000000000..ce88964c9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluImm.cs @@ -0,0 +1,38 @@ +using ARMeilleure.Common; +using System.Runtime.Intrinsics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluImm : OpCodeT32Alu, IOpCode32AluImm + { + public int Immediate { get; } + + public bool IsRotated { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm(inst, address, opCode); + + private static readonly Vector128 _factor = Vector128.Create(1, 0x00010001, 0x01000100, 0x01010101); + + public OpCodeT32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm8 = (opCode >> 0) & 0xff; + int imm3 = (opCode >> 12) & 7; + int imm1 = (opCode >> 26) & 1; + + int imm12 = imm8 | (imm3 << 8) | (imm1 << 11); + + if ((imm12 >> 10) == 0) + { + Immediate = imm8 * _factor.GetElement((imm12 >> 8) & 3); + IsRotated = false; + } + else + { + int shift = imm12 >> 7; + + Immediate = BitUtils.RotateRight(0x80 | (imm12 & 0x7f), shift, 32); + IsRotated = shift != 0; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs b/src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs new file mode 100644 index 000000000..12b65a100 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluImm12 : OpCodeT32Alu, IOpCode32AluImm + { + public int Immediate { get; } + + public bool IsRotated => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm12(inst, address, opCode); + + public OpCodeT32AluImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (opCode & 0xff) | ((opCode >> 4) & 0x700) | ((opCode >> 15) & 0x800); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluMla.cs b/src/ARMeilleure/Decoders/OpCodeT32AluMla.cs new file mode 100644 index 000000000..6cb604da6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluMla.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluMla : OpCodeT32, IOpCode32AluMla + { + public int Rn { get; } + public int Rm { get; } + public int Ra { get; } + public int Rd { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + public bool R { get; } + public bool? SetFlags => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluMla(inst, address, opCode); + + public OpCodeT32AluMla(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rd = (opCode >> 8) & 0xf; + Ra = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + R = (opCode & (1 << 4)) != 0; + + MHigh = ((opCode >> 4) & 0x1) == 1; + NHigh = ((opCode >> 5) & 0x1) == 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluReg.cs b/src/ARMeilleure/Decoders/OpCodeT32AluReg.cs new file mode 100644 index 000000000..4ac983470 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluReg : OpCodeT32Alu, IOpCode32AluReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluReg(inst, address, opCode); + + public OpCodeT32AluReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs b/src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs new file mode 100644 index 000000000..dad0d9575 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluRsImm : OpCodeT32Alu, IOpCode32AluRsImm + { + public int Rm { get; } + public int Immediate { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluRsImm(inst, address, opCode); + + public OpCodeT32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Immediate = ((opCode >> 6) & 3) | ((opCode >> 10) & 0x1c); + + ShiftType = (ShiftType)((opCode >> 4) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs b/src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs new file mode 100644 index 000000000..a1b2e6120 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluUmull : OpCodeT32, IOpCode32AluUmull + { + public int RdLo { get; } + public int RdHi { get; } + public int Rn { get; } + public int Rm { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + + public bool? SetFlags => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluUmull(inst, address, opCode); + + public OpCodeT32AluUmull(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + RdHi = (opCode >> 8) & 0xf; + RdLo = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + MHigh = ((opCode >> 4) & 0x1) == 1; + NHigh = ((opCode >> 5) & 0x1) == 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluUx.cs b/src/ARMeilleure/Decoders/OpCodeT32AluUx.cs new file mode 100644 index 000000000..861dc904a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluUx.cs @@ -0,0 +1,18 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluUx : OpCodeT32AluReg, IOpCode32AluUx + { + public int Rotate { get; } + public int RotateBits => Rotate * 8; + public bool Add => Rn != RegisterAlias.Aarch32Pc; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluUx(inst, address, opCode); + + public OpCodeT32AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rotate = (opCode >> 4) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32BImm20.cs b/src/ARMeilleure/Decoders/OpCodeT32BImm20.cs new file mode 100644 index 000000000..793f82627 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32BImm20.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32BImm20 : OpCodeT32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm20(inst, address, opCode); + + public OpCodeT32BImm20(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + int imm11 = (opCode >> 0) & 0x7ff; + int j2 = (opCode >> 11) & 1; + int j1 = (opCode >> 13) & 1; + int imm6 = (opCode >> 16) & 0x3f; + int s = (opCode >> 26) & 1; + + int imm32 = imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19); + imm32 = (imm32 << 13) >> 12; + + Immediate = pc + imm32; + + Cond = (Condition)((opCode >> 22) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32BImm24.cs b/src/ARMeilleure/Decoders/OpCodeT32BImm24.cs new file mode 100644 index 000000000..d35ab8a45 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32BImm24.cs @@ -0,0 +1,35 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32BImm24 : OpCodeT32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm24(inst, address, opCode); + + public OpCodeT32BImm24(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + if (inst.Name == InstName.Blx) + { + pc &= ~3u; + } + + int imm11 = (opCode >> 0) & 0x7ff; + int j2 = (opCode >> 11) & 1; + int j1 = (opCode >> 13) & 1; + int imm10 = (opCode >> 16) & 0x3ff; + int s = (opCode >> 26) & 1; + + int i1 = j1 ^ s ^ 1; + int i2 = j2 ^ s ^ 1; + + int imm32 = imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23); + imm32 = (imm32 << 8) >> 7; + + Immediate = pc + imm32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs b/src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs new file mode 100644 index 000000000..aac8dbfba --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemImm12 : OpCodeT32, IOpCode32Mem + { + public int Rt { get; } + public int Rn { get; } + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm12(inst, address, opCode); + + public OpCodeT32MemImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Immediate = opCode & 0xfff; + + IsLoad = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs b/src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs new file mode 100644 index 000000000..d80ce86c5 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemImm8 : OpCodeT32, IOpCode32Mem + { + public int Rt { get; } + public int Rn { get; } + public bool WBack { get; } + public bool IsLoad { get; } + public bool Index { get; } + public bool Add { get; } + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8(inst, address, opCode); + + public OpCodeT32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Index = ((opCode >> 10) & 1) != 0; + Add = ((opCode >> 9) & 1) != 0; + WBack = ((opCode >> 8) & 1) != 0; + + Immediate = opCode & 0xff; + + IsLoad = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs b/src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs new file mode 100644 index 000000000..51f5042f2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemImm8D : OpCodeT32, IOpCode32Mem + { + public int Rt { get; } + public int Rt2 { get; } + public int Rn { get; } + public bool WBack { get; } + public bool IsLoad { get; } + public bool Index { get; } + public bool Add { get; } + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8D(inst, address, opCode); + + public OpCodeT32MemImm8D(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 8) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Index = ((opCode >> 24) & 1) != 0; + Add = ((opCode >> 23) & 1) != 0; + WBack = ((opCode >> 21) & 1) != 0; + + Immediate = (opCode & 0xff) << 2; + + IsLoad = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs b/src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs new file mode 100644 index 000000000..c8eb36b3e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs @@ -0,0 +1,26 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemLdEx : OpCodeT32, IOpCode32MemEx + { + public int Rd => 0; + public int Rt { get; } + public int Rt2 { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad => true; + public bool Index => false; + public bool Add => false; + + public int Immediate => 0; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemLdEx(inst, address, opCode); + + public OpCodeT32MemLdEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 8) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemMult.cs b/src/ARMeilleure/Decoders/OpCodeT32MemMult.cs new file mode 100644 index 000000000..d155842a6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemMult.cs @@ -0,0 +1,52 @@ +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemMult : OpCodeT32, IOpCode32MemMult + { + public int Rn { get; } + + public int RegisterMask { get; } + public int Offset { get; } + public int PostOffset { get; } + + public bool IsLoad { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemMult(inst, address, opCode); + + public OpCodeT32MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + RegisterMask = opCode & 0xffff; + + int regsSize = BitOperations.PopCount((uint)RegisterMask) * 4; + + if (!u) + { + Offset -= regsSize; + } + + if (u == p) + { + Offset += 4; + } + + if (w) + { + PostOffset = u ? regsSize : -regsSize; + } + else + { + PostOffset = 0; + } + + IsLoad = isLoad; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs b/src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs new file mode 100644 index 000000000..056d3b465 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemRsImm : OpCodeT32, IOpCode32MemRsImm + { + public int Rt { get; } + public int Rn { get; } + public int Rm { get; } + public ShiftType ShiftType => ShiftType.Lsl; + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemRsImm(inst, address, opCode); + + public OpCodeT32MemRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + IsLoad = (opCode & (1 << 20)) != 0; + + Immediate = (opCode >> 4) & 3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs b/src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs new file mode 100644 index 000000000..6a0a6bb13 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemStEx : OpCodeT32, IOpCode32MemEx + { + public int Rd { get; } + public int Rt { get; } + public int Rt2 { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad => false; + public bool Index => false; + public bool Add => false; + + public int Immediate => 0; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemStEx(inst, address, opCode); + + public OpCodeT32MemStEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0xf; + Rt2 = (opCode >> 8) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs b/src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs new file mode 100644 index 000000000..2f871c740 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MovImm16 : OpCodeT32Alu, IOpCode32AluImm16 + { + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MovImm16(inst, address, opCode); + + public OpCodeT32MovImm16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (opCode & 0xff) | ((opCode >> 4) & 0x700) | ((opCode >> 15) & 0x800) | ((opCode >> 4) & 0xf000); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs b/src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs new file mode 100644 index 000000000..360559756 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32ShiftReg : OpCodeT32Alu, IOpCode32AluRsReg + { + public int Rm => Rn; + public int Rs { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32ShiftReg(inst, address, opCode); + + public OpCodeT32ShiftReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rs = (opCode >> 0) & 0xf; + + ShiftType = (ShiftType)((opCode >> 21) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32Tb.cs b/src/ARMeilleure/Decoders/OpCodeT32Tb.cs new file mode 100644 index 000000000..0a4d2a6c4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32Tb.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32Tb : OpCodeT32, IOpCode32BReg + { + public int Rm { get; } + public int Rn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Tb(inst, address, opCode); + + public OpCodeT32Tb(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rn = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeTable.cs b/src/ARMeilleure/Decoders/OpCodeTable.cs new file mode 100644 index 000000000..9e13bd9b5 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeTable.cs @@ -0,0 +1,1513 @@ +using ARMeilleure.Instructions; +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + static class OpCodeTable + { + public delegate OpCode MakeOp(InstDescriptor inst, ulong address, int opCode); + + private const int FastLookupSize = 0x1000; + + private readonly struct InstInfo + { + public int Mask { get; } + public int Value { get; } + + public InstDescriptor Inst { get; } + + public MakeOp MakeOp { get; } + + public InstInfo(int mask, int value, InstDescriptor inst, MakeOp makeOp) + { + Mask = mask; + Value = value; + Inst = inst; + MakeOp = makeOp; + } + } + + private static readonly List _allInstA32 = new(); + private static readonly List _allInstT32 = new(); + private static readonly List _allInstA64 = new(); + + private static readonly InstInfo[][] _instA32FastLookup = new InstInfo[FastLookupSize][]; + private static readonly InstInfo[][] _instT32FastLookup = new InstInfo[FastLookupSize][]; + private static readonly InstInfo[][] _instA64FastLookup = new InstInfo[FastLookupSize][]; + + static OpCodeTable() + { +#pragma warning disable IDE0055 // Disable formatting + #region "OpCode Table (AArch64)" + // Base + SetA64("x0011010000xxxxx000000xxxxxxxxxx", InstName.Adc, InstEmit.Adc, OpCodeAluRs.Create); + SetA64("x0111010000xxxxx000000xxxxxxxxxx", InstName.Adcs, InstEmit.Adcs, OpCodeAluRs.Create); + SetA64("x00100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluImm.Create); + SetA64("00001011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRs.Create); + SetA64("10001011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRs.Create); + SetA64("x0001011001xxxxxxxx0xxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRx.Create); + SetA64("x0001011001xxxxxxxx100xxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRx.Create); + SetA64("x01100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluImm.Create); + SetA64("00101011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRs.Create); + SetA64("10101011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRs.Create); + SetA64("x0101011001xxxxxxxx0xxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRx.Create); + SetA64("x0101011001xxxxxxxx100xxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRx.Create); + SetA64("0xx10000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Adr, InstEmit.Adr, OpCodeAdr.Create); + SetA64("1xx10000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Adrp, InstEmit.Adrp, OpCodeAdr.Create); + SetA64("0001001000xxxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluImm.Create); + SetA64("100100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluImm.Create); + SetA64("00001010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluRs.Create); + SetA64("10001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluRs.Create); + SetA64("0111001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluImm.Create); + SetA64("111100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluImm.Create); + SetA64("01101010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluRs.Create); + SetA64("11101010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluRs.Create); + SetA64("x0011010110xxxxx001010xxxxxxxxxx", InstName.Asrv, InstEmit.Asrv, OpCodeAluRs.Create); + SetA64("000101xxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.B, InstEmit.B, OpCodeBImmAl.Create); + SetA64("01010100xxxxxxxxxxxxxxxxxxx0xxxx", InstName.B_Cond, InstEmit.B_Cond, OpCodeBImmCond.Create); + SetA64("00110011000xxxxx0xxxxxxxxxxxxxxx", InstName.Bfm, InstEmit.Bfm, OpCodeBfm.Create); + SetA64("1011001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Bfm, InstEmit.Bfm, OpCodeBfm.Create); + SetA64("00001010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit.Bic, OpCodeAluRs.Create); + SetA64("10001010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Bic, InstEmit.Bic, OpCodeAluRs.Create); + SetA64("01101010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Bics, InstEmit.Bics, OpCodeAluRs.Create); + SetA64("11101010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Bics, InstEmit.Bics, OpCodeAluRs.Create); + SetA64("100101xxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bl, InstEmit.Bl, OpCodeBImmAl.Create); + SetA64("1101011000111111000000xxxxx00000", InstName.Blr, InstEmit.Blr, OpCodeBReg.Create); + SetA64("1101011000011111000000xxxxx00000", InstName.Br, InstEmit.Br, OpCodeBReg.Create); + SetA64("11010100001xxxxxxxxxxxxxxxx00000", InstName.Brk, InstEmit.Brk, OpCodeException.Create); + SetA64("x0110101xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cbnz, InstEmit.Cbnz, OpCodeBImmCmp.Create); + SetA64("x0110100xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cbz, InstEmit.Cbz, OpCodeBImmCmp.Create); + SetA64("x0111010010xxxxxxxxx10xxxxx0xxxx", InstName.Ccmn, InstEmit.Ccmn, OpCodeCcmpImm.Create); + SetA64("x0111010010xxxxxxxxx00xxxxx0xxxx", InstName.Ccmn, InstEmit.Ccmn, OpCodeCcmpReg.Create); + SetA64("x1111010010xxxxxxxxx10xxxxx0xxxx", InstName.Ccmp, InstEmit.Ccmp, OpCodeCcmpImm.Create); + SetA64("x1111010010xxxxxxxxx00xxxxx0xxxx", InstName.Ccmp, InstEmit.Ccmp, OpCodeCcmpReg.Create); + SetA64("11010101000000110011xxxx01011111", InstName.Clrex, InstEmit.Clrex, OpCodeSystem.Create); + SetA64("x101101011000000000101xxxxxxxxxx", InstName.Cls, InstEmit.Cls, OpCodeAlu.Create); + SetA64("x101101011000000000100xxxxxxxxxx", InstName.Clz, InstEmit.Clz, OpCodeAlu.Create); + SetA64("00011010110xxxxx010000xxxxxxxxxx", InstName.Crc32b, InstEmit.Crc32b, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010001xxxxxxxxxx", InstName.Crc32h, InstEmit.Crc32h, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010010xxxxxxxxxx", InstName.Crc32w, InstEmit.Crc32w, OpCodeAluBinary.Create); + SetA64("10011010110xxxxx010011xxxxxxxxxx", InstName.Crc32x, InstEmit.Crc32x, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010100xxxxxxxxxx", InstName.Crc32cb, InstEmit.Crc32cb, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010101xxxxxxxxxx", InstName.Crc32ch, InstEmit.Crc32ch, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010110xxxxxxxxxx", InstName.Crc32cw, InstEmit.Crc32cw, OpCodeAluBinary.Create); + SetA64("10011010110xxxxx010111xxxxxxxxxx", InstName.Crc32cx, InstEmit.Crc32cx, OpCodeAluBinary.Create); + SetA64("11010101000000110010001010011111", InstName.Csdb, InstEmit.Csdb, OpCodeSystem.Create); + SetA64("x0011010100xxxxxxxxx00xxxxxxxxxx", InstName.Csel, InstEmit.Csel, OpCodeCsel.Create); + SetA64("x0011010100xxxxxxxxx01xxxxxxxxxx", InstName.Csinc, InstEmit.Csinc, OpCodeCsel.Create); + SetA64("x1011010100xxxxxxxxx00xxxxxxxxxx", InstName.Csinv, InstEmit.Csinv, OpCodeCsel.Create); + SetA64("x1011010100xxxxxxxxx01xxxxxxxxxx", InstName.Csneg, InstEmit.Csneg, OpCodeCsel.Create); + SetA64("11010101000000110011xxxx10111111", InstName.Dmb, InstEmit.Dmb, OpCodeSystem.Create); + SetA64("11010101000000110011xxxx10011111", InstName.Dsb, InstEmit.Dsb, OpCodeSystem.Create); + SetA64("01001010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Eon, InstEmit.Eon, OpCodeAluRs.Create); + SetA64("11001010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Eon, InstEmit.Eon, OpCodeAluRs.Create); + SetA64("0101001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluImm.Create); + SetA64("110100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluImm.Create); + SetA64("01001010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create); + SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create); + SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create); + SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create); + SetA64("11010101000000110010000011011111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("11010101000000110010000011111111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("110101010000001100100001xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("1101010100000011001000100xx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("1101010100000011001000101>>11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("110101010000001100100011xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("11010101000000110010>>xxxxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("11010101000000110011xxxx11011111", InstName.Isb, InstEmit.Isb, OpCodeSystem.Create); + SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, OpCodeMemEx.Create); + SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, OpCodeMemEx.Create); + SetA64("xx001000010xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxr, InstEmit.Ldaxr, OpCodeMemEx.Create); + SetA64("<<10100xx1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldp, InstEmit.Ldp, OpCodeMemPair.Create); + SetA64("xx111000010xxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeMemImm.Create); + SetA64("xx11100101xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeMemImm.Create); + SetA64("xx111000011xxxxxxxxx10xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeMemReg.Create); + SetA64("xx011000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr_Literal, InstEmit.Ldr_Literal, OpCodeMemLit.Create); + SetA64("0x1110001x0xxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("0x1110011xxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("10111000100xxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("1011100110xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("0x1110001x1xxxxxxxxx10xxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemReg.Create); + SetA64("10111000101xxxxxxxxx10xxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemReg.Create); + SetA64("xx001000010xxxxx0xxxxxxxxxxxxxxx", InstName.Ldxr, InstEmit.Ldxr, OpCodeMemEx.Create); + SetA64("1x001000011xxxxx0xxxxxxxxxxxxxxx", InstName.Ldxp, InstEmit.Ldxp, OpCodeMemEx.Create); + SetA64("x0011010110xxxxx001000xxxxxxxxxx", InstName.Lslv, InstEmit.Lslv, OpCodeAluRs.Create); + SetA64("x0011010110xxxxx001001xxxxxxxxxx", InstName.Lsrv, InstEmit.Lsrv, OpCodeAluRs.Create); + SetA64("x0011011000xxxxx0xxxxxxxxxxxxxxx", InstName.Madd, InstEmit.Madd, OpCodeMul.Create); + SetA64("0111001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movk, InstEmit.Movk, OpCodeMov.Create); + SetA64("111100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movk, InstEmit.Movk, OpCodeMov.Create); + SetA64("0001001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movn, InstEmit.Movn, OpCodeMov.Create); + SetA64("100100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movn, InstEmit.Movn, OpCodeMov.Create); + SetA64("0101001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movz, InstEmit.Movz, OpCodeMov.Create); + SetA64("110100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movz, InstEmit.Movz, OpCodeMov.Create); + SetA64("110101010011xxxxxxxxxxxxxxxxxxxx", InstName.Mrs, InstEmit.Mrs, OpCodeSystem.Create); + SetA64("110101010001xxxxxxxxxxxxxxxxxxxx", InstName.Msr, InstEmit.Msr, OpCodeSystem.Create); + SetA64("x0011011000xxxxx1xxxxxxxxxxxxxxx", InstName.Msub, InstEmit.Msub, OpCodeMul.Create); + SetA64("11010101000000110010000000011111", InstName.Nop, InstEmit.Nop, OpCodeSystem.Create); + SetA64("00101010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit.Orn, OpCodeAluRs.Create); + SetA64("10101010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Orn, InstEmit.Orn, OpCodeAluRs.Create); + SetA64("0011001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluImm.Create); + SetA64("101100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluImm.Create); + SetA64("00101010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluRs.Create); + SetA64("10101010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluRs.Create); + SetA64("1111100110xxxxxxxxxxxxxxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemImm.Create); // immediate + SetA64("11111000100xxxxxxxxx00xxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemImm.Create); // prfum (unscaled offset) + SetA64("11011000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemLit.Create); // literal + SetA64("11111000101xxxxxxxxx10xxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemReg.Create); // register + SetA64("x101101011000000000000xxxxxxxxxx", InstName.Rbit, InstEmit.Rbit, OpCodeAlu.Create); + SetA64("1101011001011111000000xxxxx00000", InstName.Ret, InstEmit.Ret, OpCodeBReg.Create); + SetA64("x101101011000000000001xxxxxxxxxx", InstName.Rev16, InstEmit.Rev16, OpCodeAlu.Create); + SetA64("x101101011000000000010xxxxxxxxxx", InstName.Rev32, InstEmit.Rev32, OpCodeAlu.Create); + SetA64("1101101011000000000011xxxxxxxxxx", InstName.Rev64, InstEmit.Rev64, OpCodeAlu.Create); + SetA64("x0011010110xxxxx001011xxxxxxxxxx", InstName.Rorv, InstEmit.Rorv, OpCodeAluRs.Create); + SetA64("x1011010000xxxxx000000xxxxxxxxxx", InstName.Sbc, InstEmit.Sbc, OpCodeAluRs.Create); + SetA64("x1111010000xxxxx000000xxxxxxxxxx", InstName.Sbcs, InstEmit.Sbcs, OpCodeAluRs.Create); + SetA64("00010011000xxxxx0xxxxxxxxxxxxxxx", InstName.Sbfm, InstEmit.Sbfm, OpCodeBfm.Create); + SetA64("1001001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Sbfm, InstEmit.Sbfm, OpCodeBfm.Create); + SetA64("x0011010110xxxxx000011xxxxxxxxxx", InstName.Sdiv, InstEmit.Sdiv, OpCodeAluBinary.Create); + SetA64("11010101000000110010000010011111", InstName.Sev, InstEmit.Nop, OpCodeSystem.Create); + SetA64("11010101000000110010000010111111", InstName.Sevl, InstEmit.Nop, OpCodeSystem.Create); + SetA64("10011011001xxxxx0xxxxxxxxxxxxxxx", InstName.Smaddl, InstEmit.Smaddl, OpCodeMul.Create); + SetA64("10011011001xxxxx1xxxxxxxxxxxxxxx", InstName.Smsubl, InstEmit.Smsubl, OpCodeMul.Create); + SetA64("10011011010xxxxx0xxxxxxxxxxxxxxx", InstName.Smulh, InstEmit.Smulh, OpCodeMul.Create); + SetA64("xx001000100xxxxx1xxxxxxxxxxxxxxx", InstName.Stlr, InstEmit.Stlr, OpCodeMemEx.Create); + SetA64("1x001000001xxxxx1xxxxxxxxxxxxxxx", InstName.Stlxp, InstEmit.Stlxp, OpCodeMemEx.Create); + SetA64("xx001000000xxxxx1xxxxxxxxxxxxxxx", InstName.Stlxr, InstEmit.Stlxr, OpCodeMemEx.Create); + SetA64("x010100xx0xxxxxxxxxxxxxxxxxxxxxx", InstName.Stp, InstEmit.Stp, OpCodeMemPair.Create); + SetA64("xx111000000xxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeMemImm.Create); + SetA64("xx11100100xxxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeMemImm.Create); + SetA64("xx111000001xxxxxxxxx10xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeMemReg.Create); + SetA64("1x001000001xxxxx0xxxxxxxxxxxxxxx", InstName.Stxp, InstEmit.Stxp, OpCodeMemEx.Create); + SetA64("xx001000000xxxxx0xxxxxxxxxxxxxxx", InstName.Stxr, InstEmit.Stxr, OpCodeMemEx.Create); + SetA64("x10100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluImm.Create); + SetA64("01001011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRs.Create); + SetA64("11001011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRs.Create); + SetA64("x1001011001xxxxxxxx0xxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRx.Create); + SetA64("x1001011001xxxxxxxx100xxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRx.Create); + SetA64("x11100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluImm.Create); + SetA64("01101011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRs.Create); + SetA64("11101011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRs.Create); + SetA64("x1101011001xxxxxxxx0xxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRx.Create); + SetA64("x1101011001xxxxxxxx100xxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRx.Create); + SetA64("11010100000xxxxxxxxxxxxxxxx00001", InstName.Svc, InstEmit.Svc, OpCodeException.Create); + SetA64("1101010100001xxxxxxxxxxxxxxxxxxx", InstName.Sys, InstEmit.Sys, OpCodeSystem.Create); + SetA64("x0110111xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tbnz, InstEmit.Tbnz, OpCodeBImmTest.Create); + SetA64("x0110110xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tbz, InstEmit.Tbz, OpCodeBImmTest.Create); + SetA64("01010011000xxxxx0xxxxxxxxxxxxxxx", InstName.Ubfm, InstEmit.Ubfm, OpCodeBfm.Create); + SetA64("1101001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Ubfm, InstEmit.Ubfm, OpCodeBfm.Create); + SetA64("x0011010110xxxxx000010xxxxxxxxxx", InstName.Udiv, InstEmit.Udiv, OpCodeAluBinary.Create); + SetA64("10011011101xxxxx0xxxxxxxxxxxxxxx", InstName.Umaddl, InstEmit.Umaddl, OpCodeMul.Create); + SetA64("10011011101xxxxx1xxxxxxxxxxxxxxx", InstName.Umsubl, InstEmit.Umsubl, OpCodeMul.Create); + SetA64("10011011110xxxxx0xxxxxxxxxxxxxxx", InstName.Umulh, InstEmit.Umulh, OpCodeMul.Create); + SetA64("11010101000000110010000001011111", InstName.Wfe, InstEmit.Nop, OpCodeSystem.Create); + SetA64("11010101000000110010000001111111", InstName.Wfi, InstEmit.Nop, OpCodeSystem.Create); + SetA64("11010101000000110010000000111111", InstName.Yield, InstEmit.Nop, OpCodeSystem.Create); + + // FP & SIMD + SetA64("0101111011100000101110xxxxxxxxxx", InstName.Abs_S, InstEmit.Abs_S, OpCodeSimd.Create); + SetA64("0>001110<<100000101110xxxxxxxxxx", InstName.Abs_V, InstEmit.Abs_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx100001xxxxxxxxxx", InstName.Add_S, InstEmit.Add_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx100001xxxxxxxxxx", InstName.Add_V, InstEmit.Add_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx010000xxxxxxxxxx", InstName.Addhn_V, InstEmit.Addhn_V, OpCodeSimdReg.Create); + SetA64("0101111011110001101110xxxxxxxxxx", InstName.Addp_S, InstEmit.Addp_S, OpCodeSimd.Create); + SetA64("0>001110<<1xxxxx101111xxxxxxxxxx", InstName.Addp_V, InstEmit.Addp_V, OpCodeSimdReg.Create); + SetA64("000011100x110001101110xxxxxxxxxx", InstName.Addv_V, InstEmit.Addv_V, OpCodeSimd.Create); + SetA64("01001110<<110001101110xxxxxxxxxx", InstName.Addv_V, InstEmit.Addv_V, OpCodeSimd.Create); + SetA64("0100111000101000010110xxxxxxxxxx", InstName.Aesd_V, InstEmit.Aesd_V, OpCodeSimd.Create); + SetA64("0100111000101000010010xxxxxxxxxx", InstName.Aese_V, InstEmit.Aese_V, OpCodeSimd.Create); + SetA64("0100111000101000011110xxxxxxxxxx", InstName.Aesimc_V, InstEmit.Aesimc_V, OpCodeSimd.Create); + SetA64("0100111000101000011010xxxxxxxxxx", InstName.Aesmc_V, InstEmit.Aesmc_V, OpCodeSimd.Create); + SetA64("0x001110001xxxxx000111xxxxxxxxxx", InstName.And_V, InstEmit.And_V, OpCodeSimdReg.Create); + SetA64("0x001110011xxxxx000111xxxxxxxxxx", InstName.Bic_V, InstEmit.Bic_V, OpCodeSimdReg.Create); + SetA64("0x10111100000xxx0xx101xxxxxxxxxx", InstName.Bic_Vi, InstEmit.Bic_Vi, OpCodeSimdImm.Create); + SetA64("0x10111100000xxx10x101xxxxxxxxxx", InstName.Bic_Vi, InstEmit.Bic_Vi, OpCodeSimdImm.Create); + SetA64("0x101110111xxxxx000111xxxxxxxxxx", InstName.Bif_V, InstEmit.Bif_V, OpCodeSimdReg.Create); + SetA64("0x101110101xxxxx000111xxxxxxxxxx", InstName.Bit_V, InstEmit.Bit_V, OpCodeSimdReg.Create); + SetA64("0x101110011xxxxx000111xxxxxxxxxx", InstName.Bsl_V, InstEmit.Bsl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100000010010xxxxxxxxxx", InstName.Cls_V, InstEmit.Cls_V, OpCodeSimd.Create); + SetA64("0x101110<<100000010010xxxxxxxxxx", InstName.Clz_V, InstEmit.Clz_V, OpCodeSimd.Create); + SetA64("01111110111xxxxx100011xxxxxxxxxx", InstName.Cmeq_S, InstEmit.Cmeq_S, OpCodeSimdReg.Create); + SetA64("0101111011100000100110xxxxxxxxxx", InstName.Cmeq_S, InstEmit.Cmeq_S, OpCodeSimd.Create); + SetA64("0>101110<<1xxxxx100011xxxxxxxxxx", InstName.Cmeq_V, InstEmit.Cmeq_V, OpCodeSimdReg.Create); + SetA64("0>001110<<100000100110xxxxxxxxxx", InstName.Cmeq_V, InstEmit.Cmeq_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx001111xxxxxxxxxx", InstName.Cmge_S, InstEmit.Cmge_S, OpCodeSimdReg.Create); + SetA64("0111111011100000100010xxxxxxxxxx", InstName.Cmge_S, InstEmit.Cmge_S, OpCodeSimd.Create); + SetA64("0>001110<<1xxxxx001111xxxxxxxxxx", InstName.Cmge_V, InstEmit.Cmge_V, OpCodeSimdReg.Create); + SetA64("0>101110<<100000100010xxxxxxxxxx", InstName.Cmge_V, InstEmit.Cmge_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx001101xxxxxxxxxx", InstName.Cmgt_S, InstEmit.Cmgt_S, OpCodeSimdReg.Create); + SetA64("0101111011100000100010xxxxxxxxxx", InstName.Cmgt_S, InstEmit.Cmgt_S, OpCodeSimd.Create); + SetA64("0>001110<<1xxxxx001101xxxxxxxxxx", InstName.Cmgt_V, InstEmit.Cmgt_V, OpCodeSimdReg.Create); + SetA64("0>001110<<100000100010xxxxxxxxxx", InstName.Cmgt_V, InstEmit.Cmgt_V, OpCodeSimd.Create); + SetA64("01111110111xxxxx001101xxxxxxxxxx", InstName.Cmhi_S, InstEmit.Cmhi_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx001101xxxxxxxxxx", InstName.Cmhi_V, InstEmit.Cmhi_V, OpCodeSimdReg.Create); + SetA64("01111110111xxxxx001111xxxxxxxxxx", InstName.Cmhs_S, InstEmit.Cmhs_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx001111xxxxxxxxxx", InstName.Cmhs_V, InstEmit.Cmhs_V, OpCodeSimdReg.Create); + SetA64("0111111011100000100110xxxxxxxxxx", InstName.Cmle_S, InstEmit.Cmle_S, OpCodeSimd.Create); + SetA64("0>101110<<100000100110xxxxxxxxxx", InstName.Cmle_V, InstEmit.Cmle_V, OpCodeSimd.Create); + SetA64("0101111011100000101010xxxxxxxxxx", InstName.Cmlt_S, InstEmit.Cmlt_S, OpCodeSimd.Create); + SetA64("0>001110<<100000101010xxxxxxxxxx", InstName.Cmlt_V, InstEmit.Cmlt_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx100011xxxxxxxxxx", InstName.Cmtst_S, InstEmit.Cmtst_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx100011xxxxxxxxxx", InstName.Cmtst_V, InstEmit.Cmtst_V, OpCodeSimdReg.Create); + SetA64("0x00111000100000010110xxxxxxxxxx", InstName.Cnt_V, InstEmit.Cnt_V, OpCodeSimd.Create); + SetA64("0>001110000x<>>>000011xxxxxxxxxx", InstName.Dup_Gp, InstEmit.Dup_Gp, OpCodeSimdIns.Create); + SetA64("01011110000xxxxx000001xxxxxxxxxx", InstName.Dup_S, InstEmit.Dup_S, OpCodeSimdIns.Create); + SetA64("0>001110000x<>>>000001xxxxxxxxxx", InstName.Dup_V, InstEmit.Dup_V, OpCodeSimdIns.Create); + SetA64("0x101110001xxxxx000111xxxxxxxxxx", InstName.Eor_V, InstEmit.Eor_V, OpCodeSimdReg.Create); + SetA64("0>101110000xxxxx01011101<1xxxxx110101xxxxxxxxxx", InstName.Fabd_V, InstEmit.Fabd_V, OpCodeSimdReg.Create); + SetA64("000111100x100000110000xxxxxxxxxx", InstName.Fabs_S, InstEmit.Fabs_S, OpCodeSimd.Create); + SetA64("0>0011101<100000111110xxxxxxxxxx", InstName.Fabs_V, InstEmit.Fabs_V, OpCodeSimd.Create); + SetA64("011111100x1xxxxx111011xxxxxxxxxx", InstName.Facge_S, InstEmit.Facge_S, OpCodeSimdReg.Create); + SetA64("0>1011100<1xxxxx111011xxxxxxxxxx", InstName.Facge_V, InstEmit.Facge_V, OpCodeSimdReg.Create); + SetA64("011111101x1xxxxx111011xxxxxxxxxx", InstName.Facgt_S, InstEmit.Facgt_S, OpCodeSimdReg.Create); + SetA64("0>1011101<1xxxxx111011xxxxxxxxxx", InstName.Facgt_V, InstEmit.Facgt_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx001010xxxxxxxxxx", InstName.Fadd_S, InstEmit.Fadd_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx110101xxxxxxxxxx", InstName.Fadd_V, InstEmit.Fadd_V, OpCodeSimdReg.Create); + SetA64("011111100x110000110110xxxxxxxxxx", InstName.Faddp_S, InstEmit.Faddp_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx110101xxxxxxxxxx", InstName.Faddp_V, InstEmit.Faddp_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxxxxxx01xxxxx0xxxx", InstName.Fccmp_S, InstEmit.Fccmp_S, OpCodeSimdFcond.Create); + SetA64("000111100x1xxxxxxxxx01xxxxx1xxxx", InstName.Fccmpe_S, InstEmit.Fccmpe_S, OpCodeSimdFcond.Create); + SetA64("010111100x1xxxxx111001xxxxxxxxxx", InstName.Fcmeq_S, InstEmit.Fcmeq_S, OpCodeSimdReg.Create); + SetA64("010111101x100000110110xxxxxxxxxx", InstName.Fcmeq_S, InstEmit.Fcmeq_S, OpCodeSimd.Create); + SetA64("0>0011100<1xxxxx111001xxxxxxxxxx", InstName.Fcmeq_V, InstEmit.Fcmeq_V, OpCodeSimdReg.Create); + SetA64("0>0011101<100000110110xxxxxxxxxx", InstName.Fcmeq_V, InstEmit.Fcmeq_V, OpCodeSimd.Create); + SetA64("011111100x1xxxxx111001xxxxxxxxxx", InstName.Fcmge_S, InstEmit.Fcmge_S, OpCodeSimdReg.Create); + SetA64("011111101x100000110010xxxxxxxxxx", InstName.Fcmge_S, InstEmit.Fcmge_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx111001xxxxxxxxxx", InstName.Fcmge_V, InstEmit.Fcmge_V, OpCodeSimdReg.Create); + SetA64("0>1011101<100000110010xxxxxxxxxx", InstName.Fcmge_V, InstEmit.Fcmge_V, OpCodeSimd.Create); + SetA64("011111101x1xxxxx111001xxxxxxxxxx", InstName.Fcmgt_S, InstEmit.Fcmgt_S, OpCodeSimdReg.Create); + SetA64("010111101x100000110010xxxxxxxxxx", InstName.Fcmgt_S, InstEmit.Fcmgt_S, OpCodeSimd.Create); + SetA64("0>1011101<1xxxxx111001xxxxxxxxxx", InstName.Fcmgt_V, InstEmit.Fcmgt_V, OpCodeSimdReg.Create); + SetA64("0>0011101<100000110010xxxxxxxxxx", InstName.Fcmgt_V, InstEmit.Fcmgt_V, OpCodeSimd.Create); + SetA64("011111101x100000110110xxxxxxxxxx", InstName.Fcmle_S, InstEmit.Fcmle_S, OpCodeSimd.Create); + SetA64("0>1011101<100000110110xxxxxxxxxx", InstName.Fcmle_V, InstEmit.Fcmle_V, OpCodeSimd.Create); + SetA64("010111101x100000111010xxxxxxxxxx", InstName.Fcmlt_S, InstEmit.Fcmlt_S, OpCodeSimd.Create); + SetA64("0>0011101<100000111010xxxxxxxxxx", InstName.Fcmlt_V, InstEmit.Fcmlt_V, OpCodeSimd.Create); + SetA64("000111100x1xxxxx001000xxxxx0x000", InstName.Fcmp_S, InstEmit.Fcmp_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx001000xxxxx1x000", InstName.Fcmpe_S, InstEmit.Fcmpe_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxxxxxx11xxxxxxxxxx", InstName.Fcsel_S, InstEmit.Fcsel_S, OpCodeSimdFcond.Create); + SetA64("00011110xx10001xx10000xxxxxxxxxx", InstName.Fcvt_S, InstEmit.Fcvt_S, OpCodeSimd.Create); + SetA64("x00111100x100100000000xxxxxxxxxx", InstName.Fcvtas_Gp, InstEmit.Fcvtas_Gp, OpCodeSimdCvt.Create); + SetA64("010111100x100001110010xxxxxxxxxx", InstName.Fcvtas_S, InstEmit.Fcvtas_S, OpCodeSimd.Create); + SetA64("0>0011100<100001110010xxxxxxxxxx", InstName.Fcvtas_V, InstEmit.Fcvtas_V, OpCodeSimd.Create); + SetA64("x00111100x100101000000xxxxxxxxxx", InstName.Fcvtau_Gp, InstEmit.Fcvtau_Gp, OpCodeSimdCvt.Create); + SetA64("011111100x100001110010xxxxxxxxxx", InstName.Fcvtau_S, InstEmit.Fcvtau_S, OpCodeSimd.Create); + SetA64("0>1011100<100001110010xxxxxxxxxx", InstName.Fcvtau_V, InstEmit.Fcvtau_V, OpCodeSimd.Create); + SetA64("0x0011100x100001011110xxxxxxxxxx", InstName.Fcvtl_V, InstEmit.Fcvtl_V, OpCodeSimd.Create); + SetA64("x00111100x110000000000xxxxxxxxxx", InstName.Fcvtms_Gp, InstEmit.Fcvtms_Gp, OpCodeSimdCvt.Create); + SetA64("0>0011100<100001101110xxxxxxxxxx", InstName.Fcvtms_V, InstEmit.Fcvtms_V, OpCodeSimd.Create); + SetA64("x00111100x110001000000xxxxxxxxxx", InstName.Fcvtmu_Gp, InstEmit.Fcvtmu_Gp, OpCodeSimdCvt.Create); + SetA64("0x0011100x100001011010xxxxxxxxxx", InstName.Fcvtn_V, InstEmit.Fcvtn_V, OpCodeSimd.Create); + SetA64("x00111100x100000000000xxxxxxxxxx", InstName.Fcvtns_Gp, InstEmit.Fcvtns_Gp, OpCodeSimdCvt.Create); + SetA64("010111100x100001101010xxxxxxxxxx", InstName.Fcvtns_S, InstEmit.Fcvtns_S, OpCodeSimd.Create); + SetA64("0>0011100<100001101010xxxxxxxxxx", InstName.Fcvtns_V, InstEmit.Fcvtns_V, OpCodeSimd.Create); + SetA64("011111100x100001101010xxxxxxxxxx", InstName.Fcvtnu_S, InstEmit.Fcvtnu_S, OpCodeSimd.Create); + SetA64("0>1011100<100001101010xxxxxxxxxx", InstName.Fcvtnu_V, InstEmit.Fcvtnu_V, OpCodeSimd.Create); + SetA64("x00111100x101000000000xxxxxxxxxx", InstName.Fcvtps_Gp, InstEmit.Fcvtps_Gp, OpCodeSimdCvt.Create); + SetA64("x00111100x101001000000xxxxxxxxxx", InstName.Fcvtpu_Gp, InstEmit.Fcvtpu_Gp, OpCodeSimdCvt.Create); + SetA64("x00111100x111000000000xxxxxxxxxx", InstName.Fcvtzs_Gp, InstEmit.Fcvtzs_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x011000>xxxxxxxxxxxxxxx", InstName.Fcvtzs_Gp_Fixed, InstEmit.Fcvtzs_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("010111101x100001101110xxxxxxxxxx", InstName.Fcvtzs_S, InstEmit.Fcvtzs_S, OpCodeSimd.Create); + SetA64("0>0011101<100001101110xxxxxxxxxx", InstName.Fcvtzs_V, InstEmit.Fcvtzs_V, OpCodeSimd.Create); + SetA64("0x001111001xxxxx111111xxxxxxxxxx", InstName.Fcvtzs_V_Fixed, InstEmit.Fcvtzs_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx111111xxxxxxxxxx", InstName.Fcvtzs_V_Fixed, InstEmit.Fcvtzs_V_Fixed, OpCodeSimdShImm.Create); + SetA64("x00111100x111001000000xxxxxxxxxx", InstName.Fcvtzu_Gp, InstEmit.Fcvtzu_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x011001>xxxxxxxxxxxxxxx", InstName.Fcvtzu_Gp_Fixed, InstEmit.Fcvtzu_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("011111101x100001101110xxxxxxxxxx", InstName.Fcvtzu_S, InstEmit.Fcvtzu_S, OpCodeSimd.Create); + SetA64("0>1011101<100001101110xxxxxxxxxx", InstName.Fcvtzu_V, InstEmit.Fcvtzu_V, OpCodeSimd.Create); + SetA64("0x101111001xxxxx111111xxxxxxxxxx", InstName.Fcvtzu_V_Fixed, InstEmit.Fcvtzu_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx111111xxxxxxxxxx", InstName.Fcvtzu_V_Fixed, InstEmit.Fcvtzu_V_Fixed, OpCodeSimdShImm.Create); + SetA64("000111100x1xxxxx000110xxxxxxxxxx", InstName.Fdiv_S, InstEmit.Fdiv_S, OpCodeSimdReg.Create); + SetA64("0>1011100<1xxxxx111111xxxxxxxxxx", InstName.Fdiv_V, InstEmit.Fdiv_V, OpCodeSimdReg.Create); + SetA64("000111110x0xxxxx0xxxxxxxxxxxxxxx", InstName.Fmadd_S, InstEmit.Fmadd_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx010010xxxxxxxxxx", InstName.Fmax_S, InstEmit.Fmax_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx111101xxxxxxxxxx", InstName.Fmax_V, InstEmit.Fmax_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx011010xxxxxxxxxx", InstName.Fmaxnm_S, InstEmit.Fmaxnm_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx110001xxxxxxxxxx", InstName.Fmaxnm_V, InstEmit.Fmaxnm_V, OpCodeSimdReg.Create); + SetA64("011111100x110000110010xxxxxxxxxx", InstName.Fmaxnmp_S, InstEmit.Fmaxnmp_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx110001xxxxxxxxxx", InstName.Fmaxnmp_V, InstEmit.Fmaxnmp_V, OpCodeSimdReg.Create); + SetA64("0110111000110000110010xxxxxxxxxx", InstName.Fmaxnmv_V, InstEmit.Fmaxnmv_V, OpCodeSimd.Create); + SetA64("011111100x110000111110xxxxxxxxxx", InstName.Fmaxp_S, InstEmit.Fmaxp_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx111101xxxxxxxxxx", InstName.Fmaxp_V, InstEmit.Fmaxp_V, OpCodeSimdReg.Create); + SetA64("0110111000110000111110xxxxxxxxxx", InstName.Fmaxv_V, InstEmit.Fmaxv_V, OpCodeSimd.Create); + SetA64("000111100x1xxxxx010110xxxxxxxxxx", InstName.Fmin_S, InstEmit.Fmin_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx111101xxxxxxxxxx", InstName.Fmin_V, InstEmit.Fmin_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx011110xxxxxxxxxx", InstName.Fminnm_S, InstEmit.Fminnm_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx110001xxxxxxxxxx", InstName.Fminnm_V, InstEmit.Fminnm_V, OpCodeSimdReg.Create); + SetA64("011111101x110000110010xxxxxxxxxx", InstName.Fminnmp_S, InstEmit.Fminnmp_S, OpCodeSimd.Create); + SetA64("0>1011101<1xxxxx110001xxxxxxxxxx", InstName.Fminnmp_V, InstEmit.Fminnmp_V, OpCodeSimdReg.Create); + SetA64("0110111010110000110010xxxxxxxxxx", InstName.Fminnmv_V, InstEmit.Fminnmv_V, OpCodeSimd.Create); + SetA64("011111101x110000111110xxxxxxxxxx", InstName.Fminp_S, InstEmit.Fminp_S, OpCodeSimd.Create); + SetA64("0>1011101<1xxxxx111101xxxxxxxxxx", InstName.Fminp_V, InstEmit.Fminp_V, OpCodeSimdReg.Create); + SetA64("0110111010110000111110xxxxxxxxxx", InstName.Fminv_V, InstEmit.Fminv_V, OpCodeSimd.Create); + SetA64("010111111xxxxxxx0001x0xxxxxxxxxx", InstName.Fmla_Se, InstEmit.Fmla_Se, OpCodeSimdRegElemF.Create); + SetA64("0>0011100<1xxxxx110011xxxxxxxxxx", InstName.Fmla_V, InstEmit.Fmla_V, OpCodeSimdReg.Create); + SetA64("0>00111110011101<1xxxxx110011xxxxxxxxxx", InstName.Fmls_V, InstEmit.Fmls_V, OpCodeSimdReg.Create); + SetA64("0>00111111011100<1xxxxx110111xxxxxxxxxx", InstName.Fmul_V, InstEmit.Fmul_V, OpCodeSimdReg.Create); + SetA64("0>00111110011100<1xxxxx110111xxxxxxxxxx", InstName.Fmulx_V, InstEmit.Fmulx_V, OpCodeSimdReg.Create); + SetA64("0>10111111011101<100000111110xxxxxxxxxx", InstName.Fneg_V, InstEmit.Fneg_V, OpCodeSimd.Create); + SetA64("000111110x1xxxxx0xxxxxxxxxxxxxxx", InstName.Fnmadd_S, InstEmit.Fnmadd_S, OpCodeSimdReg.Create); + SetA64("000111110x1xxxxx1xxxxxxxxxxxxxxx", InstName.Fnmsub_S, InstEmit.Fnmsub_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx100010xxxxxxxxxx", InstName.Fnmul_S, InstEmit.Fnmul_S, OpCodeSimdReg.Create); + SetA64("010111101x100001110110xxxxxxxxxx", InstName.Frecpe_S, InstEmit.Frecpe_S, OpCodeSimd.Create); + SetA64("0>0011101<100001110110xxxxxxxxxx", InstName.Frecpe_V, InstEmit.Frecpe_V, OpCodeSimd.Create); + SetA64("010111100x1xxxxx111111xxxxxxxxxx", InstName.Frecps_S, InstEmit.Frecps_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx111111xxxxxxxxxx", InstName.Frecps_V, InstEmit.Frecps_V, OpCodeSimdReg.Create); + SetA64("010111101x100001111110xxxxxxxxxx", InstName.Frecpx_S, InstEmit.Frecpx_S, OpCodeSimd.Create); + SetA64("000111100x100110010000xxxxxxxxxx", InstName.Frinta_S, InstEmit.Frinta_S, OpCodeSimd.Create); + SetA64("0>1011100<100001100010xxxxxxxxxx", InstName.Frinta_V, InstEmit.Frinta_V, OpCodeSimd.Create); + SetA64("000111100x100111110000xxxxxxxxxx", InstName.Frinti_S, InstEmit.Frinti_S, OpCodeSimd.Create); + SetA64("0>1011101<100001100110xxxxxxxxxx", InstName.Frinti_V, InstEmit.Frinti_V, OpCodeSimd.Create); + SetA64("000111100x100101010000xxxxxxxxxx", InstName.Frintm_S, InstEmit.Frintm_S, OpCodeSimd.Create); + SetA64("0>0011100<100001100110xxxxxxxxxx", InstName.Frintm_V, InstEmit.Frintm_V, OpCodeSimd.Create); + SetA64("000111100x100100010000xxxxxxxxxx", InstName.Frintn_S, InstEmit.Frintn_S, OpCodeSimd.Create); + SetA64("0>0011100<100001100010xxxxxxxxxx", InstName.Frintn_V, InstEmit.Frintn_V, OpCodeSimd.Create); + SetA64("000111100x100100110000xxxxxxxxxx", InstName.Frintp_S, InstEmit.Frintp_S, OpCodeSimd.Create); + SetA64("0>0011101<100001100010xxxxxxxxxx", InstName.Frintp_V, InstEmit.Frintp_V, OpCodeSimd.Create); + SetA64("000111100x100111010000xxxxxxxxxx", InstName.Frintx_S, InstEmit.Frintx_S, OpCodeSimd.Create); + SetA64("0>1011100<100001100110xxxxxxxxxx", InstName.Frintx_V, InstEmit.Frintx_V, OpCodeSimd.Create); + SetA64("000111100x100101110000xxxxxxxxxx", InstName.Frintz_S, InstEmit.Frintz_S, OpCodeSimd.Create); + SetA64("0>0011101<100001100110xxxxxxxxxx", InstName.Frintz_V, InstEmit.Frintz_V, OpCodeSimd.Create); + SetA64("011111101x100001110110xxxxxxxxxx", InstName.Frsqrte_S, InstEmit.Frsqrte_S, OpCodeSimd.Create); + SetA64("0>1011101<100001110110xxxxxxxxxx", InstName.Frsqrte_V, InstEmit.Frsqrte_V, OpCodeSimd.Create); + SetA64("010111101x1xxxxx111111xxxxxxxxxx", InstName.Frsqrts_S, InstEmit.Frsqrts_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx111111xxxxxxxxxx", InstName.Frsqrts_V, InstEmit.Frsqrts_V, OpCodeSimdReg.Create); + SetA64("000111100x100001110000xxxxxxxxxx", InstName.Fsqrt_S, InstEmit.Fsqrt_S, OpCodeSimd.Create); + SetA64("0>1011101<100001111110xxxxxxxxxx", InstName.Fsqrt_V, InstEmit.Fsqrt_V, OpCodeSimd.Create); + SetA64("000111100x1xxxxx001110xxxxxxxxxx", InstName.Fsub_S, InstEmit.Fsub_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx110101xxxxxxxxxx", InstName.Fsub_V, InstEmit.Fsub_V, OpCodeSimdReg.Create); + SetA64("01001110000xxxxx000111xxxxxxxxxx", InstName.Ins_Gp, InstEmit.Ins_Gp, OpCodeSimdIns.Create); + SetA64("01101110000xxxxx0xxxx1xxxxxxxxxx", InstName.Ins_V, InstEmit.Ins_V, OpCodeSimdIns.Create); + SetA64("0x00110001000000xxxxxxxxxxxxxxxx", InstName.Ld__Vms, InstEmit.Ld__Vms, OpCodeSimdMemMs.Create); + SetA64("0x001100110xxxxxxxxxxxxxxxxxxxxx", InstName.Ld__Vms, InstEmit.Ld__Vms, OpCodeSimdMemMs.Create); + SetA64("0x00110101x00000xxxxxxxxxxxxxxxx", InstName.Ld__Vss, InstEmit.Ld__Vss, OpCodeSimdMemSs.Create); + SetA64("0x00110111xxxxxxxxxxxxxxxxxxxxxx", InstName.Ld__Vss, InstEmit.Ld__Vss, OpCodeSimdMemSs.Create); + SetA64("<<10110xx1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldp, InstEmit.Ldp, OpCodeSimdMemPair.Create); + SetA64("xx111100x10xxxxxxxxx00xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111100x10xxxxxxxxx01xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111100x10xxxxxxxxx11xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111101x1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111100x11xxxxxx1xx10xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemReg.Create); + SetA64("xx011100xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr_Literal, InstEmit.Ldr_Literal, OpCodeSimdMemLit.Create); + SetA64("0x001110<<1xxxxx100101xxxxxxxxxx", InstName.Mla_V, InstEmit.Mla_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0000x0xxxxxxxxxx", InstName.Mla_Ve, InstEmit.Mla_Ve, OpCodeSimdRegElem.Create); + SetA64("0x101110<<1xxxxx100101xxxxxxxxxx", InstName.Mls_V, InstEmit.Mls_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0100x0xxxxxxxxxx", InstName.Mls_Ve, InstEmit.Mls_Ve, OpCodeSimdRegElem.Create); + SetA64("0x00111100000xxx0xx001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0x00111100000xxx10x001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0x00111100000xxx110x01xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0xx0111100000xxx111001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0x001110<<1xxxxx100111xxxxxxxxxx", InstName.Mul_V, InstEmit.Mul_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx1000x0xxxxxxxxxx", InstName.Mul_Ve, InstEmit.Mul_Ve, OpCodeSimdRegElem.Create); + SetA64("0x10111100000xxx0xx001xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, OpCodeSimdImm.Create); + SetA64("0x10111100000xxx10x001xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, OpCodeSimdImm.Create); + SetA64("0x10111100000xxx110x01xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, OpCodeSimdImm.Create); + SetA64("0111111011100000101110xxxxxxxxxx", InstName.Neg_S, InstEmit.Neg_S, OpCodeSimd.Create); + SetA64("0>101110<<100000101110xxxxxxxxxx", InstName.Neg_V, InstEmit.Neg_V, OpCodeSimd.Create); + SetA64("0x10111000100000010110xxxxxxxxxx", InstName.Not_V, InstEmit.Not_V, OpCodeSimd.Create); + SetA64("0x001110111xxxxx000111xxxxxxxxxx", InstName.Orn_V, InstEmit.Orn_V, OpCodeSimdReg.Create); + SetA64("0x001110101xxxxx000111xxxxxxxxxx", InstName.Orr_V, InstEmit.Orr_V, OpCodeSimdReg.Create); + SetA64("0x00111100000xxx0xx101xxxxxxxxxx", InstName.Orr_Vi, InstEmit.Orr_Vi, OpCodeSimdImm.Create); + SetA64("0x00111100000xxx10x101xxxxxxxxxx", InstName.Orr_Vi, InstEmit.Orr_Vi, OpCodeSimdImm.Create); + SetA64("0x001110001xxxxx111000xxxxxxxxxx", InstName.Pmull_V, InstEmit.Pmull_V, OpCodeSimdReg.Create); + SetA64("0x001110111xxxxx111000xxxxxxxxxx", InstName.Pmull_V, InstEmit.Pmull_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx010000xxxxxxxxxx", InstName.Raddhn_V, InstEmit.Raddhn_V, OpCodeSimdReg.Create); + SetA64("0x10111001100000010110xxxxxxxxxx", InstName.Rbit_V, InstEmit.Rbit_V, OpCodeSimd.Create); + SetA64("0x00111000100000000110xxxxxxxxxx", InstName.Rev16_V, InstEmit.Rev16_V, OpCodeSimd.Create); + SetA64("0x1011100x100000000010xxxxxxxxxx", InstName.Rev32_V, InstEmit.Rev32_V, OpCodeSimd.Create); + SetA64("0x001110<<100000000010xxxxxxxxxx", InstName.Rev64_V, InstEmit.Rev64_V, OpCodeSimd.Create); + SetA64("0x00111100>>>xxx100011xxxxxxxxxx", InstName.Rshrn_V, InstEmit.Rshrn_V, OpCodeSimdShImm.Create); + SetA64("0x101110<<1xxxxx011000xxxxxxxxxx", InstName.Rsubhn_V, InstEmit.Rsubhn_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011111xxxxxxxxxx", InstName.Saba_V, InstEmit.Saba_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx010100xxxxxxxxxx", InstName.Sabal_V, InstEmit.Sabal_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011101xxxxxxxxxx", InstName.Sabd_V, InstEmit.Sabd_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011100xxxxxxxxxx", InstName.Sabdl_V, InstEmit.Sabdl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100000011010xxxxxxxxxx", InstName.Sadalp_V, InstEmit.Sadalp_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx000000xxxxxxxxxx", InstName.Saddl_V, InstEmit.Saddl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100000001010xxxxxxxxxx", InstName.Saddlp_V, InstEmit.Saddlp_V, OpCodeSimd.Create); + SetA64("000011100x110000001110xxxxxxxxxx", InstName.Saddlv_V, InstEmit.Saddlv_V, OpCodeSimd.Create); + SetA64("01001110<<110000001110xxxxxxxxxx", InstName.Saddlv_V, InstEmit.Saddlv_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx000100xxxxxxxxxx", InstName.Saddw_V, InstEmit.Saddw_V, OpCodeSimdReg.Create); + SetA64("x00111100x100010000000xxxxxxxxxx", InstName.Scvtf_Gp, InstEmit.Scvtf_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x000010>xxxxxxxxxxxxxxx", InstName.Scvtf_Gp_Fixed, InstEmit.Scvtf_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("010111100x100001110110xxxxxxxxxx", InstName.Scvtf_S, InstEmit.Scvtf_S, OpCodeSimd.Create); + SetA64("010111110>>xxxxx111001xxxxxxxxxx", InstName.Scvtf_S_Fixed, InstEmit.Scvtf_S_Fixed, OpCodeSimdShImm.Create); + SetA64("0>0011100<100001110110xxxxxxxxxx", InstName.Scvtf_V, InstEmit.Scvtf_V, OpCodeSimd.Create); + SetA64("0x001111001xxxxx111001xxxxxxxxxx", InstName.Scvtf_V_Fixed, InstEmit.Scvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx111001xxxxxxxxxx", InstName.Scvtf_V_Fixed, InstEmit.Scvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("01011110000xxxxx000000xxxxxxxxxx", InstName.Sha1c_V, InstEmit.Sha1c_V, OpCodeSimdReg.Create); + SetA64("0101111000101000000010xxxxxxxxxx", InstName.Sha1h_V, InstEmit.Sha1h_V, OpCodeSimd.Create); + SetA64("01011110000xxxxx001000xxxxxxxxxx", InstName.Sha1m_V, InstEmit.Sha1m_V, OpCodeSimdReg.Create); + SetA64("01011110000xxxxx000100xxxxxxxxxx", InstName.Sha1p_V, InstEmit.Sha1p_V, OpCodeSimdReg.Create); + SetA64("01011110000xxxxx001100xxxxxxxxxx", InstName.Sha1su0_V, InstEmit.Sha1su0_V, OpCodeSimdReg.Create); + SetA64("0101111000101000000110xxxxxxxxxx", InstName.Sha1su1_V, InstEmit.Sha1su1_V, OpCodeSimd.Create); + SetA64("01011110000xxxxx010000xxxxxxxxxx", InstName.Sha256h_V, InstEmit.Sha256h_V, OpCodeSimdReg.Create); + SetA64("01011110000xxxxx010100xxxxxxxxxx", InstName.Sha256h2_V, InstEmit.Sha256h2_V, OpCodeSimdReg.Create); + SetA64("0101111000101000001010xxxxxxxxxx", InstName.Sha256su0_V, InstEmit.Sha256su0_V, OpCodeSimd.Create); + SetA64("01011110000xxxxx011000xxxxxxxxxx", InstName.Sha256su1_V, InstEmit.Sha256su1_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx000001xxxxxxxxxx", InstName.Shadd_V, InstEmit.Shadd_V, OpCodeSimdReg.Create); + SetA64("0101111101xxxxxx010101xxxxxxxxxx", InstName.Shl_S, InstEmit.Shl_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx010101xxxxxxxxxx", InstName.Shl_V, InstEmit.Shl_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx010101xxxxxxxxxx", InstName.Shl_V, InstEmit.Shl_V, OpCodeSimdShImm.Create); + SetA64("0x101110<<100001001110xxxxxxxxxx", InstName.Shll_V, InstEmit.Shll_V, OpCodeSimd.Create); + SetA64("0x00111100>>>xxx100001xxxxxxxxxx", InstName.Shrn_V, InstEmit.Shrn_V, OpCodeSimdShImm.Create); + SetA64("0x001110<<1xxxxx001001xxxxxxxxxx", InstName.Shsub_V, InstEmit.Shsub_V, OpCodeSimdReg.Create); + SetA64("0111111101xxxxxx010101xxxxxxxxxx", InstName.Sli_S, InstEmit.Sli_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx010101xxxxxxxxxx", InstName.Sli_V, InstEmit.Sli_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx010101xxxxxxxxxx", InstName.Sli_V, InstEmit.Sli_V, OpCodeSimdShImm.Create); + SetA64("0x001110<<1xxxxx011001xxxxxxxxxx", InstName.Smax_V, InstEmit.Smax_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx101001xxxxxxxxxx", InstName.Smaxp_V, InstEmit.Smaxp_V, OpCodeSimdReg.Create); + SetA64("000011100x110000101010xxxxxxxxxx", InstName.Smaxv_V, InstEmit.Smaxv_V, OpCodeSimd.Create); + SetA64("01001110<<110000101010xxxxxxxxxx", InstName.Smaxv_V, InstEmit.Smaxv_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx011011xxxxxxxxxx", InstName.Smin_V, InstEmit.Smin_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx101011xxxxxxxxxx", InstName.Sminp_V, InstEmit.Sminp_V, OpCodeSimdReg.Create); + SetA64("000011100x110001101010xxxxxxxxxx", InstName.Sminv_V, InstEmit.Sminv_V, OpCodeSimd.Create); + SetA64("01001110<<110001101010xxxxxxxxxx", InstName.Sminv_V, InstEmit.Sminv_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx100000xxxxxxxxxx", InstName.Smlal_V, InstEmit.Smlal_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx0010x0xxxxxxxxxx", InstName.Smlal_Ve, InstEmit.Smlal_Ve, OpCodeSimdRegElem.Create); + SetA64("0x001110<<1xxxxx101000xxxxxxxxxx", InstName.Smlsl_V, InstEmit.Smlsl_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx0110x0xxxxxxxxxx", InstName.Smlsl_Ve, InstEmit.Smlsl_Ve, OpCodeSimdRegElem.Create); + SetA64("0x001110000xxxxx001011xxxxxxxxxx", InstName.Smov_S, InstEmit.Smov_S, OpCodeSimdIns.Create); + SetA64("0x001110<<1xxxxx110000xxxxxxxxxx", InstName.Smull_V, InstEmit.Smull_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx1010x0xxxxxxxxxx", InstName.Smull_Ve, InstEmit.Smull_Ve, OpCodeSimdRegElem.Create); + SetA64("01011110xx100000011110xxxxxxxxxx", InstName.Sqabs_S, InstEmit.Sqabs_S, OpCodeSimd.Create); + SetA64("0>001110<<100000011110xxxxxxxxxx", InstName.Sqabs_V, InstEmit.Sqabs_V, OpCodeSimd.Create); + SetA64("01011110xx1xxxxx000011xxxxxxxxxx", InstName.Sqadd_S, InstEmit.Sqadd_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx000011xxxxxxxxxx", InstName.Sqadd_V, InstEmit.Sqadd_V, OpCodeSimdReg.Create); + SetA64("01011110011xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_S, InstEmit.Sqdmulh_S, OpCodeSimdReg.Create); + SetA64("01011110101xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_S, InstEmit.Sqdmulh_S, OpCodeSimdReg.Create); + SetA64("0x001110011xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_V, InstEmit.Sqdmulh_V, OpCodeSimdReg.Create); + SetA64("0x001110101xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_V, InstEmit.Sqdmulh_V, OpCodeSimdReg.Create); + SetA64("0x00111101xxxxxx1100x0xxxxxxxxxx", InstName.Sqdmulh_Ve, InstEmit.Sqdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("0x00111110xxxxxx1100x0xxxxxxxxxx", InstName.Sqdmulh_Ve, InstEmit.Sqdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("01111110xx100000011110xxxxxxxxxx", InstName.Sqneg_S, InstEmit.Sqneg_S, OpCodeSimd.Create); + SetA64("0>101110<<100000011110xxxxxxxxxx", InstName.Sqneg_V, InstEmit.Sqneg_V, OpCodeSimd.Create); + SetA64("01111110011xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_S, InstEmit.Sqrdmulh_S, OpCodeSimdReg.Create); + SetA64("01111110101xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_S, InstEmit.Sqrdmulh_S, OpCodeSimdReg.Create); + SetA64("0x101110011xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_V, InstEmit.Sqrdmulh_V, OpCodeSimdReg.Create); + SetA64("0x101110101xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_V, InstEmit.Sqrdmulh_V, OpCodeSimdReg.Create); + SetA64("0x00111101xxxxxx1101x0xxxxxxxxxx", InstName.Sqrdmulh_Ve, InstEmit.Sqrdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("0x00111110xxxxxx1101x0xxxxxxxxxx", InstName.Sqrdmulh_Ve, InstEmit.Sqrdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("0>001110<<1xxxxx010111xxxxxxxxxx", InstName.Sqrshl_V, InstEmit.Sqrshl_V, OpCodeSimdReg.Create); + SetA64("0101111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_S, InstEmit.Sqrshrn_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create); + SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, OpCodeSimdShImm.Create); + SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, OpCodeSimdReg.Create); + SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create); + SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_V, InstEmit.Sqshrun_V, OpCodeSimdShImm.Create); + SetA64("01011110xx1xxxxx001011xxxxxxxxxx", InstName.Sqsub_S, InstEmit.Sqsub_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx001011xxxxxxxxxx", InstName.Sqsub_V, InstEmit.Sqsub_V, OpCodeSimdReg.Create); + SetA64("01011110<<100001010010xxxxxxxxxx", InstName.Sqxtn_S, InstEmit.Sqxtn_S, OpCodeSimd.Create); + SetA64("0x001110<<100001010010xxxxxxxxxx", InstName.Sqxtn_V, InstEmit.Sqxtn_V, OpCodeSimd.Create); + SetA64("01111110<<100001001010xxxxxxxxxx", InstName.Sqxtun_S, InstEmit.Sqxtun_S, OpCodeSimd.Create); + SetA64("0x101110<<100001001010xxxxxxxxxx", InstName.Sqxtun_V, InstEmit.Sqxtun_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx000101xxxxxxxxxx", InstName.Srhadd_V, InstEmit.Srhadd_V, OpCodeSimdReg.Create); + SetA64("0111111101xxxxxx010001xxxxxxxxxx", InstName.Sri_S, InstEmit.Sri_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx010001xxxxxxxxxx", InstName.Sri_V, InstEmit.Sri_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx010001xxxxxxxxxx", InstName.Sri_V, InstEmit.Sri_V, OpCodeSimdShImm.Create); + SetA64("0>001110<<1xxxxx010101xxxxxxxxxx", InstName.Srshl_V, InstEmit.Srshl_V, OpCodeSimdReg.Create); + SetA64("0101111101xxxxxx001001xxxxxxxxxx", InstName.Srshr_S, InstEmit.Srshr_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx001001xxxxxxxxxx", InstName.Srshr_V, InstEmit.Srshr_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx001001xxxxxxxxxx", InstName.Srshr_V, InstEmit.Srshr_V, OpCodeSimdShImm.Create); + SetA64("0101111101xxxxxx001101xxxxxxxxxx", InstName.Srsra_S, InstEmit.Srsra_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx001101xxxxxxxxxx", InstName.Srsra_V, InstEmit.Srsra_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx001101xxxxxxxxxx", InstName.Srsra_V, InstEmit.Srsra_V, OpCodeSimdShImm.Create); + SetA64("01011110111xxxxx010001xxxxxxxxxx", InstName.Sshl_S, InstEmit.Sshl_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx010001xxxxxxxxxx", InstName.Sshl_V, InstEmit.Sshl_V, OpCodeSimdReg.Create); + SetA64("0x00111100>>>xxx101001xxxxxxxxxx", InstName.Sshll_V, InstEmit.Sshll_V, OpCodeSimdShImm.Create); + SetA64("0101111101xxxxxx000001xxxxxxxxxx", InstName.Sshr_S, InstEmit.Sshr_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx000001xxxxxxxxxx", InstName.Sshr_V, InstEmit.Sshr_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx000001xxxxxxxxxx", InstName.Sshr_V, InstEmit.Sshr_V, OpCodeSimdShImm.Create); + SetA64("0101111101xxxxxx000101xxxxxxxxxx", InstName.Ssra_S, InstEmit.Ssra_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx000101xxxxxxxxxx", InstName.Ssra_V, InstEmit.Ssra_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx000101xxxxxxxxxx", InstName.Ssra_V, InstEmit.Ssra_V, OpCodeSimdShImm.Create); + SetA64("0x001110<<1xxxxx001000xxxxxxxxxx", InstName.Ssubl_V, InstEmit.Ssubl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx001100xxxxxxxxxx", InstName.Ssubw_V, InstEmit.Ssubw_V, OpCodeSimdReg.Create); + SetA64("0x00110000000000xxxxxxxxxxxxxxxx", InstName.St__Vms, InstEmit.St__Vms, OpCodeSimdMemMs.Create); + SetA64("0x001100100xxxxxxxxxxxxxxxxxxxxx", InstName.St__Vms, InstEmit.St__Vms, OpCodeSimdMemMs.Create); + SetA64("0x00110100x00000xxxxxxxxxxxxxxxx", InstName.St__Vss, InstEmit.St__Vss, OpCodeSimdMemSs.Create); + SetA64("0x00110110xxxxxxxxxxxxxxxxxxxxxx", InstName.St__Vss, InstEmit.St__Vss, OpCodeSimdMemSs.Create); + SetA64("<<10110xx0xxxxxxxxxxxxxxxxxxxxxx", InstName.Stp, InstEmit.Stp, OpCodeSimdMemPair.Create); + SetA64("xx111100x00xxxxxxxxx00xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111100x00xxxxxxxxx01xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111100x00xxxxxxxxx11xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111101x0xxxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111100x01xxxxxx1xx10xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemReg.Create); + SetA64("01111110111xxxxx100001xxxxxxxxxx", InstName.Sub_S, InstEmit.Sub_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx100001xxxxxxxxxx", InstName.Sub_V, InstEmit.Sub_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011000xxxxxxxxxx", InstName.Subhn_V, InstEmit.Subhn_V, OpCodeSimdReg.Create); + SetA64("01011110xx100000001110xxxxxxxxxx", InstName.Suqadd_S, InstEmit.Suqadd_S, OpCodeSimd.Create); + SetA64("0>001110<<100000001110xxxxxxxxxx", InstName.Suqadd_V, InstEmit.Suqadd_V, OpCodeSimd.Create); + SetA64("0x001110000xxxxx0xx000xxxxxxxxxx", InstName.Tbl_V, InstEmit.Tbl_V, OpCodeSimdTbl.Create); + SetA64("0x001110000xxxxx0xx100xxxxxxxxxx", InstName.Tbx_V, InstEmit.Tbx_V, OpCodeSimdTbl.Create); + SetA64("0>001110<<0xxxxx001010xxxxxxxxxx", InstName.Trn1_V, InstEmit.Trn1_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx011010xxxxxxxxxx", InstName.Trn2_V, InstEmit.Trn2_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011111xxxxxxxxxx", InstName.Uaba_V, InstEmit.Uaba_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx010100xxxxxxxxxx", InstName.Uabal_V, InstEmit.Uabal_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011101xxxxxxxxxx", InstName.Uabd_V, InstEmit.Uabd_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011100xxxxxxxxxx", InstName.Uabdl_V, InstEmit.Uabdl_V, OpCodeSimdReg.Create); + SetA64("0x101110<<100000011010xxxxxxxxxx", InstName.Uadalp_V, InstEmit.Uadalp_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx000000xxxxxxxxxx", InstName.Uaddl_V, InstEmit.Uaddl_V, OpCodeSimdReg.Create); + SetA64("0x101110<<100000001010xxxxxxxxxx", InstName.Uaddlp_V, InstEmit.Uaddlp_V, OpCodeSimd.Create); + SetA64("001011100x110000001110xxxxxxxxxx", InstName.Uaddlv_V, InstEmit.Uaddlv_V, OpCodeSimd.Create); + SetA64("01101110<<110000001110xxxxxxxxxx", InstName.Uaddlv_V, InstEmit.Uaddlv_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx000100xxxxxxxxxx", InstName.Uaddw_V, InstEmit.Uaddw_V, OpCodeSimdReg.Create); + SetA64("x00111100x100011000000xxxxxxxxxx", InstName.Ucvtf_Gp, InstEmit.Ucvtf_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x000011>xxxxxxxxxxxxxxx", InstName.Ucvtf_Gp_Fixed, InstEmit.Ucvtf_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("011111100x100001110110xxxxxxxxxx", InstName.Ucvtf_S, InstEmit.Ucvtf_S, OpCodeSimd.Create); + SetA64("011111110>>xxxxx111001xxxxxxxxxx", InstName.Ucvtf_S_Fixed, InstEmit.Ucvtf_S_Fixed, OpCodeSimdShImm.Create); + SetA64("0>1011100<100001110110xxxxxxxxxx", InstName.Ucvtf_V, InstEmit.Ucvtf_V, OpCodeSimd.Create); + SetA64("0x101111001xxxxx111001xxxxxxxxxx", InstName.Ucvtf_V_Fixed, InstEmit.Ucvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx111001xxxxxxxxxx", InstName.Ucvtf_V_Fixed, InstEmit.Ucvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0x101110<<1xxxxx000001xxxxxxxxxx", InstName.Uhadd_V, InstEmit.Uhadd_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx001001xxxxxxxxxx", InstName.Uhsub_V, InstEmit.Uhsub_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011001xxxxxxxxxx", InstName.Umax_V, InstEmit.Umax_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx101001xxxxxxxxxx", InstName.Umaxp_V, InstEmit.Umaxp_V, OpCodeSimdReg.Create); + SetA64("001011100x110000101010xxxxxxxxxx", InstName.Umaxv_V, InstEmit.Umaxv_V, OpCodeSimd.Create); + SetA64("01101110<<110000101010xxxxxxxxxx", InstName.Umaxv_V, InstEmit.Umaxv_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx011011xxxxxxxxxx", InstName.Umin_V, InstEmit.Umin_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx101011xxxxxxxxxx", InstName.Uminp_V, InstEmit.Uminp_V, OpCodeSimdReg.Create); + SetA64("001011100x110001101010xxxxxxxxxx", InstName.Uminv_V, InstEmit.Uminv_V, OpCodeSimd.Create); + SetA64("01101110<<110001101010xxxxxxxxxx", InstName.Uminv_V, InstEmit.Uminv_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx100000xxxxxxxxxx", InstName.Umlal_V, InstEmit.Umlal_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0010x0xxxxxxxxxx", InstName.Umlal_Ve, InstEmit.Umlal_Ve, OpCodeSimdRegElem.Create); + SetA64("0x101110<<1xxxxx101000xxxxxxxxxx", InstName.Umlsl_V, InstEmit.Umlsl_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0110x0xxxxxxxxxx", InstName.Umlsl_Ve, InstEmit.Umlsl_Ve, OpCodeSimdRegElem.Create); + SetA64("0x001110000xxxxx001111xxxxxxxxxx", InstName.Umov_S, InstEmit.Umov_S, OpCodeSimdIns.Create); + SetA64("0x101110<<1xxxxx110000xxxxxxxxxx", InstName.Umull_V, InstEmit.Umull_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx1010x0xxxxxxxxxx", InstName.Umull_Ve, InstEmit.Umull_Ve, OpCodeSimdRegElem.Create); + SetA64("01111110xx1xxxxx000011xxxxxxxxxx", InstName.Uqadd_S, InstEmit.Uqadd_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx000011xxxxxxxxxx", InstName.Uqadd_V, InstEmit.Uqadd_V, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx010111xxxxxxxxxx", InstName.Uqrshl_V, InstEmit.Uqrshl_V, OpCodeSimdReg.Create); + SetA64("0111111100>>>xxx100111xxxxxxxxxx", InstName.Uqrshrn_S, InstEmit.Uqrshrn_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100111xxxxxxxxxx", InstName.Uqrshrn_V, InstEmit.Uqrshrn_V, OpCodeSimdShImm.Create); + SetA64("0>101110<<1xxxxx010011xxxxxxxxxx", InstName.Uqshl_V, InstEmit.Uqshl_V, OpCodeSimdReg.Create); + SetA64("0111111100>>>xxx100101xxxxxxxxxx", InstName.Uqshrn_S, InstEmit.Uqshrn_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100101xxxxxxxxxx", InstName.Uqshrn_V, InstEmit.Uqshrn_V, OpCodeSimdShImm.Create); + SetA64("01111110xx1xxxxx001011xxxxxxxxxx", InstName.Uqsub_S, InstEmit.Uqsub_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx001011xxxxxxxxxx", InstName.Uqsub_V, InstEmit.Uqsub_V, OpCodeSimdReg.Create); + SetA64("01111110<<100001010010xxxxxxxxxx", InstName.Uqxtn_S, InstEmit.Uqxtn_S, OpCodeSimd.Create); + SetA64("0x101110<<100001010010xxxxxxxxxx", InstName.Uqxtn_V, InstEmit.Uqxtn_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx000101xxxxxxxxxx", InstName.Urhadd_V, InstEmit.Urhadd_V, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx010101xxxxxxxxxx", InstName.Urshl_V, InstEmit.Urshl_V, OpCodeSimdReg.Create); + SetA64("0111111101xxxxxx001001xxxxxxxxxx", InstName.Urshr_S, InstEmit.Urshr_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx001001xxxxxxxxxx", InstName.Urshr_V, InstEmit.Urshr_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx001001xxxxxxxxxx", InstName.Urshr_V, InstEmit.Urshr_V, OpCodeSimdShImm.Create); + SetA64("0111111101xxxxxx001101xxxxxxxxxx", InstName.Ursra_S, InstEmit.Ursra_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx001101xxxxxxxxxx", InstName.Ursra_V, InstEmit.Ursra_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx001101xxxxxxxxxx", InstName.Ursra_V, InstEmit.Ursra_V, OpCodeSimdShImm.Create); + SetA64("01111110111xxxxx010001xxxxxxxxxx", InstName.Ushl_S, InstEmit.Ushl_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx010001xxxxxxxxxx", InstName.Ushl_V, InstEmit.Ushl_V, OpCodeSimdReg.Create); + SetA64("0x10111100>>>xxx101001xxxxxxxxxx", InstName.Ushll_V, InstEmit.Ushll_V, OpCodeSimdShImm.Create); + SetA64("0111111101xxxxxx000001xxxxxxxxxx", InstName.Ushr_S, InstEmit.Ushr_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx000001xxxxxxxxxx", InstName.Ushr_V, InstEmit.Ushr_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx000001xxxxxxxxxx", InstName.Ushr_V, InstEmit.Ushr_V, OpCodeSimdShImm.Create); + SetA64("01111110xx100000001110xxxxxxxxxx", InstName.Usqadd_S, InstEmit.Usqadd_S, OpCodeSimd.Create); + SetA64("0>101110<<100000001110xxxxxxxxxx", InstName.Usqadd_V, InstEmit.Usqadd_V, OpCodeSimd.Create); + SetA64("0111111101xxxxxx000101xxxxxxxxxx", InstName.Usra_S, InstEmit.Usra_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx000101xxxxxxxxxx", InstName.Usra_V, InstEmit.Usra_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx000101xxxxxxxxxx", InstName.Usra_V, InstEmit.Usra_V, OpCodeSimdShImm.Create); + SetA64("0x101110<<1xxxxx001000xxxxxxxxxx", InstName.Usubl_V, InstEmit.Usubl_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx001100xxxxxxxxxx", InstName.Usubw_V, InstEmit.Usubw_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx000110xxxxxxxxxx", InstName.Uzp1_V, InstEmit.Uzp1_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx010110xxxxxxxxxx", InstName.Uzp2_V, InstEmit.Uzp2_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100001001010xxxxxxxxxx", InstName.Xtn_V, InstEmit.Xtn_V, OpCodeSimd.Create); + SetA64("0>001110<<0xxxxx001110xxxxxxxxxx", InstName.Zip1_V, InstEmit.Zip1_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx011110xxxxxxxxxx", InstName.Zip2_V, InstEmit.Zip2_V, OpCodeSimdReg.Create); + #endregion + + #region "OpCode Table (AArch32, A32)" + // Base + SetA32("<<<<0010101xxxxxxxxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluImm.Create); + SetA32("<<<<0000101xxxxxxxxxxxxxxxx0xxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluRsImm.Create); + SetA32("<<<<0000101xxxxxxxxxxxxx0xx1xxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluRsReg.Create); + SetA32("<<<<0010100xxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit32.Add, OpCode32AluImm.Create); + SetA32("<<<<0000100xxxxxxxxxxxxxxxx0xxxx", InstName.Add, InstEmit32.Add, OpCode32AluRsImm.Create); + SetA32("<<<<0000100xxxxxxxxxxxxx0xx1xxxx", InstName.Add, InstEmit32.Add, OpCode32AluRsReg.Create); + SetA32("<<<<0010000xxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit32.And, OpCode32AluImm.Create); + SetA32("<<<<0000000xxxxxxxxxxxxxxxx0xxxx", InstName.And, InstEmit32.And, OpCode32AluRsImm.Create); + SetA32("<<<<0000000xxxxxxxxxxxxx0xx1xxxx", InstName.And, InstEmit32.And, OpCode32AluRsReg.Create); + SetA32("<<<<1010xxxxxxxxxxxxxxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCode32BImm.Create); + SetA32("<<<<0111110xxxxxxxxxxxxxx0011111", InstName.Bfc, InstEmit32.Bfc, OpCode32AluBf.Create); + SetA32("<<<<0111110xxxxxxxxxxxxxx001xxxx", InstName.Bfi, InstEmit32.Bfi, OpCode32AluBf.Create); + SetA32("<<<<0011110xxxxxxxxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCode32AluImm.Create); + SetA32("<<<<0001110xxxxxxxxxxxxxxxx0xxxx", InstName.Bic, InstEmit32.Bic, OpCode32AluRsImm.Create); + SetA32("<<<<0001110xxxxxxxxxxxxx0xx1xxxx", InstName.Bic, InstEmit32.Bic, OpCode32AluRsReg.Create); + SetA32("<<<<1011xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, OpCode32BImm.Create); + SetA32("1111101xxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Blx, InstEmit32.Blx, OpCode32BImm.Create); + SetA32("<<<<000100101111111111110011xxxx", InstName.Blx, InstEmit32.Blxr, OpCode32BReg.Create); + SetA32("<<<<000100101111111111110001xxxx", InstName.Bx, InstEmit32.Bx, OpCode32BReg.Create); + SetA32("11110101011111111111000000011111", InstName.Clrex, InstEmit32.Clrex, OpCode32.Create); + SetA32("<<<<000101101111xxxx11110001xxxx", InstName.Clz, InstEmit32.Clz, OpCode32AluReg.Create); + SetA32("<<<<00110111xxxx0000xxxxxxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluImm.Create); + SetA32("<<<<00010111xxxx0000xxxxxxx0xxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluRsImm.Create); + SetA32("<<<<00010111xxxx0000xxxx0xx1xxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluRsReg.Create); + SetA32("<<<<00110101xxxx0000xxxxxxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCode32AluImm.Create); + SetA32("<<<<00010101xxxx0000xxxxxxx0xxxx", InstName.Cmp, InstEmit32.Cmp, OpCode32AluRsImm.Create); + SetA32("<<<<00010101xxxx0000xxxx0xx1xxxx", InstName.Cmp, InstEmit32.Cmp, OpCode32AluRsReg.Create); + SetA32("<<<<00010000xxxxxxxx00000100xxxx", InstName.Crc32b, InstEmit32.Crc32b, OpCode32AluReg.Create); + SetA32("<<<<00010000xxxxxxxx00100100xxxx", InstName.Crc32cb, InstEmit32.Crc32cb, OpCode32AluReg.Create); + SetA32("<<<<00010010xxxxxxxx00100100xxxx", InstName.Crc32ch, InstEmit32.Crc32ch, OpCode32AluReg.Create); + SetA32("<<<<00010100xxxxxxxx00100100xxxx", InstName.Crc32cw, InstEmit32.Crc32cw, OpCode32AluReg.Create); + SetA32("<<<<00010010xxxxxxxx00000100xxxx", InstName.Crc32h, InstEmit32.Crc32h, OpCode32AluReg.Create); + SetA32("<<<<00010100xxxxxxxx00000100xxxx", InstName.Crc32w, InstEmit32.Crc32w, OpCode32AluReg.Create); + SetA32("<<<<0011001000001111000000010100", InstName.Csdb, InstEmit32.Csdb, OpCode32.Create); + SetA32("1111010101111111111100000101xxxx", InstName.Dmb, InstEmit32.Dmb, OpCode32.Create); + SetA32("1111010101111111111100000100xxxx", InstName.Dsb, InstEmit32.Dsb, OpCode32.Create); + SetA32("<<<<0010001xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluImm.Create); + SetA32("<<<<0000001xxxxxxxxxxxxxxxx0xxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluRsImm.Create); + SetA32("<<<<0000001xxxxxxxxxxxxx0xx1xxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluRsReg.Create); + SetA32("<<<<0011001000001111000000010000", InstName.Esb, InstEmit32.Nop, OpCode32.Create); // Error Synchronization Barrier (FEAT_RAS) + SetA32("<<<<001100100000111100000000011x", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000001xxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000010001", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000010011", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000010101", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<001100100000111100000001011x", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000011xxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<00110010000011110000001xxxxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000001xxxxxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<001100100000111100001xxxxxxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("1111010101111111111100000110xxxx", InstName.Isb, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<00011001xxxxxxxx110010011111", InstName.Lda, InstEmit32.Lda, OpCode32MemLdEx.Create); + SetA32("<<<<00011101xxxxxxxx110010011111", InstName.Ldab, InstEmit32.Ldab, OpCode32MemLdEx.Create); + SetA32("<<<<00011001xxxxxxxx111010011111", InstName.Ldaex, InstEmit32.Ldaex, OpCode32MemLdEx.Create); + SetA32("<<<<00011101xxxxxxxx111010011111", InstName.Ldaexb, InstEmit32.Ldaexb, OpCode32MemLdEx.Create); + SetA32("<<<<00011011xxxxxxxx111010011111", InstName.Ldaexd, InstEmit32.Ldaexd, OpCode32MemLdEx.Create); + SetA32("<<<<00011111xxxxxxxx111010011111", InstName.Ldaexh, InstEmit32.Ldaexh, OpCode32MemLdEx.Create); + SetA32("<<<<00011111xxxxxxxx110010011111", InstName.Ldah, InstEmit32.Ldah, OpCode32MemLdEx.Create); + SetA32("<<<<100xx0x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, OpCode32MemMult.Create); + SetA32("<<<<010xx0x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCode32MemImm.Create); + SetA32("<<<<011xx0x1xxxxxxxxxxxxxxx0xxxx", InstName.Ldr, InstEmit32.Ldr, OpCode32MemRsImm.Create); + SetA32("<<<<010xx1x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCode32MemImm.Create); + SetA32("<<<<011xx1x1xxxxxxxxxxxxxxx0xxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCode32MemRsImm.Create); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1101xxxx", InstName.Ldrd, InstEmit32.Ldrd, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x0xxxxxxxx00001101xxxx", InstName.Ldrd, InstEmit32.Ldrd, OpCode32MemReg.Create); + SetA32("<<<<00011001xxxxxxxx111110011111", InstName.Ldrex, InstEmit32.Ldrex, OpCode32MemLdEx.Create); + SetA32("<<<<00011101xxxxxxxx111110011111", InstName.Ldrexb, InstEmit32.Ldrexb, OpCode32MemLdEx.Create); + SetA32("<<<<00011011xxxxxxxx111110011111", InstName.Ldrexd, InstEmit32.Ldrexd, OpCode32MemLdEx.Create); + SetA32("<<<<00011111xxxxxxxx111110011111", InstName.Ldrexh, InstEmit32.Ldrexh, OpCode32MemLdEx.Create); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1011xxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x1xxxxxxxx00001011xxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCode32MemReg.Create); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1101xxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x1xxxxxxxx00001101xxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCode32MemReg.Create); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1111xxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x1xxxxxxxx00001111xxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCode32MemReg.Create); + SetA32("<<<<1110xxx0xxxxxxxx111xxxx1xxxx", InstName.Mcr, InstEmit32.Mcr, OpCode32System.Create); + SetA32("<<<<0000001xxxxxxxxxxxxx1001xxxx", InstName.Mla, InstEmit32.Mla, OpCode32AluMla.Create); + SetA32("<<<<00000110xxxxxxxxxxxx1001xxxx", InstName.Mls, InstEmit32.Mls, OpCode32AluMla.Create); + SetA32("<<<<0011101x0000xxxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluImm.Create); + SetA32("<<<<0001101x0000xxxxxxxxxxx0xxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluRsImm.Create); + SetA32("<<<<0001101x0000xxxxxxxx0xx1xxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluRsReg.Create); + SetA32("<<<<00110000xxxxxxxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluImm16.Create); + SetA32("<<<<00110100xxxxxxxxxxxxxxxxxxxx", InstName.Movt, InstEmit32.Movt, OpCode32AluImm16.Create); + SetA32("<<<<1110xxx1xxxxxxxx111xxxx1xxxx", InstName.Mrc, InstEmit32.Mrc, OpCode32System.Create); + SetA32("<<<<11000101xxxxxxxx111xxxxxxxxx", InstName.Mrrc, InstEmit32.Mrrc, OpCode32System.Create); + SetA32("<<<<00010x001111xxxx000000000000", InstName.Mrs, InstEmit32.Mrs, OpCode32Mrs.Create); + SetA32("<<<<00010x10xxxx111100000000xxxx", InstName.Msr, InstEmit32.Msr, OpCode32MsrReg.Create); + SetA32("<<<<0000000xxxxx0000xxxx1001xxxx", InstName.Mul, InstEmit32.Mul, OpCode32AluMla.Create); + SetA32("<<<<0011111x0000xxxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCode32AluImm.Create); + SetA32("<<<<0001111x0000xxxxxxxxxxx0xxxx", InstName.Mvn, InstEmit32.Mvn, OpCode32AluRsImm.Create); + SetA32("<<<<0001111x0000xxxxxxxx0xx1xxxx", InstName.Mvn, InstEmit32.Mvn, OpCode32AluRsReg.Create); + SetA32("<<<<0011001000001111000000000000", InstName.Nop, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011100xxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCode32AluImm.Create); + SetA32("<<<<0001100xxxxxxxxxxxxxxxx0xxxx", InstName.Orr, InstEmit32.Orr, OpCode32AluRsImm.Create); + SetA32("<<<<0001100xxxxxxxxxxxxx0xx1xxxx", InstName.Orr, InstEmit32.Orr, OpCode32AluRsReg.Create); + SetA32("<<<<01101000xxxxxxxxxxxxxx01xxxx", InstName.Pkh, InstEmit32.Pkh, OpCode32AluRsImm.Create); + SetA32("11110101xx01xxxx1111xxxxxxxxxxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create); + SetA32("11110111xx01xxxx1111xxxxxxx0xxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<011011111111xxxx11110011xxxx", InstName.Rbit, InstEmit32.Rbit, OpCode32AluReg.Create); + SetA32("<<<<011010111111xxxx11110011xxxx", InstName.Rev, InstEmit32.Rev, OpCode32AluReg.Create); + SetA32("<<<<011010111111xxxx11111011xxxx", InstName.Rev16, InstEmit32.Rev16, OpCode32AluReg.Create); + SetA32("<<<<011011111111xxxx11111011xxxx", InstName.Revsh, InstEmit32.Revsh, OpCode32AluReg.Create); + SetA32("<<<<0010011xxxxxxxxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCode32AluImm.Create); + SetA32("<<<<0000011xxxxxxxxxxxxxxxx0xxxx", InstName.Rsb, InstEmit32.Rsb, OpCode32AluRsImm.Create); + SetA32("<<<<0000011xxxxxxxxxxxxx0xx1xxxx", InstName.Rsb, InstEmit32.Rsb, OpCode32AluRsReg.Create); + SetA32("<<<<0010111xxxxxxxxxxxxxxxxxxxxx", InstName.Rsc, InstEmit32.Rsc, OpCode32AluImm.Create); + SetA32("<<<<0000111xxxxxxxxxxxxxxxx0xxxx", InstName.Rsc, InstEmit32.Rsc, OpCode32AluRsImm.Create); + SetA32("<<<<0000111xxxxxxxxxxxxx0xx1xxxx", InstName.Rsc, InstEmit32.Rsc, OpCode32AluRsReg.Create); + SetA32("<<<<01100001xxxxxxxx11111001xxxx", InstName.Sadd8, InstEmit32.Sadd8, OpCode32AluReg.Create); + SetA32("<<<<0010110xxxxxxxxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCode32AluImm.Create); + SetA32("<<<<0000110xxxxxxxxxxxxxxxx0xxxx", InstName.Sbc, InstEmit32.Sbc, OpCode32AluRsImm.Create); + SetA32("<<<<0000110xxxxxxxxxxxxx0xx1xxxx", InstName.Sbc, InstEmit32.Sbc, OpCode32AluRsReg.Create); + SetA32("<<<<0111101xxxxxxxxxxxxxx101xxxx", InstName.Sbfx, InstEmit32.Sbfx, OpCode32AluBf.Create); + SetA32("<<<<01110001xxxx1111xxxx0001xxxx", InstName.Sdiv, InstEmit32.Sdiv, OpCode32AluMla.Create); + SetA32("<<<<01101000xxxxxxxx11111011xxxx", InstName.Sel, InstEmit32.Sel, OpCode32AluReg.Create); + SetA32("<<<<0011001000001111000000000100", InstName.Sev, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011001000001111000000000101", InstName.Sevl, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<01100011xxxxxxxx11111001xxxx", InstName.Shadd8, InstEmit32.Shadd8, OpCode32AluReg.Create); + SetA32("<<<<01100011xxxxxxxx11111111xxxx", InstName.Shsub8, InstEmit32.Shsub8, OpCode32AluReg.Create); + SetA32("<<<<00010000xxxxxxxxxxxx1xx0xxxx", InstName.Smla__, InstEmit32.Smla__, OpCode32AluMla.Create); + SetA32("<<<<0000111xxxxxxxxxxxxx1001xxxx", InstName.Smlal, InstEmit32.Smlal, OpCode32AluUmull.Create); + SetA32("<<<<00010100xxxxxxxxxxxx1xx0xxxx", InstName.Smlal__, InstEmit32.Smlal__, OpCode32AluUmull.Create); + SetA32("<<<<00010010xxxxxxxxxxxx1x00xxxx", InstName.Smlaw_, InstEmit32.Smlaw_, OpCode32AluMla.Create); + SetA32("<<<<01110101xxxxxxxxxxxx00x1xxxx", InstName.Smmla, InstEmit32.Smmla, OpCode32AluMla.Create); + SetA32("<<<<01110101xxxxxxxxxxxx11x1xxxx", InstName.Smmls, InstEmit32.Smmls, OpCode32AluMla.Create); + SetA32("<<<<00010110xxxxxxxxxxxx1xx0xxxx", InstName.Smul__, InstEmit32.Smul__, OpCode32AluMla.Create); + SetA32("<<<<0000110xxxxxxxxxxxxx1001xxxx", InstName.Smull, InstEmit32.Smull, OpCode32AluUmull.Create); + SetA32("<<<<00010010xxxx0000xxxx1x10xxxx", InstName.Smulw_, InstEmit32.Smulw_, OpCode32AluMla.Create); + SetA32("<<<<0110101xxxxxxxxxxxxxxx01xxxx", InstName.Ssat, InstEmit32.Ssat, OpCode32Sat.Create); + SetA32("<<<<01101010xxxxxxxx11110011xxxx", InstName.Ssat16, InstEmit32.Ssat16, OpCode32Sat16.Create); + SetA32("<<<<01100001xxxxxxxx11111111xxxx", InstName.Ssub8, InstEmit32.Ssub8, OpCode32AluReg.Create); + SetA32("<<<<00011000xxxx111111001001xxxx", InstName.Stl, InstEmit32.Stl, OpCode32MemStEx.Create); + SetA32("<<<<00011100xxxx111111001001xxxx", InstName.Stlb, InstEmit32.Stlb, OpCode32MemStEx.Create); + SetA32("<<<<00011000xxxxxxxx11101001xxxx", InstName.Stlex, InstEmit32.Stlex, OpCode32MemStEx.Create); + SetA32("<<<<00011100xxxxxxxx11101001xxxx", InstName.Stlexb, InstEmit32.Stlexb, OpCode32MemStEx.Create); + SetA32("<<<<00011010xxxxxxxx11101001xxxx", InstName.Stlexd, InstEmit32.Stlexd, OpCode32MemStEx.Create); + SetA32("<<<<00011110xxxxxxxx11101001xxxx", InstName.Stlexh, InstEmit32.Stlexh, OpCode32MemStEx.Create); + SetA32("<<<<00011110xxxx111111001001xxxx", InstName.Stlh, InstEmit32.Stlh, OpCode32MemStEx.Create); + SetA32("<<<<100xx0x0xxxxxxxxxxxxxxxxxxxx", InstName.Stm, InstEmit32.Stm, OpCode32MemMult.Create); + SetA32("<<<<010xx0x0xxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit32.Str, OpCode32MemImm.Create); + SetA32("<<<<011xx0x0xxxxxxxxxxxxxxx0xxxx", InstName.Str, InstEmit32.Str, OpCode32MemRsImm.Create); + SetA32("<<<<010xx1x0xxxxxxxxxxxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCode32MemImm.Create); + SetA32("<<<<011xx1x0xxxxxxxxxxxxxxx0xxxx", InstName.Strb, InstEmit32.Strb, OpCode32MemRsImm.Create); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1111xxxx", InstName.Strd, InstEmit32.Strd, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x0xxxxxxxx00001111xxxx", InstName.Strd, InstEmit32.Strd, OpCode32MemReg.Create); + SetA32("<<<<00011000xxxxxxxx11111001xxxx", InstName.Strex, InstEmit32.Strex, OpCode32MemStEx.Create); + SetA32("<<<<00011100xxxxxxxx11111001xxxx", InstName.Strexb, InstEmit32.Strexb, OpCode32MemStEx.Create); + SetA32("<<<<00011010xxxxxxxx11111001xxxx", InstName.Strexd, InstEmit32.Strexd, OpCode32MemStEx.Create); + SetA32("<<<<00011110xxxxxxxx11111001xxxx", InstName.Strexh, InstEmit32.Strexh, OpCode32MemStEx.Create); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1011xxxx", InstName.Strh, InstEmit32.Strh, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x0xxxxxxxx00001011xxxx", InstName.Strh, InstEmit32.Strh, OpCode32MemReg.Create); + SetA32("<<<<0010010xxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCode32AluImm.Create); + SetA32("<<<<0000010xxxxxxxxxxxxxxxx0xxxx", InstName.Sub, InstEmit32.Sub, OpCode32AluRsImm.Create); + SetA32("<<<<0000010xxxxxxxxxxxxx0xx1xxxx", InstName.Sub, InstEmit32.Sub, OpCode32AluRsReg.Create); + SetA32("<<<<1111xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Svc, InstEmit32.Svc, OpCode32Exception.Create); + SetA32("<<<<01101010xxxxxxxxxx000111xxxx", InstName.Sxtb, InstEmit32.Sxtb, OpCode32AluUx.Create); + SetA32("<<<<01101000xxxxxxxxxx000111xxxx", InstName.Sxtb16, InstEmit32.Sxtb16, OpCode32AluUx.Create); + SetA32("<<<<01101011xxxxxxxxxx000111xxxx", InstName.Sxth, InstEmit32.Sxth, OpCode32AluUx.Create); + SetA32("<<<<00110011xxxx0000xxxxxxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCode32AluImm.Create); + SetA32("<<<<00010011xxxx0000xxxxxxx0xxxx", InstName.Teq, InstEmit32.Teq, OpCode32AluRsImm.Create); + SetA32("<<<<00010011xxxx0000xxxx0xx1xxxx", InstName.Teq, InstEmit32.Teq, OpCode32AluRsReg.Create); + SetA32("<<<<0111111111111101111011111110", InstName.Trap, InstEmit32.Trap, OpCode32Exception.Create); + SetA32("<<<<0011001000001111000000010010", InstName.Tsb, InstEmit32.Nop, OpCode32.Create); // Trace Synchronization Barrier (FEAT_TRF) + SetA32("<<<<00110001xxxx0000xxxxxxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluImm.Create); + SetA32("<<<<00010001xxxx0000xxxxxxx0xxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluRsImm.Create); + SetA32("<<<<00010001xxxx0000xxxx0xx1xxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluRsReg.Create); + SetA32("<<<<01100101xxxxxxxx11111001xxxx", InstName.Uadd8, InstEmit32.Uadd8, OpCode32AluReg.Create); + SetA32("<<<<0111111xxxxxxxxxxxxxx101xxxx", InstName.Ubfx, InstEmit32.Ubfx, OpCode32AluBf.Create); + SetA32("<<<<01110011xxxx1111xxxx0001xxxx", InstName.Udiv, InstEmit32.Udiv, OpCode32AluMla.Create); + SetA32("<<<<01100111xxxxxxxx11111001xxxx", InstName.Uhadd8, InstEmit32.Uhadd8, OpCode32AluReg.Create); + SetA32("<<<<01100111xxxxxxxx11111111xxxx", InstName.Uhsub8, InstEmit32.Uhsub8, OpCode32AluReg.Create); + SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create); + SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create); + SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create); + SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create); + SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create); + SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create); + SetA32("<<<<01101110xxxxxxxxxx000111xxxx", InstName.Uxtb, InstEmit32.Uxtb, OpCode32AluUx.Create); + SetA32("<<<<01101100xxxxxxxxxx000111xxxx", InstName.Uxtb16, InstEmit32.Uxtb16, OpCode32AluUx.Create); + SetA32("<<<<01101111xxxxxxxxxx000111xxxx", InstName.Uxth, InstEmit32.Uxth, OpCode32AluUx.Create); + SetA32("<<<<0011001000001111000000000010", InstName.Wfe, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011001000001111000000000011", InstName.Wfi, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011001000001111000000000001", InstName.Yield, InstEmit32.Nop, OpCode32.Create); + + // VFP + SetVfp("<<<<11101x110000xxxx101x11x0xxxx", InstName.Vabs, InstEmit32.Vabs_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11100x11xxxxxxxx101xx0x0xxxx", InstName.Vadd, InstEmit32.Vadd_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x11010xxxxx101x01x0xxxx", InstName.Vcmp, InstEmit32.Vcmp, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x11010xxxxx101x11x0xxxx", InstName.Vcmpe, InstEmit32.Vcmpe, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110111xxxx101x11x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FD, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); // FP 32 and 64, scalar. + SetVfp("<<<<11101x11110xxxxx101x11x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FI, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // FP32 to int. + SetVfp("<<<<11101x111000xxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FI, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // Int to FP32. + SetVfp("111111101x1111xxxxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_RM, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // The many FP32 to int encodings (fp). + SetVfp("<<<<11101x11001xxxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_TB, OpCode32SimdCvtTB.Create, OpCode32SimdCvtTB.CreateT32); + SetVfp("<<<<11101x00xxxxxxxx101xx0x0xxxx", InstName.Vdiv, InstEmit32.Vdiv_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101xx0xxxxxxxx1011x0x10000", InstName.Vdup, InstEmit32.Vdup, OpCode32SimdDupGP.Create, OpCode32SimdDupGP.CreateT32); + SetVfp("<<<<11101x10xxxxxxxx101xx0x0xxxx", InstName.Vfma, InstEmit32.Vfma_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x10xxxxxxxx101xx1x0xxxx", InstName.Vfms, InstEmit32.Vfms_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x01xxxxxxxx101xx1x0xxxx", InstName.Vfnma, InstEmit32.Vfnma_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x01xxxxxxxx101xx0x0xxxx", InstName.Vfnms, InstEmit32.Vfnms_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11001x01xxxxxxxx1011xxxxxxx0", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x11xxxxxxxx1011xxxxxxx0", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x11xxxxxxxx1011xxxxxxx0", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x01xxxxxxxx1010xxxxxxxx", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x11xxxxxxxx1010xxxxxxxx", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x11xxxxxxxx1010xxxxxxxx", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<1101xx01xxxxxxxx101xxxxxxxxx", InstName.Vldr, InstEmit32.Vldr, OpCode32SimdMemImm.Create, OpCode32SimdMemImm.CreateT32); + SetVfp("111111101x00xxxxxxxx10>>x0x0xxxx", InstName.Vmaxnm, InstEmit32.Vmaxnm_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("111111101x00xxxxxxxx10>>x1x0xxxx", InstName.Vminnm, InstEmit32.Vminnm_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x00xxxxxxxx101xx0x0xxxx", InstName.Vmla, InstEmit32.Vmla_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x00xxxxxxxx101xx1x0xxxx", InstName.Vmls, InstEmit32.Vmls_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100xx0xxxxxxxx1011xxx10000", InstName.Vmov, InstEmit32.Vmov_G1, OpCode32SimdMovGpElem.Create, OpCode32SimdMovGpElem.CreateT32); // From gen purpose. + SetVfp("<<<<1110xxx1xxxxxxxx1011xxx10000", InstName.Vmov, InstEmit32.Vmov_G1, OpCode32SimdMovGpElem.Create, OpCode32SimdMovGpElem.CreateT32); // To gen purpose. + SetVfp("<<<<1100010xxxxxxxxx101000x1xxxx", InstName.Vmov, InstEmit32.Vmov_G2, OpCode32SimdMovGpDouble.Create, OpCode32SimdMovGpDouble.CreateT32); // To/from gen purpose x2 and single precision x2. + SetVfp("<<<<1100010xxxxxxxxx101100x1xxxx", InstName.Vmov, InstEmit32.Vmov_GD, OpCode32SimdMovGpDouble.Create, OpCode32SimdMovGpDouble.CreateT32); // To/from gen purpose x2 and double precision. + SetVfp("<<<<1110000xxxxxxxxx1010x0010000", InstName.Vmov, InstEmit32.Vmov_GS, OpCode32SimdMovGp.Create, OpCode32SimdMovGp.CreateT32); // To/from gen purpose and single precision. + SetVfp("<<<<11101x11xxxxxxxx101x0000xxxx", InstName.Vmov, InstEmit32.Vmov_I, OpCode32SimdImm44.Create, OpCode32SimdImm44.CreateT32); // Scalar f16/32/64 based on size 01 10 11. + SetVfp("<<<<11101x110000xxxx101x01x0xxxx", InstName.Vmov, InstEmit32.Vmov_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101111xxxxxxxx101000010000", InstName.Vmrs, InstEmit32.Vmrs, OpCode32SimdSpecial.Create, OpCode32SimdSpecial.CreateT32); + SetVfp("<<<<11101110xxxxxxxx101000010000", InstName.Vmsr, InstEmit32.Vmsr, OpCode32SimdSpecial.Create, OpCode32SimdSpecial.CreateT32); + SetVfp("<<<<11100x10xxxxxxxx101xx0x0xxxx", InstName.Vmul, InstEmit32.Vmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x110001xxxx101x01x0xxxx", InstName.Vneg, InstEmit32.Vneg_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11100x01xxxxxxxx101xx1x0xxxx", InstName.Vnmla, InstEmit32.Vnmla_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x01xxxxxxxx101xx0x0xxxx", InstName.Vnmls, InstEmit32.Vnmls_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32); + SetVfp("<<<<11001x00xxxxxxxx1011xxxxxxx0", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x10xxxxxxxx1011xxxxxxx0", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x10xxxxxxxx1011xxxxxxx0", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x00xxxxxxxx1010xxxxxxxx", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x10xxxxxxxx1010xxxxxxxx", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x10xxxxxxxx1010xxxxxxxx", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<1101xx00xxxxxxxx101xxxxxxxxx", InstName.Vstr, InstEmit32.Vstr, OpCode32SimdMemImm.Create, OpCode32SimdMemImm.CreateT32); + SetVfp("<<<<11100x11xxxxxxxx101xx1x0xxxx", InstName.Vsub, InstEmit32.Vsub_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + + // ASIMD + SetAsimd("111100111x110000xxx0001101x0xxx0", InstName.Aesd_V, InstEmit32.Aesd_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100111x110000xxx0001100x0xxx0", InstName.Aese_V, InstEmit32.Aese_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100111x110000xxx0001111x0xxx0", InstName.Aesimc_V, InstEmit32.Aesimc_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100111x110000xxx0001110x0xxx0", InstName.Aesmc_V, InstEmit32.Aesmc_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100110x00xxx0xxx01100x1x0xxx0", InstName.Sha256h_V, InstEmit32.Sha256h_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100110x01xxx0xxx01100x1x0xxx0", InstName.Sha256h2_V, InstEmit32.Sha256h2_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100111x111010xxx0001111x0xxx0", InstName.Sha256su0_V, InstEmit32.Sha256su0_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100110x10xxx0xxx01100x1x0xxx0", InstName.Sha256su1_V, InstEmit32.Sha256su1_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("1111001x0x<xxxx", InstName.Vld4, InstEmit32.Vld4, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101000x10xxxxxxxx000x<>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x>>>xxxxxxx100000x1xxx0", InstName.Vqshrun, InstEmit32.Vqshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("1111001x0xxxxxxxxxxx0010xxx1xxxx", InstName.Vqsub, InstEmit32.Vqsub, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100111x111011xxxx010x0xx0xxxx", InstName.Vrecpe, InstEmit32.Vrecpe, OpCode32SimdSqrte.Create, OpCode32SimdSqrte.CreateT32); + SetAsimd("111100100x00xxxxxxxx1111xxx1xxxx", InstName.Vrecps, InstEmit32.Vrecps, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100111x11xx00xxxx000<>>xxxxxxx0010>xx1xxxx", InstName.Vrshr, InstEmit32.Vrshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111100101x>>>xxxxxxx100001x1xxx0", InstName.Vrshrn, InstEmit32.Vrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x111011xxxx010x1xx0xxxx", InstName.Vrsqrte, InstEmit32.Vrsqrte, OpCode32SimdSqrte.Create, OpCode32SimdSqrte.CreateT32); + SetAsimd("111100100x10xxxxxxxx1111xxx1xxxx", InstName.Vrsqrts, InstEmit32.Vrsqrts, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx0011>xx1xxxx", InstName.Vrsra, InstEmit32.Vrsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111100101x>>>xxxxxxx0101>xx1xxxx", InstName.Vshl, InstEmit32.Vshl, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("1111001x0xxxxxxxxxxx0100xxx0xxxx", InstName.Vshl, InstEmit32.Vshl_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding. + SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101001x00xxxxxxxx1000x000xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101001x00xxxxxxxx1000x011xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101000x00xxxxxxxx0111xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemPair.Create, OpCode32SimdMemPair.CreateT32); // Regs = 1. + SetAsimd("111101000x00xxxxxxxx1010xx<>>>", InstName.It, InstEmit32.It, OpCodeT16IfThen.Create); + SetT16("11000xxxxxxxxxxx", InstName.Stm, InstEmit32.Stm, OpCodeT16MemMult.Create); + SetT16("11001xxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, OpCodeT16MemMult.Create); + SetT16("1101<< allInsts, Func toFastLookupIndex) + { + List[] temp = new List[FastLookupSize]; + + for (int index = 0; index < temp.Length; index++) + { + temp[index] = new List(); + } + + foreach (InstInfo inst in allInsts) + { + int mask = toFastLookupIndex(inst.Mask); + int value = toFastLookupIndex(inst.Value); + + for (int index = 0; index < temp.Length; index++) + { + if ((index & mask) == value) + { + temp[index].Add(inst); + } + } + } + + for (int index = 0; index < temp.Length; index++) + { + table[index] = temp[index].ToArray(); + } + } + + private static void SetA32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + Set(encoding, _allInstA32, new InstDescriptor(name, emitter), makeOp); + } + + private static void SetT16(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + encoding = "xxxxxxxxxxxxxxxx" + encoding; + Set(encoding, _allInstT32, new InstDescriptor(name, emitter), makeOp); + } + + private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + string reversedEncoding = $"{encoding.AsSpan(16)}{encoding.AsSpan(0, 16)}"; + OpCode ReversedMakeOp(InstDescriptor inst, ulong address, int opCode) + => makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16)); + Set(reversedEncoding, _allInstT32, new InstDescriptor(name, emitter), ReversedMakeOp); + } + + private static void SetVfp(string encoding, InstName name, InstEmitter emitter, MakeOp makeOpA32, MakeOp makeOpT32) + { + SetA32(encoding, name, emitter, makeOpA32); + + string thumbEncoding = encoding; + if (thumbEncoding.StartsWith("<<<<")) + { + thumbEncoding = $"1110{thumbEncoding.AsSpan(4)}"; + } + SetT32(thumbEncoding, name, emitter, makeOpT32); + } + + private static void SetAsimd(string encoding, InstName name, InstEmitter emitter, MakeOp makeOpA32, MakeOp makeOpT32) + { + SetA32(encoding, name, emitter, makeOpA32); + + string thumbEncoding = encoding; + if (thumbEncoding.StartsWith("11110100")) + { + thumbEncoding = $"11111001{encoding.AsSpan(8)}"; + } + else if (thumbEncoding.StartsWith("1111001x")) + { + thumbEncoding = $"111x1111{encoding.AsSpan(8)}"; + } + else if (thumbEncoding.StartsWith("11110010")) + { + thumbEncoding = $"11101111{encoding.AsSpan(8)}"; + } + else if (thumbEncoding.StartsWith("11110011")) + { + thumbEncoding = $"11111111{encoding.AsSpan(8)}"; + } + else + { + throw new ArgumentException("Invalid ASIMD instruction encoding"); + } + SetT32(thumbEncoding, name, emitter, makeOpT32); + } + + private static void SetA64(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + Set(encoding, _allInstA64, new InstDescriptor(name, emitter), makeOp); + } + + private static void Set(string encoding, List list, InstDescriptor inst, MakeOp makeOp) + { + int bit = encoding.Length - 1; + int value = 0; + int xMask = 0; + int xBits = 0; + + int[] xPos = new int[encoding.Length]; + + int blacklisted = 0; + + for (int index = 0; index < encoding.Length; index++, bit--) + { + // Note: < and > are used on special encodings. + // The < means that we should never have ALL bits with the '<' set. + // So, when the encoding has <<, it means that 00, 01, and 10 are valid, + // but not 11. <<< is 000, 001, ..., 110 but NOT 111, and so on... + // For >, the invalid value is zero. So, for >> 01, 10 and 11 are valid, + // but 00 isn't. + char chr = encoding[index]; + + if (chr == '1') + { + value |= 1 << bit; + } + else if (chr == 'x') + { + xMask |= 1 << bit; + } + else if (chr == '>') + { + xPos[xBits++] = bit; + } + else if (chr == '<') + { + xPos[xBits++] = bit; + + blacklisted |= 1 << bit; + } + else if (chr != '0') + { + throw new ArgumentException($"Invalid encoding: {encoding}", nameof(encoding)); + } + } + + xMask = ~xMask; + + if (xBits == 0) + { + list.Add(new InstInfo(xMask, value, inst, makeOp)); + + return; + } + + for (int index = 0; index < (1 << xBits); index++) + { + int mask = 0; + + for (int x = 0; x < xBits; x++) + { + mask |= ((index >> x) & 1) << xPos[x]; + } + + if (mask != blacklisted) + { + list.Add(new InstInfo(xMask, value | mask, inst, makeOp)); + } + } + } + + public static (InstDescriptor inst, MakeOp makeOp) GetInstA32(int opCode) + { + return GetInstFromList(_instA32FastLookup[ToFastLookupIndexA(opCode)], opCode); + } + + public static (InstDescriptor inst, MakeOp makeOp) GetInstT32(int opCode) + { + return GetInstFromList(_instT32FastLookup[ToFastLookupIndexT(opCode)], opCode); + } + + public static (InstDescriptor inst, MakeOp makeOp) GetInstA64(int opCode) + { + return GetInstFromList(_instA64FastLookup[ToFastLookupIndexA(opCode)], opCode); + } + + private static (InstDescriptor inst, MakeOp makeOp) GetInstFromList(InstInfo[] insts, int opCode) + { + foreach (InstInfo info in insts) + { + if ((opCode & info.Mask) == info.Value) + { + return (info.Inst, info.MakeOp); + } + } + + return (new InstDescriptor(InstName.Und, InstEmit.Und), null); + } + + private static int ToFastLookupIndexA(int value) + { + return ((value >> 10) & 0x00F) | ((value >> 18) & 0xFF0); + } + + private static int ToFastLookupIndexT(int value) + { + return (value >> 4) & 0xFFF; + } + } +} diff --git a/src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs b/src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs new file mode 100644 index 000000000..9d988f0c9 --- /dev/null +++ b/src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Decoders.Optimizations +{ + static class TailCallRemover + { + public static Block[] RunPass(ulong entryAddress, List blocks) + { + // Detect tail calls: + // - Assume this function spans the space covered by contiguous code blocks surrounding the entry address. + // - A jump to an area outside this contiguous region will be treated as an exit block. + // - Include a small allowance for jumps outside the contiguous range. + + if (!Decoder.BinarySearch(blocks, entryAddress, out int entryBlockId)) + { + throw new InvalidOperationException("Function entry point is not contained in a block."); + } + + const ulong Allowance = 4; + + Block entryBlock = blocks[entryBlockId]; + + Block startBlock = entryBlock; + Block endBlock = entryBlock; + + int startBlockIndex = entryBlockId; + int endBlockIndex = entryBlockId; + + for (int i = entryBlockId + 1; i < blocks.Count; i++) // Search forwards. + { + Block block = blocks[i]; + + if (endBlock.EndAddress < block.Address - Allowance) + { + break; // End of contiguous function. + } + + endBlock = block; + endBlockIndex = i; + } + + for (int i = entryBlockId - 1; i >= 0; i--) // Search backwards. + { + Block block = blocks[i]; + + if (startBlock.Address > block.EndAddress + Allowance) + { + break; // End of contiguous function. + } + + startBlock = block; + startBlockIndex = i; + } + + if (startBlockIndex == 0 && endBlockIndex == blocks.Count - 1) + { + return blocks.ToArray(); // Nothing to do here. + } + + // Mark branches whose target is outside of the contiguous region as an exit block. + for (int i = startBlockIndex; i <= endBlockIndex; i++) + { + Block block = blocks[i]; + + if (block.Branch != null && (block.Branch.Address > endBlock.EndAddress || block.Branch.EndAddress < startBlock.Address)) + { + block.Branch.Exit = true; + } + } + + var newBlocks = new List(blocks.Count); + + // Finally, rebuild decoded block list, ignoring blocks outside the contiguous range. + for (int i = 0; i < blocks.Count; i++) + { + Block block = blocks[i]; + + if (block.Exit || (i >= startBlockIndex && i <= endBlockIndex)) + { + newBlocks.Add(block); + } + } + + return newBlocks.ToArray(); + } + } +} diff --git a/src/ARMeilleure/Decoders/RegisterSize.cs b/src/ARMeilleure/Decoders/RegisterSize.cs new file mode 100644 index 000000000..7c00984e8 --- /dev/null +++ b/src/ARMeilleure/Decoders/RegisterSize.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + enum RegisterSize + { + Int32, + Int64, + Simd64, + Simd128, + } +} diff --git a/src/ARMeilleure/Decoders/ShiftType.cs b/src/ARMeilleure/Decoders/ShiftType.cs new file mode 100644 index 000000000..43b738f3f --- /dev/null +++ b/src/ARMeilleure/Decoders/ShiftType.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + enum ShiftType + { + Lsl = 0, + Lsr = 1, + Asr = 2, + Ror = 3, + } +} diff --git a/src/ARMeilleure/Diagnostics/IRDumper.cs b/src/ARMeilleure/Diagnostics/IRDumper.cs new file mode 100644 index 000000000..16833d085 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/IRDumper.cs @@ -0,0 +1,327 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ARMeilleure.Diagnostics +{ + class IRDumper + { + private const string Indentation = " "; + + private int _indentLevel; + + private readonly StringBuilder _builder; + + private readonly Dictionary _localNames; + private readonly Dictionary _symbolNames; + + public IRDumper(int indent) + { + _indentLevel = indent; + + _builder = new StringBuilder(); + + _localNames = new Dictionary(); + _symbolNames = new Dictionary(); + } + + private void Indent() + { + _builder.EnsureCapacity(_builder.Capacity + _indentLevel * Indentation.Length); + + for (int index = 0; index < _indentLevel; index++) + { +#pragma warning disable CA1834 // Use StringBuilder.Append(char) for single character strings + _builder.Append(Indentation); +#pragma warning restore CA1834 + } + } + + private void IncreaseIndentation() + { + _indentLevel++; + } + + private void DecreaseIndentation() + { + _indentLevel--; + } + + private void DumpBlockName(BasicBlock block) + { + _builder.Append("block").Append(block.Index); + } + + private void DumpBlockHeader(BasicBlock block) + { + DumpBlockName(block); + + if (block.Frequency == BasicBlockFrequency.Cold) + { + _builder.Append(" cold"); + } + + if (block.SuccessorsCount > 0) + { + _builder.Append(" ("); + + for (int i = 0; i < block.SuccessorsCount; i++) + { + DumpBlockName(block.GetSuccessor(i)); + + if (i < block.SuccessorsCount - 1) + { + _builder.Append(", "); + } + } + + _builder.Append(')'); + } + + _builder.Append(':'); + } + + private void DumpOperand(Operand operand) + { + if (operand == default) + { + _builder.Append(""); + return; + } + + _builder.Append(GetTypeName(operand.Type)).Append(' '); + + switch (operand.Kind) + { + case OperandKind.LocalVariable: + if (!_localNames.TryGetValue(operand, out string localName)) + { + localName = $"%{_localNames.Count}"; + + _localNames.Add(operand, localName); + } + + _builder.Append(localName); + break; + + case OperandKind.Register: + Register reg = operand.GetRegister(); + + switch (reg.Type) + { + case RegisterType.Flag: + _builder.Append('b'); + break; + case RegisterType.FpFlag: + _builder.Append('f'); + break; + case RegisterType.Integer: + _builder.Append('r'); + break; + case RegisterType.Vector: + _builder.Append('v'); + break; + } + + _builder.Append(reg.Index); + break; + + case OperandKind.Constant: + string symbolName = Symbols.Get(operand.Value); + + if (symbolName != null && !_symbolNames.ContainsKey(operand.Value)) + { + _symbolNames.Add(operand.Value, symbolName); + } + + _builder.Append("0x").Append(operand.Value.ToString("X")); + break; + + case OperandKind.Memory: + var memOp = operand.GetMemory(); + + _builder.Append('['); + + DumpOperand(memOp.BaseAddress); + + if (memOp.Index != default) + { + _builder.Append(" + "); + + DumpOperand(memOp.Index); + + switch (memOp.Scale) + { + case Multiplier.x2: + _builder.Append("*2"); + break; + case Multiplier.x4: + _builder.Append("*4"); + break; + case Multiplier.x8: + _builder.Append("*8"); + break; + } + } + + if (memOp.Displacement != 0) + { + _builder.Append(" + 0x").Append(memOp.Displacement.ToString("X")); + } + + _builder.Append(']'); + break; + + default: + _builder.Append(operand.Type); + break; + } + } + + private void DumpNode(ControlFlowGraph cfg, Operation node) + { + for (int index = 0; index < node.DestinationsCount; index++) + { + DumpOperand(node.GetDestination(index)); + + if (index == node.DestinationsCount - 1) + { + _builder.Append(" = "); + } + else + { + _builder.Append(", "); + } + } + + switch (node) + { + case Operation operation: + if (operation.Instruction == Instruction.Phi) + { + PhiOperation phi = operation.AsPhi(); + + _builder.Append("Phi "); + + for (int index = 0; index < phi.SourcesCount; index++) + { + _builder.Append('('); + + DumpBlockName(phi.GetBlock(cfg, index)); + + _builder.Append(": "); + + DumpOperand(phi.GetSource(index)); + + _builder.Append(')'); + + if (index < phi.SourcesCount - 1) + { + _builder.Append(", "); + } + } + + break; + } + + bool comparison = false; + + _builder.Append(operation.Instruction); + + if (operation.Instruction == Instruction.Extended) + { + _builder.Append('.').Append(operation.Intrinsic); + } + else if (operation.Instruction == Instruction.BranchIf || + operation.Instruction == Instruction.Compare) + { + comparison = true; + } + + _builder.Append(' '); + + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (index < operation.SourcesCount - 1) + { + DumpOperand(source); + + _builder.Append(", "); + } + else if (comparison) + { + _builder.Append((Comparison)source.AsInt32()); + } + else + { + DumpOperand(source); + } + } + break; + } + + if (_symbolNames.Count == 1) + { + _builder.Append(" ;; ").Append(_symbolNames.First().Value); + } + else if (_symbolNames.Count > 1) + { + _builder.Append(" ;;"); + + foreach ((ulong value, string name) in _symbolNames) + { + _builder.Append(" 0x").Append(value.ToString("X")).Append(" = ").Append(name); + } + } + + // Reset the set of symbols for the next Node we're going to dump. + _symbolNames.Clear(); + } + + public static string GetDump(ControlFlowGraph cfg) + { + var dumper = new IRDumper(1); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + dumper.Indent(); + dumper.DumpBlockHeader(block); + + dumper._builder.AppendLine(); + + dumper.IncreaseIndentation(); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + dumper.Indent(); + dumper.DumpNode(cfg, node); + + dumper._builder.AppendLine(); + } + + dumper.DecreaseIndentation(); + } + + return dumper._builder.ToString(); + } + + private static string GetTypeName(OperandType type) + { + return type switch + { + OperandType.None => "none", + OperandType.I32 => "i32", + OperandType.I64 => "i64", + OperandType.FP32 => "f32", + OperandType.FP64 => "f64", + OperandType.V128 => "v128", + _ => throw new ArgumentException($"Invalid operand type \"{type}\"."), + }; + } + } +} diff --git a/src/ARMeilleure/Diagnostics/Logger.cs b/src/ARMeilleure/Diagnostics/Logger.cs new file mode 100644 index 000000000..d7f61230c --- /dev/null +++ b/src/ARMeilleure/Diagnostics/Logger.cs @@ -0,0 +1,56 @@ +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +namespace ARMeilleure.Diagnostics +{ + static class Logger + { + private static long _startTime; + + private static readonly long[] _accumulatedTime; + + static Logger() + { + _accumulatedTime = new long[(int)PassName.Count]; + } + + [Conditional("M_DEBUG")] + public static void StartPass(PassName name) + { + WriteOutput(name + " pass started..."); + + _startTime = Stopwatch.GetTimestamp(); + } + + [Conditional("M_DEBUG")] + public static void EndPass(PassName name, ControlFlowGraph cfg) + { + EndPass(name); + + WriteOutput("IR after " + name + " pass:"); + + WriteOutput(IRDumper.GetDump(cfg)); + } + + [Conditional("M_DEBUG")] + public static void EndPass(PassName name) + { + long elapsedTime = Stopwatch.GetTimestamp() - _startTime; + + _accumulatedTime[(int)name] += elapsedTime; + + WriteOutput($"{name} pass ended after {GetMilliseconds(_accumulatedTime[(int)name])} ms..."); + } + + private static long GetMilliseconds(long ticks) + { + return (long)(((double)ticks / Stopwatch.Frequency) * 1000); + } + + private static void WriteOutput(string text) + { + Console.WriteLine(text); + } + } +} diff --git a/src/ARMeilleure/Diagnostics/PassName.cs b/src/ARMeilleure/Diagnostics/PassName.cs new file mode 100644 index 000000000..2d87659f0 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/PassName.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Diagnostics +{ + enum PassName + { + Decoding, + Translation, + RegisterUsage, + TailMerge, + Dominance, + SsaConstruction, + RegisterToLocal, + Optimization, + PreAllocation, + RegisterAllocation, + CodeGeneration, + + Count, + } +} diff --git a/src/ARMeilleure/Diagnostics/Symbols.cs b/src/ARMeilleure/Diagnostics/Symbols.cs new file mode 100644 index 000000000..be74d2b5b --- /dev/null +++ b/src/ARMeilleure/Diagnostics/Symbols.cs @@ -0,0 +1,85 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace ARMeilleure.Diagnostics +{ + static class Symbols + { + private readonly struct RangedSymbol + { + public readonly ulong Start; + public readonly ulong End; + public readonly ulong ElementSize; + public readonly string Name; + + public RangedSymbol(ulong start, ulong end, ulong elemSize, string name) + { + Start = start; + End = end; + ElementSize = elemSize; + Name = name; + } + } + + private static readonly ConcurrentDictionary _symbols; + private static readonly List _rangedSymbols; + + static Symbols() + { + _symbols = new ConcurrentDictionary(); + _rangedSymbols = new List(); + } + + public static string Get(ulong address) + { + if (_symbols.TryGetValue(address, out string result)) + { + return result; + } + + lock (_rangedSymbols) + { + foreach (RangedSymbol symbol in _rangedSymbols) + { + if (address >= symbol.Start && address <= symbol.End) + { + ulong diff = address - symbol.Start; + ulong rem = diff % symbol.ElementSize; + + StringBuilder resultBuilder = new(); + resultBuilder.Append($"{symbol.Name}_{diff / symbol.ElementSize}"); + + if (rem != 0) + { + resultBuilder.Append($"+{rem}"); + } + + result = resultBuilder.ToString(); + _symbols.TryAdd(address, result); + + return result; + } + } + } + + return null; + } + + [Conditional("M_DEBUG")] + public static void Add(ulong address, string name) + { + _symbols.TryAdd(address, name); + } + + [Conditional("M_DEBUG")] + public static void Add(ulong address, ulong size, ulong elemSize, string name) + { + lock (_rangedSymbols) + { + _rangedSymbols.Add(new RangedSymbol(address, address + size, elemSize, name)); + } + } + } +} diff --git a/src/ARMeilleure/Diagnostics/TranslatorEventSource.cs b/src/ARMeilleure/Diagnostics/TranslatorEventSource.cs new file mode 100644 index 000000000..2e1be8c51 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/TranslatorEventSource.cs @@ -0,0 +1,67 @@ +using System.Diagnostics.Tracing; +using System.Threading; + +namespace ARMeilleure.Diagnostics +{ + [EventSource(Name = "ARMeilleure")] + class TranslatorEventSource : EventSource + { + public static readonly TranslatorEventSource Log = new(); + + private int _rejitQueue; + private ulong _funcTabSize; + private ulong _funcTabLeafSize; + private PollingCounter _rejitQueueCounter; + private PollingCounter _funcTabSizeCounter; + private PollingCounter _funcTabLeafSizeCounter; + + public TranslatorEventSource() + { + _rejitQueueCounter = new PollingCounter("rejit-queue-length", this, () => _rejitQueue) + { + DisplayName = "Rejit Queue Length", + }; + + _funcTabSizeCounter = new PollingCounter("addr-tab-alloc", this, () => _funcTabSize / 1024d / 1024d) + { + DisplayName = "AddressTable Total Bytes Allocated", + DisplayUnits = "MiB", + }; + + _funcTabLeafSizeCounter = new PollingCounter("addr-tab-leaf-alloc", this, () => _funcTabLeafSize / 1024d / 1024d) + { + DisplayName = "AddressTable Total Leaf Bytes Allocated", + DisplayUnits = "MiB", + }; + } + + public void RejitQueueAdd(int count) + { + Interlocked.Add(ref _rejitQueue, count); + } + + public void AddressTableAllocated(int bytes, bool leaf) + { + _funcTabSize += (uint)bytes; + + if (leaf) + { + _funcTabLeafSize += (uint)bytes; + } + } + + protected override void Dispose(bool disposing) + { + _rejitQueueCounter.Dispose(); + _rejitQueueCounter = null; + + _funcTabLeafSizeCounter.Dispose(); + _funcTabLeafSizeCounter = null; + + _funcTabSizeCounter.Dispose(); + _funcTabSizeCounter = null; + + base.Dispose(disposing); + } + } +} diff --git a/src/ARMeilleure/Instructions/CryptoHelper.cs b/src/ARMeilleure/Instructions/CryptoHelper.cs new file mode 100644 index 000000000..ba68cebb3 --- /dev/null +++ b/src/ARMeilleure/Instructions/CryptoHelper.cs @@ -0,0 +1,282 @@ +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf + +using ARMeilleure.State; +using System; + +namespace ARMeilleure.Instructions +{ + static class CryptoHelper + { + #region "LookUp Tables" +#pragma warning disable IDE1006 // Naming rule violation + private static ReadOnlySpan _sBox => new byte[] + { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, + }; + + private static ReadOnlySpan _invSBox => new byte[] + { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, + }; + + private static ReadOnlySpan _gfMul02 => new byte[] + { + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, + 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, + 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, + 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, + 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, + 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, + 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, + 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, + 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, + 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, + 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, + 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, + 0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, + 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, + 0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, + 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5, + }; + + private static ReadOnlySpan _gfMul03 => new byte[] + { + 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, + 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, + 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, + 0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41, + 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, + 0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1, + 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1, + 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, + 0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a, + 0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba, + 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, + 0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda, + 0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a, + 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, + 0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a, + 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a, + }; + + private static ReadOnlySpan _gfMul09 => new byte[] + { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, + 0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, + 0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, + 0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, + 0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, + 0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, + 0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, + 0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, + 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, + 0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, + 0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, + 0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, + 0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46, + }; + + private static ReadOnlySpan _gfMul0B => new byte[] + { + 0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, + 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, + 0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, + 0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, + 0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, + 0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, + 0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, + 0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, + 0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, + 0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, + 0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, + 0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, + 0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, + 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, + 0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, + 0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3, + }; + + private static ReadOnlySpan _gfMul0D => new byte[] + { + 0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, + 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, + 0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, + 0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, + 0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, + 0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, + 0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, + 0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, + 0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, + 0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, + 0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, + 0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, + 0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, + 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, + 0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, + 0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97, + }; + + private static ReadOnlySpan _gfMul0E => new byte[] + { + 0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, + 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, + 0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, + 0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, + 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, + 0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, + 0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, + 0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, + 0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, + 0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, + 0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, + 0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, + 0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, + 0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, + 0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, + 0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d, + }; + + private static ReadOnlySpan _srPerm => new byte[] + { + 0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, + }; + + private static ReadOnlySpan _isrPerm => new byte[] + { + 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11, + }; +#pragma warning restore IDE1006 + #endregion + + public static V128 AesInvMixColumns(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int columns = 0; columns <= 3; columns++) + { + int idx = columns << 2; + + byte row0 = inState[idx + 0]; // A, E, I, M: [row0, col0-col3] + byte row1 = inState[idx + 1]; // B, F, J, N: [row1, col0-col3] + byte row2 = inState[idx + 2]; // C, G, K, O: [row2, col0-col3] + byte row3 = inState[idx + 3]; // D, H, L, P: [row3, col0-col3] + + outState[idx + 0] = (byte)((uint)_gfMul0E[row0] ^ _gfMul0B[row1] ^ _gfMul0D[row2] ^ _gfMul09[row3]); + outState[idx + 1] = (byte)((uint)_gfMul09[row0] ^ _gfMul0E[row1] ^ _gfMul0B[row2] ^ _gfMul0D[row3]); + outState[idx + 2] = (byte)((uint)_gfMul0D[row0] ^ _gfMul09[row1] ^ _gfMul0E[row2] ^ _gfMul0B[row3]); + outState[idx + 3] = (byte)((uint)_gfMul0B[row0] ^ _gfMul0D[row1] ^ _gfMul09[row2] ^ _gfMul0E[row3]); + } + + return new V128(outState); + } + + public static V128 AesInvShiftRows(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[_isrPerm[idx]] = inState[idx]; + } + + return new V128(outState); + } + + public static V128 AesInvSubBytes(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[idx] = _invSBox[inState[idx]]; + } + + return new V128(outState); + } + + public static V128 AesMixColumns(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int columns = 0; columns <= 3; columns++) + { + int idx = columns << 2; + + byte row0 = inState[idx + 0]; // A, E, I, M: [row0, col0-col3] + byte row1 = inState[idx + 1]; // B, F, J, N: [row1, col0-col3] + byte row2 = inState[idx + 2]; // C, G, K, O: [row2, col0-col3] + byte row3 = inState[idx + 3]; // D, H, L, P: [row3, col0-col3] + + outState[idx + 0] = (byte)((uint)_gfMul02[row0] ^ _gfMul03[row1] ^ row2 ^ row3); + outState[idx + 1] = (byte)((uint)row0 ^ _gfMul02[row1] ^ _gfMul03[row2] ^ row3); + outState[idx + 2] = (byte)((uint)row0 ^ row1 ^ _gfMul02[row2] ^ _gfMul03[row3]); + outState[idx + 3] = (byte)((uint)_gfMul03[row0] ^ row1 ^ row2 ^ _gfMul02[row3]); + } + + return new V128(outState); + } + + public static V128 AesShiftRows(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[_srPerm[idx]] = inState[idx]; + } + + return new V128(outState); + } + + public static V128 AesSubBytes(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[idx] = _sBox[inState[idx]]; + } + + return new V128(outState); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitAlu.cs b/src/ARMeilleure/Instructions/InstEmitAlu.cs new file mode 100644 index 000000000..ac17c32a7 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitAlu.cs @@ -0,0 +1,399 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System.Diagnostics; +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Adc(ArmEmitterContext context) => EmitAdc(context, setFlags: false); + public static void Adcs(ArmEmitterContext context) => EmitAdc(context, setFlags: true); + + private static void EmitAdc(ArmEmitterContext context, bool setFlags) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.Add(n, m); + + Operand carry = GetFlag(PState.CFlag); + + if (context.CurrOp.RegisterSize == RegisterSize.Int64) + { + carry = context.ZeroExtend32(OperandType.I64, carry); + } + + d = context.Add(d, carry); + + if (setFlags) + { + EmitNZFlagsCheck(context, d); + + EmitAdcsCCheck(context, n, d); + EmitAddsVCheck(context, n, m, d); + } + + SetAluDOrZR(context, d); + } + + public static void Add(ArmEmitterContext context) + { + SetAluD(context, context.Add(GetAluN(context), GetAluM(context))); + } + + public static void Adds(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + context.MarkComparison(n, m); + + Operand d = context.Add(n, m); + + EmitNZFlagsCheck(context, d); + + EmitAddsCCheck(context, n, d); + EmitAddsVCheck(context, n, m, d); + + SetAluDOrZR(context, d); + } + + public static void And(ArmEmitterContext context) + { + SetAluD(context, context.BitwiseAnd(GetAluN(context), GetAluM(context))); + } + + public static void Ands(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseAnd(n, m); + + EmitNZFlagsCheck(context, d); + EmitCVFlagsClear(context); + + SetAluDOrZR(context, d); + } + + public static void Asrv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.ShiftRightSI(GetAluN(context), GetAluMShift(context))); + } + + public static void Bic(ArmEmitterContext context) => EmitBic(context, setFlags: false); + public static void Bics(ArmEmitterContext context) => EmitBic(context, setFlags: true); + + private static void EmitBic(ArmEmitterContext context, bool setFlags) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseAnd(n, context.BitwiseNot(m)); + + if (setFlags) + { + EmitNZFlagsCheck(context, d); + EmitCVFlagsClear(context); + } + + SetAluD(context, d, setFlags); + } + + public static void Cls(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + Operand nHigh = context.ShiftRightUI(n, Const(1)); + + bool is32Bits = op.RegisterSize == RegisterSize.Int32; + + Operand mask = is32Bits ? Const(int.MaxValue) : Const(long.MaxValue); + + Operand nLow = context.BitwiseAnd(n, mask); + + Operand res = context.CountLeadingZeros(context.BitwiseExclusiveOr(nHigh, nLow)); + + res = context.Subtract(res, Const(res.Type, 1)); + + SetAluDOrZR(context, res); + } + + public static void Clz(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + Operand d = context.CountLeadingZeros(n); + + SetAluDOrZR(context, d); + } + + public static void Eon(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseExclusiveOr(n, context.BitwiseNot(m)); + + SetAluD(context, d); + } + + public static void Eor(ArmEmitterContext context) + { + SetAluD(context, context.BitwiseExclusiveOr(GetAluN(context), GetAluM(context))); + } + + public static void Extr(ArmEmitterContext context) + { + OpCodeAluRs op = (OpCodeAluRs)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rm); + + if (op.Shift != 0) + { + if (op.Rn == op.Rm) + { + res = context.RotateRight(res, Const(op.Shift)); + } + else + { + res = context.ShiftRightUI(res, Const(op.Shift)); + + Operand n = GetIntOrZR(context, op.Rn); + + int invShift = op.GetBitsCount() - op.Shift; + + res = context.BitwiseOr(res, context.ShiftLeft(n, Const(invShift))); + } + } + + SetAluDOrZR(context, res); + } + + public static void Lslv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.ShiftLeft(GetAluN(context), GetAluMShift(context))); + } + + public static void Lsrv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.ShiftRightUI(GetAluN(context), GetAluMShift(context))); + } + + public static void Sbc(ArmEmitterContext context) => EmitSbc(context, setFlags: false); + public static void Sbcs(ArmEmitterContext context) => EmitSbc(context, setFlags: true); + + private static void EmitSbc(ArmEmitterContext context, bool setFlags) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.Subtract(n, m); + + Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); + + if (context.CurrOp.RegisterSize == RegisterSize.Int64) + { + borrow = context.ZeroExtend32(OperandType.I64, borrow); + } + + d = context.Subtract(d, borrow); + + if (setFlags) + { + EmitNZFlagsCheck(context, d); + + EmitSbcsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, d); + } + + SetAluDOrZR(context, d); + } + + public static void Sub(ArmEmitterContext context) + { + SetAluD(context, context.Subtract(GetAluN(context), GetAluM(context))); + } + + public static void Subs(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + context.MarkComparison(n, m); + + Operand d = context.Subtract(n, m); + + EmitNZFlagsCheck(context, d); + + EmitSubsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, d); + + SetAluDOrZR(context, d); + } + + public static void Orn(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseOr(n, context.BitwiseNot(m)); + + SetAluD(context, d); + } + + public static void Orr(ArmEmitterContext context) + { + SetAluD(context, context.BitwiseOr(GetAluN(context), GetAluM(context))); + } + + public static void Rbit(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand d; + + if (op.RegisterSize == RegisterSize.Int32) + { + d = EmitReverseBits32Op(context, n); + } + else + { + d = EmitReverseBits64Op(context, n); + } + + SetAluDOrZR(context, d); + } + + private static Operand EmitReverseBits64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaaaaaaaaaaul)), Const(1)), + context.ShiftLeft(context.BitwiseAnd(op, Const(0x5555555555555555ul)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccccccccccul)), Const(2)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x3333333333333333ul)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0f0f0f0f0ul)), Const(4)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0f0f0f0f0f0f0f0ful)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x00ff00ff00ff00fful)), Const(8))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(32)), context.ShiftLeft(val, Const(32))); + } + + public static void Rev16(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand d; + + if (op.RegisterSize == RegisterSize.Int32) + { + d = EmitReverseBytes16_32Op(context, n); + } + else + { + d = EmitReverseBytes16_64Op(context, n); + } + + SetAluDOrZR(context, d); + } + + public static void Rev32(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand d; + + if (op.RegisterSize == RegisterSize.Int32) + { + d = context.ByteSwap(n); + } + else + { + d = EmitReverseBytes32_64Op(context, n); + } + + SetAluDOrZR(context, d); + } + + private static Operand EmitReverseBytes32_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = EmitReverseBytes16_64Op(context, op); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); + } + + public static void Rev64(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + SetAluDOrZR(context, context.ByteSwap(GetIntOrZR(context, op.Rn))); + } + + public static void Rorv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.RotateRight(GetAluN(context), GetAluMShift(context))); + } + + private static Operand GetAluMShift(ArmEmitterContext context) + { + IOpCodeAluRs op = (IOpCodeAluRs)context.CurrOp; + + Operand m = GetIntOrZR(context, op.Rm); + + if (op.RegisterSize == RegisterSize.Int64) + { + m = context.ConvertI64ToI32(m); + } + + return context.BitwiseAnd(m, Const(context.CurrOp.GetBitsCount() - 1)); + } + + private static void EmitCVFlagsClear(ArmEmitterContext context) + { + SetFlag(context, PState.CFlag, Const(0)); + SetFlag(context, PState.VFlag, Const(0)); + } + + public static void SetAluD(ArmEmitterContext context, Operand d) + { + SetAluD(context, d, x31IsZR: false); + } + + public static void SetAluDOrZR(ArmEmitterContext context, Operand d) + { + SetAluD(context, d, x31IsZR: true); + } + + public static void SetAluD(ArmEmitterContext context, Operand d, bool x31IsZR) + { + IOpCodeAlu op = (IOpCodeAlu)context.CurrOp; + + if ((x31IsZR || op is IOpCodeAluRs) && op.Rd == RegisterConsts.ZeroIndex) + { + return; + } + + SetIntOrSP(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitAlu32.cs b/src/ARMeilleure/Instructions/InstEmitAlu32.cs new file mode 100644 index 000000000..3a5e71bcc --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitAlu32.cs @@ -0,0 +1,932 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System.Diagnostics.CodeAnalysis; +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + [SuppressMessage("Style", "IDE0059: Remove unnecessary value assignment")] + static partial class InstEmit32 + { + public static void Add(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Add(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitAddsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Adc(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Add(n, m); + + Operand carry = GetFlag(PState.CFlag); + + res = context.Add(res, carry); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitAdcsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void And(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseAnd(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Bfc(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + Operand d = GetIntA32(context, op.Rd); + Operand res = context.BitwiseAnd(d, Const(~op.DestMask)); + + SetIntA32(context, op.Rd, res); + } + + public static void Bfi(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand d = GetIntA32(context, op.Rd); + Operand part = context.BitwiseAnd(n, Const(op.SourceMask)); + + if (op.Lsb != 0) + { + part = context.ShiftLeft(part, Const(op.Lsb)); + } + + Operand res = context.BitwiseAnd(d, Const(~op.DestMask)); + res = context.BitwiseOr(res, context.BitwiseAnd(part, Const(op.DestMask))); + + SetIntA32(context, op.Rd, res); + } + + public static void Bic(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseAnd(n, context.BitwiseNot(m)); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Clz(ArmEmitterContext context) + { + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.CountLeadingZeros(m); + EmitAluStore(context, res); + } + + public static void Cmp(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(n, m); + + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, n, res); + EmitSubsVCheck(context, n, m, res); + } + + public static void Cmn(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Add(n, m); + + EmitNZFlagsCheck(context, res); + + EmitAddsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + public static void Eor(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseExclusiveOr(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Mov(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand m = GetAluM(context); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, m); + } + + EmitAluStore(context, m); + } + + public static void Movt(ArmEmitterContext context) + { + IOpCode32AluImm16 op = (IOpCode32AluImm16)context.CurrOp; + + Operand d = GetIntA32(context, op.Rd); + Operand imm = Const(op.Immediate << 16); // Immeditate value as top halfword. + Operand res = context.BitwiseAnd(d, Const(0x0000ffff)); + res = context.BitwiseOr(res, imm); + + EmitAluStore(context, res); + } + + public static void Mul(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.Multiply(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Mvn(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + Operand m = GetAluM(context); + + Operand res = context.BitwiseNot(m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Orr(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseOr(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Orn(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseOr(n, context.BitwiseNot(m)); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Pkh(ArmEmitterContext context) + { + OpCode32AluRsImm op = (OpCode32AluRsImm)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res; + + bool tbform = op.ShiftType == ShiftType.Asr; + if (tbform) + { + res = context.BitwiseOr(context.BitwiseAnd(n, Const(0xFFFF0000)), context.BitwiseAnd(m, Const(0xFFFF))); + } + else + { + res = context.BitwiseOr(context.BitwiseAnd(m, Const(0xFFFF0000)), context.BitwiseAnd(n, Const(0xFFFF))); + } + + EmitAluStore(context, res); + } + + public static void Rbit(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = EmitReverseBits32Op(context, m); + + EmitAluStore(context, res); + } + + public static void Rev(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = context.ByteSwap(m); + + EmitAluStore(context, res); + } + + public static void Rev16(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = EmitReverseBytes16_32Op(context, m); + + EmitAluStore(context, res); + } + + public static void Revsh(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = EmitReverseBytes16_32Op(context, m); + + EmitAluStore(context, context.SignExtend16(OperandType.I32, res)); + } + + public static void Rsc(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(m, n); + + Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); + + res = context.Subtract(res, borrow); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSbcsCCheck(context, m, n); + EmitSubsVCheck(context, m, n, res); + } + + EmitAluStore(context, res); + } + + public static void Rsb(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(m, n); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, m, res); + EmitSubsVCheck(context, m, n, res); + } + + EmitAluStore(context, res); + } + + public static void Sadd8(ArmEmitterContext context) + { + EmitAddSub8(context, add: true, unsigned: false); + } + + public static void Sbc(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(n, m); + + Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); + + res = context.Subtract(res, borrow); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSbcsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Sbfx(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width. + + Operand n = GetIntA32(context, op.Rn); + Operand res = context.ShiftRightSI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb)); + + SetIntA32(context, op.Rd, res); + } + + public static void Sdiv(ArmEmitterContext context) + { + EmitDiv(context, unsigned: false); + } + + public static void Sel(ArmEmitterContext context) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + Operand ge0 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE0Flag))); + Operand ge1 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE1Flag))); + Operand ge2 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE2Flag))); + Operand ge3 = context.Negate(GetFlag(PState.GE3Flag)); + + Operand mask = context.BitwiseOr(ge0, context.ShiftLeft(ge1, Const(8))); + mask = context.BitwiseOr(mask, context.ShiftLeft(ge2, Const(16))); + mask = context.BitwiseOr(mask, context.ShiftLeft(ge3, Const(24))); + + Operand res = context.BitwiseOr(context.BitwiseAnd(n, mask), context.BitwiseAnd(m, context.BitwiseNot(mask))); + + SetIntA32(context, op.Rd, res); + } + + public static void Shadd8(ArmEmitterContext context) + { + EmitHadd8(context, unsigned: false); + } + + public static void Shsub8(ArmEmitterContext context) + { + EmitHsub8(context, unsigned: false); + } + + public static void Ssat(ArmEmitterContext context) + { + OpCode32Sat op = (OpCode32Sat)context.CurrOp; + + EmitSat(context, -(1 << op.SatImm), (1 << op.SatImm) - 1); + } + + public static void Ssat16(ArmEmitterContext context) + { + OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; + + EmitSat16(context, -(1 << op.SatImm), (1 << op.SatImm) - 1); + } + + public static void Ssub8(ArmEmitterContext context) + { + EmitAddSub8(context, add: false, unsigned: false); + } + + public static void Sub(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, n, res); + EmitSubsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Sxtb(ArmEmitterContext context) + { + EmitSignExtend(context, true, 8); + } + + public static void Sxtb16(ArmEmitterContext context) + { + EmitExtend16(context, true); + } + + public static void Sxth(ArmEmitterContext context) + { + EmitSignExtend(context, true, 16); + } + + public static void Teq(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseExclusiveOr(n, m); + + EmitNZFlagsCheck(context, res); + } + + public static void Tst(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseAnd(n, m); + EmitNZFlagsCheck(context, res); + } + + public static void Uadd8(ArmEmitterContext context) + { + EmitAddSub8(context, add: true, unsigned: true); + } + + public static void Ubfx(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width. + + Operand n = GetIntA32(context, op.Rn); + Operand res = context.ShiftRightUI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb)); + + SetIntA32(context, op.Rd, res); + } + + public static void Udiv(ArmEmitterContext context) + { + EmitDiv(context, unsigned: true); + } + + public static void Uhadd8(ArmEmitterContext context) + { + EmitHadd8(context, unsigned: true); + } + + public static void Uhsub8(ArmEmitterContext context) + { + EmitHsub8(context, unsigned: true); + } + + public static void Usat(ArmEmitterContext context) + { + OpCode32Sat op = (OpCode32Sat)context.CurrOp; + + EmitSat(context, 0, op.SatImm == 32 ? (int)(~0) : (1 << op.SatImm) - 1); + } + + public static void Usat16(ArmEmitterContext context) + { + OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; + + EmitSat16(context, 0, (1 << op.SatImm) - 1); + } + + public static void Usub8(ArmEmitterContext context) + { + EmitAddSub8(context, add: false, unsigned: true); + } + + public static void Uxtb(ArmEmitterContext context) + { + EmitSignExtend(context, false, 8); + } + + public static void Uxtb16(ArmEmitterContext context) + { + EmitExtend16(context, false); + } + + public static void Uxth(ArmEmitterContext context) + { + EmitSignExtend(context, false, 16); + } + + private static void EmitSignExtend(ArmEmitterContext context, bool signed, int bits) + { + IOpCode32AluUx op = (IOpCode32AluUx)context.CurrOp; + + Operand m = GetAluM(context); + Operand res; + + if (op.RotateBits == 0) + { + res = m; + } + else + { + Operand rotate = Const(op.RotateBits); + res = context.RotateRight(m, rotate); + } + + switch (bits) + { + case 8: + res = (signed) ? context.SignExtend8(OperandType.I32, res) : context.ZeroExtend8(OperandType.I32, res); + break; + case 16: + res = (signed) ? context.SignExtend16(OperandType.I32, res) : context.ZeroExtend16(OperandType.I32, res); + break; + } + + if (op.Add) + { + res = context.Add(res, GetAluN(context)); + } + + EmitAluStore(context, res); + } + + private static void EmitExtend16(ArmEmitterContext context, bool signed) + { + IOpCode32AluUx op = (IOpCode32AluUx)context.CurrOp; + + Operand m = GetAluM(context); + Operand res; + + if (op.RotateBits == 0) + { + res = m; + } + else + { + Operand rotate = Const(op.RotateBits); + res = context.RotateRight(m, rotate); + } + + Operand low16, high16; + if (signed) + { + low16 = context.SignExtend8(OperandType.I32, res); + high16 = context.SignExtend8(OperandType.I32, context.ShiftRightUI(res, Const(16))); + } + else + { + low16 = context.ZeroExtend8(OperandType.I32, res); + high16 = context.ZeroExtend8(OperandType.I32, context.ShiftRightUI(res, Const(16))); + } + + if (op.Add) + { + Operand n = GetAluN(context); + Operand lowAdd, highAdd; + if (signed) + { + lowAdd = context.SignExtend16(OperandType.I32, n); + highAdd = context.SignExtend16(OperandType.I32, context.ShiftRightUI(n, Const(16))); + } + else + { + lowAdd = context.ZeroExtend16(OperandType.I32, n); + highAdd = context.ZeroExtend16(OperandType.I32, context.ShiftRightUI(n, Const(16))); + } + + low16 = context.Add(low16, lowAdd); + high16 = context.Add(high16, highAdd); + } + + res = context.BitwiseOr( + context.ZeroExtend16(OperandType.I32, low16), + context.ShiftLeft(context.ZeroExtend16(OperandType.I32, high16), Const(16))); + + EmitAluStore(context, res); + } + + private static void EmitDiv(ArmEmitterContext context, bool unsigned) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + Operand zero = Const(m.Type, 0); + + Operand divisorIsZero = context.ICompareEqual(m, zero); + + Operand lblBadDiv = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBadDiv, divisorIsZero); + + if (!unsigned) + { + // ARM64 behaviour: If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow). + // TODO: tests to ensure A32 works the same + + Operand intMin = Const(int.MinValue); + Operand minus1 = Const(-1); + + Operand nIsIntMin = context.ICompareEqual(n, intMin); + Operand mIsMinus1 = context.ICompareEqual(m, minus1); + + Operand lblGoodDiv = Label(); + + context.BranchIfFalse(lblGoodDiv, context.BitwiseAnd(nIsIntMin, mIsMinus1)); + + EmitAluStore(context, intMin); + + context.Branch(lblEnd); + + context.MarkLabel(lblGoodDiv); + } + + Operand res = unsigned + ? context.DivideUI(n, m) + : context.Divide(n, m); + + EmitAluStore(context, res); + + context.Branch(lblEnd); + + context.MarkLabel(lblBadDiv); + + EmitAluStore(context, zero); + + context.MarkLabel(lblEnd); + } + + private static void EmitAddSub8(ArmEmitterContext context, bool add, bool unsigned) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + Operand res = Const(0); + + for (int byteSel = 0; byteSel < 4; byteSel++) + { + Operand shift = Const(byteSel * 8); + + Operand nByte = context.ShiftRightUI(n, shift); + Operand mByte = context.ShiftRightUI(m, shift); + + nByte = unsigned ? context.ZeroExtend8(OperandType.I32, nByte) : context.SignExtend8(OperandType.I32, nByte); + mByte = unsigned ? context.ZeroExtend8(OperandType.I32, mByte) : context.SignExtend8(OperandType.I32, mByte); + + Operand resByte = add ? context.Add(nByte, mByte) : context.Subtract(nByte, mByte); + + res = context.BitwiseOr(res, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, resByte), shift)); + + SetFlag(context, PState.GE0Flag + byteSel, unsigned && add + ? context.ShiftRightUI(resByte, Const(8)) + : context.ShiftRightUI(context.BitwiseNot(resByte), Const(31))); + } + + SetIntA32(context, op.Rd, res); + } + + private static void EmitHadd8(ArmEmitterContext context, bool unsigned) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand m = GetIntA32(context, op.Rm); + Operand n = GetIntA32(context, op.Rn); + + Operand xor, res, carry; + + // This relies on the equality x+y == ((x&y) << 1) + (x^y). + // Note that x^y always contains the LSB of the result. + // Since we want to calculate (x+y)/2, we can instead calculate (x&y) + ((x^y)>>1). + // We mask by 0x7F to remove the LSB so that it doesn't leak into the field below. + + res = context.BitwiseAnd(m, n); + carry = context.BitwiseExclusiveOr(m, n); + xor = context.ShiftRightUI(carry, Const(1)); + xor = context.BitwiseAnd(xor, Const(0x7F7F7F7Fu)); + res = context.Add(res, xor); + + if (!unsigned) + { + // Propagates the sign bit from (x^y)>>1 upwards by one. + carry = context.BitwiseAnd(carry, Const(0x80808080u)); + res = context.BitwiseExclusiveOr(res, carry); + } + + SetIntA32(context, op.Rd, res); + } + + private static void EmitHsub8(ArmEmitterContext context, bool unsigned) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand m = GetIntA32(context, op.Rm); + Operand n = GetIntA32(context, op.Rn); + Operand left, right, carry, res; + + // This relies on the equality x-y == (x^y) - (((x^y)&y) << 1). + // Note that x^y always contains the LSB of the result. + // Since we want to calculate (x+y)/2, we can instead calculate ((x^y)>>1) - ((x^y)&y). + + carry = context.BitwiseExclusiveOr(m, n); + left = context.ShiftRightUI(carry, Const(1)); + right = context.BitwiseAnd(carry, m); + + // We must now perform a partitioned subtraction. + // We can do this because minuend contains 7 bit fields. + // We use the extra bit in minuend as a bit to borrow from; we set this bit. + // We invert this bit at the end as this tells us if that bit was borrowed from. + + res = context.BitwiseOr(left, Const(0x80808080)); + res = context.Subtract(res, right); + res = context.BitwiseExclusiveOr(res, Const(0x80808080)); + + if (!unsigned) + { + // We then sign extend the result into this bit. + carry = context.BitwiseAnd(carry, Const(0x80808080)); + res = context.BitwiseExclusiveOr(res, carry); + } + + SetIntA32(context, op.Rd, res); + } + + private static void EmitSat(ArmEmitterContext context, int intMin, int intMax) + { + OpCode32Sat op = (OpCode32Sat)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + + int shift = DecodeImmShift(op.ShiftType, op.Imm5); + + switch (op.ShiftType) + { + case ShiftType.Lsl: + if (shift == 32) + { + n = Const(0); + } + else + { + n = context.ShiftLeft(n, Const(shift)); + } + break; + case ShiftType.Asr: + if (shift == 32) + { + n = context.ShiftRightSI(n, Const(31)); + } + else + { + n = context.ShiftRightSI(n, Const(shift)); + } + break; + } + + Operand lblCheckLtIntMin = Label(); + Operand lblNoSat = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblCheckLtIntMin, context.ICompareGreater(n, Const(intMax))); + + SetFlag(context, PState.QFlag, Const(1)); + SetIntA32(context, op.Rd, Const(intMax)); + context.Branch(lblEnd); + + context.MarkLabel(lblCheckLtIntMin); + context.BranchIfFalse(lblNoSat, context.ICompareLess(n, Const(intMin))); + + SetFlag(context, PState.QFlag, Const(1)); + SetIntA32(context, op.Rd, Const(intMin)); + context.Branch(lblEnd); + + context.MarkLabel(lblNoSat); + + SetIntA32(context, op.Rd, n); + + context.MarkLabel(lblEnd); + } + + private static void EmitSat16(ArmEmitterContext context, int intMin, int intMax) + { + OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; + + void SetD(int part, Operand value) + { + if (part == 0) + { + SetIntA32(context, op.Rd, context.ZeroExtend16(OperandType.I32, value)); + } + else + { + SetIntA32(context, op.Rd, context.BitwiseOr(GetIntA32(context, op.Rd), context.ShiftLeft(value, Const(16)))); + } + } + + Operand n = GetIntA32(context, op.Rn); + + Operand nLow = context.SignExtend16(OperandType.I32, n); + Operand nHigh = context.ShiftRightSI(n, Const(16)); + + for (int part = 0; part < 2; part++) + { + Operand nPart = part == 0 ? nLow : nHigh; + + Operand lblCheckLtIntMin = Label(); + Operand lblNoSat = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblCheckLtIntMin, context.ICompareGreater(nPart, Const(intMax))); + + SetFlag(context, PState.QFlag, Const(1)); + SetD(part, Const(intMax)); + context.Branch(lblEnd); + + context.MarkLabel(lblCheckLtIntMin); + context.BranchIfFalse(lblNoSat, context.ICompareLess(nPart, Const(intMin))); + + SetFlag(context, PState.QFlag, Const(1)); + SetD(part, Const(intMin)); + context.Branch(lblEnd); + + context.MarkLabel(lblNoSat); + + SetD(part, nPart); + + context.MarkLabel(lblEnd); + } + } + + private static void EmitAluStore(ArmEmitterContext context, Operand value) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + EmitGenericAluStoreA32(context, op.Rd, ShouldSetFlags(context), value); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitAluHelper.cs b/src/ARMeilleure/Instructions/InstEmitAluHelper.cs new file mode 100644 index 000000000..4d4a31f7b --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitAluHelper.cs @@ -0,0 +1,652 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitAluHelper + { + public static bool ShouldSetFlags(ArmEmitterContext context) + { + IOpCode32HasSetFlags op = (IOpCode32HasSetFlags)context.CurrOp; + + if (op.SetFlags == null) + { + return !context.IsInIfThenBlock; + } + + return op.SetFlags.Value; + } + + public static void EmitNZFlagsCheck(ArmEmitterContext context, Operand d) + { + SetFlag(context, PState.NFlag, context.ICompareLess(d, Const(d.Type, 0))); + SetFlag(context, PState.ZFlag, context.ICompareEqual(d, Const(d.Type, 0))); + } + + public static void EmitAdcsCCheck(ArmEmitterContext context, Operand n, Operand d) + { + // C = (Rd == Rn && CIn) || Rd < Rn + Operand cIn = GetFlag(PState.CFlag); + + Operand cOut = context.BitwiseAnd(context.ICompareEqual(d, n), cIn); + + cOut = context.BitwiseOr(cOut, context.ICompareLessUI(d, n)); + + SetFlag(context, PState.CFlag, cOut); + } + + public static void EmitAddsCCheck(ArmEmitterContext context, Operand n, Operand d) + { + // C = Rd < Rn + SetFlag(context, PState.CFlag, context.ICompareLessUI(d, n)); + } + + public static void EmitAddsVCheck(ArmEmitterContext context, Operand n, Operand m, Operand d) + { + // V = (Rd ^ Rn) & ~(Rn ^ Rm) < 0 + Operand vOut = context.BitwiseExclusiveOr(d, n); + + vOut = context.BitwiseAnd(vOut, context.BitwiseNot(context.BitwiseExclusiveOr(n, m))); + + vOut = context.ICompareLess(vOut, Const(vOut.Type, 0)); + + SetFlag(context, PState.VFlag, vOut); + } + + public static void EmitSbcsCCheck(ArmEmitterContext context, Operand n, Operand m) + { + // C = (Rn == Rm && CIn) || Rn > Rm + Operand cIn = GetFlag(PState.CFlag); + + Operand cOut = context.BitwiseAnd(context.ICompareEqual(n, m), cIn); + + cOut = context.BitwiseOr(cOut, context.ICompareGreaterUI(n, m)); + + SetFlag(context, PState.CFlag, cOut); + } + + public static void EmitSubsCCheck(ArmEmitterContext context, Operand n, Operand m) + { + // C = Rn >= Rm + SetFlag(context, PState.CFlag, context.ICompareGreaterOrEqualUI(n, m)); + } + + public static void EmitSubsVCheck(ArmEmitterContext context, Operand n, Operand m, Operand d) + { + // V = (Rd ^ Rn) & (Rn ^ Rm) < 0 + Operand vOut = context.BitwiseExclusiveOr(d, n); + + vOut = context.BitwiseAnd(vOut, context.BitwiseExclusiveOr(n, m)); + + vOut = context.ICompareLess(vOut, Const(vOut.Type, 0)); + + SetFlag(context, PState.VFlag, vOut); + } + + public static Operand EmitReverseBits32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaau)), Const(1)), + context.ShiftLeft(context.BitwiseAnd(op, Const(0x55555555u)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccu)), Const(2)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x33333333u)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0u)), Const(4)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0f0f0f0fu)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00u)), Const(8)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x00ff00ffu)), Const(8))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(16)), context.ShiftLeft(val, Const(16))); + } + + public static Operand EmitReverseBytes16_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft(context.BitwiseAnd(op, Const(0x00ff00ff00ff00fful)), Const(8))); + } + + public static Operand EmitReverseBytes16_32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = EmitReverseBytes16_64Op(context, context.ZeroExtend32(OperandType.I64, op)); + + return context.ConvertI64ToI32(val); + } + + private static void EmitAluWritePc(ArmEmitterContext context, Operand value) + { + Debug.Assert(value.Type == OperandType.I32); + + if (((OpCode32)context.CurrOp).IsThumb) + { + bool isReturn = IsA32Return(context); + if (!isReturn) + { + context.StoreToContext(); + } + + InstEmitFlowHelper.EmitVirtualJump(context, value, isReturn); + } + else + { + EmitBxWritePc(context, value); + } + } + + public static void EmitGenericAluStoreA32(ArmEmitterContext context, int rd, bool setFlags, Operand value) + { + Debug.Assert(value.Type == OperandType.I32); + + if (rd == RegisterAlias.Aarch32Pc && setFlags) + { + if (setFlags) + { + // TODO: Load SPSR etc. + + EmitBxWritePc(context, value); + } + else + { + EmitAluWritePc(context, value); + } + } + else + { + SetIntA32(context, rd, value); + } + } + + public static Operand GetAluN(ArmEmitterContext context) + { + if (context.CurrOp is IOpCodeAlu op) + { + if (op.DataOp == DataOp.Logical || op is IOpCodeAluRs) + { + return GetIntOrZR(context, op.Rn); + } + else + { + return GetIntOrSP(context, op.Rn); + } + } + else if (context.CurrOp is IOpCode32Alu op32) + { + return GetIntA32(context, op32.Rn); + } + else + { + throw InvalidOpCodeType(context.CurrOp); + } + } + + public static Operand GetAluM(ArmEmitterContext context, bool setCarry = true) + { + switch (context.CurrOp) + { + // ARM32. + case IOpCode32AluImm op: + { + if (ShouldSetFlags(context) && op.IsRotated && setCarry) + { + SetFlag(context, PState.CFlag, Const((uint)op.Immediate >> 31)); + } + + return Const(op.Immediate); + } + + case IOpCode32AluImm16 op: + return Const(op.Immediate); + + case IOpCode32AluRsImm op: + return GetMShiftedByImmediate(context, op, setCarry); + case IOpCode32AluRsReg op: + return GetMShiftedByReg(context, op, setCarry); + + case IOpCode32AluReg op: + return GetIntA32(context, op.Rm); + + // ARM64. + case IOpCodeAluImm op: + { + if (op.GetOperandType() == OperandType.I32) + { + return Const((int)op.Immediate); + } + else + { + return Const(op.Immediate); + } + } + + case IOpCodeAluRs op: + { + Operand value = GetIntOrZR(context, op.Rm); + + switch (op.ShiftType) + { + case ShiftType.Lsl: + value = context.ShiftLeft(value, Const(op.Shift)); + break; + case ShiftType.Lsr: + value = context.ShiftRightUI(value, Const(op.Shift)); + break; + case ShiftType.Asr: + value = context.ShiftRightSI(value, Const(op.Shift)); + break; + case ShiftType.Ror: + value = context.RotateRight(value, Const(op.Shift)); + break; + } + + return value; + } + + case IOpCodeAluRx op: + { + Operand value = GetExtendedM(context, op.Rm, op.IntType); + + value = context.ShiftLeft(value, Const(op.Shift)); + + return value; + } + + default: + throw InvalidOpCodeType(context.CurrOp); + } + } + + private static Exception InvalidOpCodeType(OpCode opCode) + { + return new InvalidOperationException($"Invalid OpCode type \"{opCode?.GetType().Name ?? "null"}\"."); + } + + // ARM32 helpers. + public static Operand GetMShiftedByImmediate(ArmEmitterContext context, IOpCode32AluRsImm op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + + int shift = op.Immediate; + + if (shift == 0) + { + switch (op.ShiftType) + { + case ShiftType.Lsr: + shift = 32; + break; + case ShiftType.Asr: + shift = 32; + break; + case ShiftType.Ror: + shift = 1; + break; + } + } + + if (shift != 0) + { + setCarry &= ShouldSetFlags(context); + + switch (op.ShiftType) + { + case ShiftType.Lsl: + m = GetLslC(context, m, setCarry, shift); + break; + case ShiftType.Lsr: + m = GetLsrC(context, m, setCarry, shift); + break; + case ShiftType.Asr: + m = GetAsrC(context, m, setCarry, shift); + break; + case ShiftType.Ror: + if (op.Immediate != 0) + { + m = GetRorC(context, m, setCarry, shift); + } + else + { + m = GetRrxC(context, m, setCarry); + } + break; + } + } + + return m; + } + + public static int DecodeImmShift(ShiftType shiftType, int shift) + { + if (shift == 0) + { + switch (shiftType) + { + case ShiftType.Lsr: + shift = 32; + break; + case ShiftType.Asr: + shift = 32; + break; + case ShiftType.Ror: + shift = 1; + break; + } + } + + return shift; + } + + public static Operand GetMShiftedByReg(ArmEmitterContext context, IOpCode32AluRsReg op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + Operand s = context.ZeroExtend8(OperandType.I32, GetIntA32(context, op.Rs)); + Operand shiftIsZero = context.ICompareEqual(s, Const(0)); + + Operand zeroResult = m; + Operand shiftResult = m; + + setCarry &= ShouldSetFlags(context); + + switch (op.ShiftType) + { + case ShiftType.Lsl: + shiftResult = EmitLslC(context, m, setCarry, s, shiftIsZero); + break; + case ShiftType.Lsr: + shiftResult = EmitLsrC(context, m, setCarry, s, shiftIsZero); + break; + case ShiftType.Asr: + shiftResult = EmitAsrC(context, m, setCarry, s, shiftIsZero); + break; + case ShiftType.Ror: + shiftResult = EmitRorC(context, m, setCarry, s, shiftIsZero); + break; + } + + return context.ConditionalSelect(shiftIsZero, zeroResult, shiftResult); + } + + public static void EmitIfHelper(ArmEmitterContext context, Operand boolValue, Action action, bool expected = true) + { + Debug.Assert(boolValue.Type == OperandType.I32); + + Operand endLabel = Label(); + + if (expected) + { + context.BranchIfFalse(endLabel, boolValue); + } + else + { + context.BranchIfTrue(endLabel, boolValue); + } + + action(); + + context.MarkLabel(endLabel); + } + + public static Operand EmitLslC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + Operand shiftLarge = context.ICompareGreaterOrEqual(shift, Const(32)); + Operand result = context.ShiftLeft(m, shift); + if (setCarry) + { + EmitIfHelper(context, shiftIsZero, () => + { + Operand cOut = context.ShiftRightUI(m, context.Subtract(Const(32), shift)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + cOut = context.ConditionalSelect(context.ICompareGreater(shift, Const(32)), Const(0), cOut); + + SetFlag(context, PState.CFlag, cOut); + }, false); + } + + return context.ConditionalSelect(shiftLarge, Const(0), result); + } + + public static Operand GetLslC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift > 32) + { + return GetShiftByMoreThan32(context, setCarry); + } + else if (shift == 32) + { + if (setCarry) + { + SetCarryMLsb(context, m); + } + + return Const(0); + } + else + { + if (setCarry) + { + Operand cOut = context.ShiftRightUI(m, Const(32 - shift)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + } + + return context.ShiftLeft(m, Const(shift)); + } + } + + public static Operand EmitLsrC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + Operand shiftLarge = context.ICompareGreaterOrEqual(shift, Const(32)); + Operand result = context.ShiftRightUI(m, shift); + if (setCarry) + { + EmitIfHelper(context, shiftIsZero, () => + { + Operand cOut = context.ShiftRightUI(m, context.Subtract(shift, Const(1))); + + cOut = context.BitwiseAnd(cOut, Const(1)); + cOut = context.ConditionalSelect(context.ICompareGreater(shift, Const(32)), Const(0), cOut); + + SetFlag(context, PState.CFlag, cOut); + }, false); + } + + return context.ConditionalSelect(shiftLarge, Const(0), result); + } + + public static Operand GetLsrC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift > 32) + { + return GetShiftByMoreThan32(context, setCarry); + } + else if (shift == 32) + { + if (setCarry) + { + SetCarryMMsb(context, m); + } + + return Const(0); + } + else + { + if (setCarry) + { + SetCarryMShrOut(context, m, shift); + } + + return context.ShiftRightUI(m, Const(shift)); + } + } + + private static Operand GetShiftByMoreThan32(ArmEmitterContext context, bool setCarry) + { + if (setCarry) + { + SetFlag(context, PState.CFlag, Const(0)); + } + + return Const(0); + } + + public static Operand EmitAsrC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + Operand l32Result; + Operand ge32Result; + + Operand less32 = context.ICompareLess(shift, Const(32)); + + ge32Result = context.ShiftRightSI(m, Const(31)); + + if (setCarry) + { + EmitIfHelper(context, context.BitwiseOr(less32, shiftIsZero), () => + { + SetCarryMLsb(context, ge32Result); + }, false); + } + + l32Result = context.ShiftRightSI(m, shift); + if (setCarry) + { + EmitIfHelper(context, context.BitwiseAnd(less32, context.BitwiseNot(shiftIsZero)), () => + { + Operand cOut = context.ShiftRightUI(m, context.Subtract(shift, Const(1))); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + }); + } + + return context.ConditionalSelect(less32, l32Result, ge32Result); + } + + public static Operand GetAsrC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift >= 32) + { + m = context.ShiftRightSI(m, Const(31)); + + if (setCarry) + { + SetCarryMLsb(context, m); + } + + return m; + } + else + { + if (setCarry) + { + SetCarryMShrOut(context, m, shift); + } + + return context.ShiftRightSI(m, Const(shift)); + } + } + + public static Operand EmitRorC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + shift = context.BitwiseAnd(shift, Const(0x1f)); + m = context.RotateRight(m, shift); + + if (setCarry) + { + EmitIfHelper(context, shiftIsZero, () => + { + SetCarryMMsb(context, m); + }, false); + } + + return m; + } + + public static Operand GetRorC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + shift &= 0x1f; + + m = context.RotateRight(m, Const(shift)); + + if (setCarry) + { + SetCarryMMsb(context, m); + } + + return m; + } + + public static Operand GetRrxC(ArmEmitterContext context, Operand m, bool setCarry) + { + Debug.Assert(m.Type == OperandType.I32); + + // Rotate right by 1 with carry. + Operand cIn = context.Copy(GetFlag(PState.CFlag)); + + if (setCarry) + { + SetCarryMLsb(context, m); + } + + m = context.ShiftRightUI(m, Const(1)); + + m = context.BitwiseOr(m, context.ShiftLeft(cIn, Const(31))); + + return m; + } + + private static void SetCarryMLsb(ArmEmitterContext context, Operand m) + { + Debug.Assert(m.Type == OperandType.I32); + + SetFlag(context, PState.CFlag, context.BitwiseAnd(m, Const(1))); + } + + private static void SetCarryMMsb(ArmEmitterContext context, Operand m) + { + Debug.Assert(m.Type == OperandType.I32); + + SetFlag(context, PState.CFlag, context.ShiftRightUI(m, Const(31))); + } + + private static void SetCarryMShrOut(ArmEmitterContext context, Operand m, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + Operand cOut = context.ShiftRightUI(m, Const(shift - 1)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitBfm.cs b/src/ARMeilleure/Instructions/InstEmitBfm.cs new file mode 100644 index 000000000..aaf228756 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitBfm.cs @@ -0,0 +1,196 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Bfm(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand d = GetIntOrZR(context, op.Rd); + Operand n = GetIntOrZR(context, op.Rn); + + Operand res; + + if (op.Pos < op.Shift) + { + // BFI. + int shift = op.GetBitsCount() - op.Shift; + + int width = op.Pos + 1; + + long mask = (long)(ulong.MaxValue >> (64 - width)); + + res = context.ShiftLeft(context.BitwiseAnd(n, Const(n.Type, mask)), Const(shift)); + + res = context.BitwiseOr(res, context.BitwiseAnd(d, Const(d.Type, ~(mask << shift)))); + } + else + { + // BFXIL. + int shift = op.Shift; + + int width = op.Pos - shift + 1; + + long mask = (long)(ulong.MaxValue >> (64 - width)); + + res = context.BitwiseAnd(context.ShiftRightUI(n, Const(shift)), Const(n.Type, mask)); + + res = context.BitwiseOr(res, context.BitwiseAnd(d, Const(d.Type, ~mask))); + } + + SetIntOrZR(context, op.Rd, res); + } + + public static void Sbfm(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + int bitsCount = op.GetBitsCount(); + + if (op.Pos + 1 == bitsCount) + { + EmitSbfmShift(context); + } + else if (op.Pos < op.Shift) + { + EmitSbfiz(context); + } + else if (op.Pos == 7 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.SignExtend8(n.Type, n)); + } + else if (op.Pos == 15 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.SignExtend16(n.Type, n)); + } + else if (op.Pos == 31 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.SignExtend32(n.Type, n)); + } + else + { + Operand res = GetIntOrZR(context, op.Rn); + + res = context.ShiftLeft(res, Const(bitsCount - 1 - op.Pos)); + res = context.ShiftRightSI(res, Const(bitsCount - 1)); + res = context.BitwiseAnd(res, Const(res.Type, ~op.TMask)); + + Operand n2 = GetBfmN(context); + + SetIntOrZR(context, op.Rd, context.BitwiseOr(res, n2)); + } + } + + public static void Ubfm(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + if (op.Pos + 1 == op.GetBitsCount()) + { + EmitUbfmShift(context); + } + else if (op.Pos < op.Shift) + { + EmitUbfiz(context); + } + else if (op.Pos + 1 == op.Shift) + { + EmitBfmLsl(context); + } + else if (op.Pos == 7 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.BitwiseAnd(n, Const(n.Type, 0xff))); + } + else if (op.Pos == 15 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.BitwiseAnd(n, Const(n.Type, 0xffff))); + } + else + { + SetIntOrZR(context, op.Rd, GetBfmN(context)); + } + } + + private static void EmitSbfiz(ArmEmitterContext context) => EmitBfiz(context, signed: true); + private static void EmitUbfiz(ArmEmitterContext context) => EmitBfiz(context, signed: false); + + private static void EmitBfiz(ArmEmitterContext context, bool signed) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + int width = op.Pos + 1; + + Operand res = GetIntOrZR(context, op.Rn); + + res = context.ShiftLeft(res, Const(op.GetBitsCount() - width)); + + res = signed + ? context.ShiftRightSI(res, Const(op.Shift - width)) + : context.ShiftRightUI(res, Const(op.Shift - width)); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitSbfmShift(ArmEmitterContext context) + { + EmitBfmShift(context, signed: true); + } + + private static void EmitUbfmShift(ArmEmitterContext context) + { + EmitBfmShift(context, signed: false); + } + + private static void EmitBfmShift(ArmEmitterContext context, bool signed) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + res = signed + ? context.ShiftRightSI(res, Const(op.Shift)) + : context.ShiftRightUI(res, Const(op.Shift)); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitBfmLsl(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + int shift = op.GetBitsCount() - op.Shift; + + SetIntOrZR(context, op.Rd, context.ShiftLeft(res, Const(shift))); + } + + private static Operand GetBfmN(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + long mask = op.WMask & op.TMask; + + return context.BitwiseAnd(context.RotateRight(res, Const(op.Shift)), Const(res.Type, mask)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitCcmp.cs b/src/ARMeilleure/Instructions/InstEmitCcmp.cs new file mode 100644 index 000000000..a71fc2689 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitCcmp.cs @@ -0,0 +1,60 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Ccmn(ArmEmitterContext context) => EmitCcmp(context, isNegated: true); + public static void Ccmp(ArmEmitterContext context) => EmitCcmp(context, isNegated: false); + + private static void EmitCcmp(ArmEmitterContext context, bool isNegated) + { + OpCodeCcmp op = (OpCodeCcmp)context.CurrOp; + + Operand lblTrue = Label(); + Operand lblEnd = Label(); + + EmitCondBranch(context, lblTrue, op.Cond); + + SetFlag(context, PState.VFlag, Const((op.Nzcv >> 0) & 1)); + SetFlag(context, PState.CFlag, Const((op.Nzcv >> 1) & 1)); + SetFlag(context, PState.ZFlag, Const((op.Nzcv >> 2) & 1)); + SetFlag(context, PState.NFlag, Const((op.Nzcv >> 3) & 1)); + + context.Branch(lblEnd); + + context.MarkLabel(lblTrue); + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + if (isNegated) + { + Operand d = context.Add(n, m); + + EmitNZFlagsCheck(context, d); + + EmitAddsCCheck(context, n, d); + EmitAddsVCheck(context, n, m, d); + } + else + { + Operand d = context.Subtract(n, m); + + EmitNZFlagsCheck(context, d); + + EmitSubsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, d); + } + + context.MarkLabel(lblEnd); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitCsel.cs b/src/ARMeilleure/Instructions/InstEmitCsel.cs new file mode 100644 index 000000000..1cd936b31 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitCsel.cs @@ -0,0 +1,52 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + private enum CselOperation + { + None, + Increment, + Invert, + Negate, + } + + public static void Csel(ArmEmitterContext context) => EmitCsel(context, CselOperation.None); + public static void Csinc(ArmEmitterContext context) => EmitCsel(context, CselOperation.Increment); + public static void Csinv(ArmEmitterContext context) => EmitCsel(context, CselOperation.Invert); + public static void Csneg(ArmEmitterContext context) => EmitCsel(context, CselOperation.Negate); + + private static void EmitCsel(ArmEmitterContext context, CselOperation cselOp) + { + OpCodeCsel op = (OpCodeCsel)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + if (cselOp == CselOperation.Increment) + { + m = context.Add(m, Const(m.Type, 1)); + } + else if (cselOp == CselOperation.Invert) + { + m = context.BitwiseNot(m); + } + else if (cselOp == CselOperation.Negate) + { + m = context.Negate(m); + } + + Operand condTrue = GetCondTrue(context, op.Cond); + + Operand d = context.ConditionalSelect(condTrue, n, m); + + SetIntOrZR(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitDiv.cs b/src/ARMeilleure/Instructions/InstEmitDiv.cs new file mode 100644 index 000000000..728462ed4 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitDiv.cs @@ -0,0 +1,66 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Sdiv(ArmEmitterContext context) => EmitDiv(context, unsigned: false); + public static void Udiv(ArmEmitterContext context) => EmitDiv(context, unsigned: true); + + private static void EmitDiv(ArmEmitterContext context, bool unsigned) + { + OpCodeAluBinary op = (OpCodeAluBinary)context.CurrOp; + + // If Rm == 0, Rd = 0 (division by zero). + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand divisorIsZero = context.ICompareEqual(m, Const(m.Type, 0)); + + Operand lblBadDiv = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBadDiv, divisorIsZero); + + if (!unsigned) + { + // If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow). + bool is32Bits = op.RegisterSize == RegisterSize.Int32; + + Operand intMin = is32Bits ? Const(int.MinValue) : Const(long.MinValue); + Operand minus1 = is32Bits ? Const(-1) : Const(-1L); + + Operand nIsIntMin = context.ICompareEqual(n, intMin); + Operand mIsMinus1 = context.ICompareEqual(m, minus1); + + Operand lblGoodDiv = Label(); + + context.BranchIfFalse(lblGoodDiv, context.BitwiseAnd(nIsIntMin, mIsMinus1)); + + SetAluDOrZR(context, intMin); + + context.Branch(lblEnd); + + context.MarkLabel(lblGoodDiv); + } + + Operand d = unsigned + ? context.DivideUI(n, m) + : context.Divide(n, m); + + SetAluDOrZR(context, d); + + context.Branch(lblEnd); + + context.MarkLabel(lblBadDiv); + + SetAluDOrZR(context, Const(op.GetOperandType(), 0)); + + context.MarkLabel(lblEnd); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitException.cs b/src/ARMeilleure/Instructions/InstEmitException.cs new file mode 100644 index 000000000..d30fb2fbd --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitException.cs @@ -0,0 +1,55 @@ +using ARMeilleure.Decoders; +using ARMeilleure.Translation; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Brk(ArmEmitterContext context) + { + OpCodeException op = (OpCodeException)context.CurrOp; + + string name = nameof(NativeInterface.Break); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id)); + + context.LoadFromContext(); + + context.Return(Const(op.Address)); + } + + public static void Svc(ArmEmitterContext context) + { + OpCodeException op = (OpCodeException)context.CurrOp; + + string name = nameof(NativeInterface.SupervisorCall); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id)); + + context.LoadFromContext(); + + Translator.EmitSynchronization(context); + } + + public static void Und(ArmEmitterContext context) + { + OpCode op = context.CurrOp; + + string name = nameof(NativeInterface.Undefined); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.RawOpCode)); + + context.LoadFromContext(); + + context.Return(Const(op.Address)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitException32.cs b/src/ARMeilleure/Instructions/InstEmitException32.cs new file mode 100644 index 000000000..57af1522b --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitException32.cs @@ -0,0 +1,39 @@ +using ARMeilleure.Decoders; +using ARMeilleure.Translation; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Svc(ArmEmitterContext context) + { + IOpCode32Exception op = (IOpCode32Exception)context.CurrOp; + + string name = nameof(NativeInterface.SupervisorCall); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id)); + + context.LoadFromContext(); + + Translator.EmitSynchronization(context); + } + + public static void Trap(ArmEmitterContext context) + { + IOpCode32Exception op = (IOpCode32Exception)context.CurrOp; + + string name = nameof(NativeInterface.Break); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id)); + + context.LoadFromContext(); + + context.Return(Const(context.CurrOp.Address)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitFlow.cs b/src/ARMeilleure/Instructions/InstEmitFlow.cs new file mode 100644 index 000000000..a986bf66f --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitFlow.cs @@ -0,0 +1,107 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void B(ArmEmitterContext context) + { + OpCodeBImmAl op = (OpCodeBImmAl)context.CurrOp; + + context.Branch(context.GetLabel((ulong)op.Immediate)); + } + + public static void B_Cond(ArmEmitterContext context) + { + OpCodeBImmCond op = (OpCodeBImmCond)context.CurrOp; + + EmitBranch(context, op.Cond); + } + + public static void Bl(ArmEmitterContext context) + { + OpCodeBImmAl op = (OpCodeBImmAl)context.CurrOp; + + context.Copy(GetIntOrZR(context, RegisterAlias.Lr), Const(op.Address + 4)); + + EmitCall(context, (ulong)op.Immediate); + } + + public static void Blr(ArmEmitterContext context) + { + OpCodeBReg op = (OpCodeBReg)context.CurrOp; + + Operand n = context.Copy(GetIntOrZR(context, op.Rn)); + + context.Copy(GetIntOrZR(context, RegisterAlias.Lr), Const(op.Address + 4)); + + EmitVirtualCall(context, n); + } + + public static void Br(ArmEmitterContext context) + { + OpCodeBReg op = (OpCodeBReg)context.CurrOp; + + EmitVirtualJump(context, GetIntOrZR(context, op.Rn), op.Rn == RegisterAlias.Lr); + } + + public static void Cbnz(ArmEmitterContext context) => EmitCb(context, onNotZero: true); + public static void Cbz(ArmEmitterContext context) => EmitCb(context, onNotZero: false); + + private static void EmitCb(ArmEmitterContext context, bool onNotZero) + { + OpCodeBImmCmp op = (OpCodeBImmCmp)context.CurrOp; + + EmitBranch(context, GetIntOrZR(context, op.Rt), onNotZero); + } + + public static void Ret(ArmEmitterContext context) + { + OpCodeBReg op = (OpCodeBReg)context.CurrOp; + + context.Return(GetIntOrZR(context, op.Rn)); + } + + public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true); + public static void Tbz(ArmEmitterContext context) => EmitTb(context, onNotZero: false); + + private static void EmitTb(ArmEmitterContext context, bool onNotZero) + { + OpCodeBImmTest op = (OpCodeBImmTest)context.CurrOp; + + Operand value = context.BitwiseAnd(GetIntOrZR(context, op.Rt), Const(1L << op.Bit)); + + EmitBranch(context, value, onNotZero); + } + + private static void EmitBranch(ArmEmitterContext context, Condition cond) + { + OpCodeBImm op = (OpCodeBImm)context.CurrOp; + + EmitCondBranch(context, context.GetLabel((ulong)op.Immediate), cond); + } + + private static void EmitBranch(ArmEmitterContext context, Operand value, bool onNotZero) + { + OpCodeBImm op = (OpCodeBImm)context.CurrOp; + + Operand lblTarget = context.GetLabel((ulong)op.Immediate); + + if (onNotZero) + { + context.BranchIfTrue(lblTarget, value); + } + else + { + context.BranchIfFalse(lblTarget, value); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitFlow32.cs b/src/ARMeilleure/Instructions/InstEmitFlow32.cs new file mode 100644 index 000000000..289d3f483 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitFlow32.cs @@ -0,0 +1,136 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void B(ArmEmitterContext context) + { + IOpCode32BImm op = (IOpCode32BImm)context.CurrOp; + + context.Branch(context.GetLabel((ulong)op.Immediate)); + } + + public static void Bl(ArmEmitterContext context) + { + Blx(context, x: false); + } + + public static void Blx(ArmEmitterContext context) + { + Blx(context, x: true); + } + + private static void Blx(ArmEmitterContext context, bool x) + { + IOpCode32BImm op = (IOpCode32BImm)context.CurrOp; + + uint pc = op.GetPc(); + + bool isThumb = ((OpCode32)context.CurrOp).IsThumb; + + uint currentPc = isThumb + ? pc | 1 + : pc - 4; + + SetIntA32(context, GetBankedRegisterAlias(context.Mode, RegisterAlias.Aarch32Lr), Const(currentPc)); + + // If x is true, then this is a branch with link and exchange. + // In this case we need to swap the mode between Arm <-> Thumb. + if (x) + { + SetFlag(context, PState.TFlag, Const(isThumb ? 0 : 1)); + } + + EmitCall(context, (ulong)op.Immediate); + } + + public static void Blxr(ArmEmitterContext context) + { + IOpCode32BReg op = (IOpCode32BReg)context.CurrOp; + + uint pc = op.GetPc(); + + Operand addr = context.Copy(GetIntA32(context, op.Rm)); + Operand bitOne = context.BitwiseAnd(addr, Const(1)); + + bool isThumb = ((OpCode32)context.CurrOp).IsThumb; + + uint currentPc = isThumb + ? (pc - 2) | 1 + : pc - 4; + + SetIntA32(context, GetBankedRegisterAlias(context.Mode, RegisterAlias.Aarch32Lr), Const(currentPc)); + + SetFlag(context, PState.TFlag, bitOne); + + EmitBxWritePc(context, addr); + } + + public static void Bx(ArmEmitterContext context) + { + IOpCode32BReg op = (IOpCode32BReg)context.CurrOp; + + EmitBxWritePc(context, GetIntA32(context, op.Rm), op.Rm); + } + + public static void Cbnz(ArmEmitterContext context) => EmitCb(context, onNotZero: true); + public static void Cbz(ArmEmitterContext context) => EmitCb(context, onNotZero: false); + + private static void EmitCb(ArmEmitterContext context, bool onNotZero) + { + OpCodeT16BImmCmp op = (OpCodeT16BImmCmp)context.CurrOp; + + Operand value = GetIntA32(context, op.Rn); + Operand lblTarget = context.GetLabel((ulong)op.Immediate); + + if (onNotZero) + { + context.BranchIfTrue(lblTarget, value); + } + else + { + context.BranchIfFalse(lblTarget, value); + } + } + + public static void It(ArmEmitterContext context) + { + OpCodeT16IfThen op = (OpCodeT16IfThen)context.CurrOp; + + context.SetIfThenBlockState(op.IfThenBlockConds); + } + + public static void Tbb(ArmEmitterContext context) => EmitTb(context, halfword: false); + public static void Tbh(ArmEmitterContext context) => EmitTb(context, halfword: true); + + private static void EmitTb(ArmEmitterContext context, bool halfword) + { + OpCodeT32Tb op = (OpCodeT32Tb)context.CurrOp; + + Operand halfwords; + + if (halfword) + { + Operand address = context.Add(GetIntA32(context, op.Rn), context.ShiftLeft(GetIntA32(context, op.Rm), Const(1))); + halfwords = InstEmitMemoryHelper.EmitReadInt(context, address, 1); + } + else + { + Operand address = context.Add(GetIntA32(context, op.Rn), GetIntA32(context, op.Rm)); + halfwords = InstEmitMemoryHelper.EmitReadIntAligned(context, address, 0); + } + + Operand targetAddress = context.Add(Const((int)op.GetPc()), context.ShiftLeft(halfwords, Const(1))); + + EmitVirtualJump(context, targetAddress, isReturn: false); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs new file mode 100644 index 000000000..2009bafda --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -0,0 +1,240 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using ARMeilleure.Translation.PTC; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitFlowHelper + { + public static void EmitCondBranch(ArmEmitterContext context, Operand target, Condition cond) + { + if (cond != Condition.Al) + { + context.BranchIfTrue(target, GetCondTrue(context, cond)); + } + else + { + context.Branch(target); + } + } + + public static Operand GetCondTrue(ArmEmitterContext context, Condition condition) + { + Operand cmpResult = context.TryGetComparisonResult(condition); + + if (cmpResult != default) + { + return cmpResult; + } + + Operand value = Const(1); + + Operand Inverse(Operand val) + { + return context.BitwiseExclusiveOr(val, Const(1)); + } + + switch (condition) + { + case Condition.Eq: + value = GetFlag(PState.ZFlag); + break; + + case Condition.Ne: + value = Inverse(GetFlag(PState.ZFlag)); + break; + + case Condition.GeUn: + value = GetFlag(PState.CFlag); + break; + + case Condition.LtUn: + value = Inverse(GetFlag(PState.CFlag)); + break; + + case Condition.Mi: + value = GetFlag(PState.NFlag); + break; + + case Condition.Pl: + value = Inverse(GetFlag(PState.NFlag)); + break; + + case Condition.Vs: + value = GetFlag(PState.VFlag); + break; + + case Condition.Vc: + value = Inverse(GetFlag(PState.VFlag)); + break; + + case Condition.GtUn: + { + Operand c = GetFlag(PState.CFlag); + Operand z = GetFlag(PState.ZFlag); + + value = context.BitwiseAnd(c, Inverse(z)); + + break; + } + + case Condition.LeUn: + { + Operand c = GetFlag(PState.CFlag); + Operand z = GetFlag(PState.ZFlag); + + value = context.BitwiseOr(Inverse(c), z); + + break; + } + + case Condition.Ge: + { + Operand n = GetFlag(PState.NFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.ICompareEqual(n, v); + + break; + } + + case Condition.Lt: + { + Operand n = GetFlag(PState.NFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.ICompareNotEqual(n, v); + + break; + } + + case Condition.Gt: + { + Operand n = GetFlag(PState.NFlag); + Operand z = GetFlag(PState.ZFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.BitwiseAnd(Inverse(z), context.ICompareEqual(n, v)); + + break; + } + + case Condition.Le: + { + Operand n = GetFlag(PState.NFlag); + Operand z = GetFlag(PState.ZFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.BitwiseOr(z, context.ICompareNotEqual(n, v)); + + break; + } + } + + return value; + } + + public static void EmitCall(ArmEmitterContext context, ulong immediate) + { + bool isRecursive = immediate == context.EntryAddress; + + if (isRecursive) + { + context.Branch(context.GetLabel(immediate)); + } + else + { + EmitTableBranch(context, Const(immediate), isJump: false); + } + } + + public static void EmitVirtualCall(ArmEmitterContext context, Operand target) + { + EmitTableBranch(context, target, isJump: false); + } + + public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn) + { + if (isReturn) + { + if (target.Type == OperandType.I32) + { + target = context.ZeroExtend32(OperandType.I64, target); + } + + context.Return(target); + } + else + { + EmitTableBranch(context, target, isJump: true); + } + } + + private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump) + { + context.StoreToContext(); + + if (guestAddress.Type == OperandType.I32) + { + guestAddress = context.ZeroExtend32(OperandType.I64, guestAddress); + } + + // Store the target guest address into the native context. The stubs uses this address to dispatch into the + // next translation. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand dispAddressAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())); + context.Store(dispAddressAddr, guestAddress); + + Operand hostAddress; + + // If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback + // onto the dispatch stub. + if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value)) + { + Operand hostAddressAddr = !context.HasPtc ? + Const(ref context.FunctionTable.GetValue(guestAddress.Value)) : + Const(ref context.FunctionTable.GetValue(guestAddress.Value), new Symbol(SymbolType.FunctionTable, guestAddress.Value)); + + hostAddress = context.Load(OperandType.I64, hostAddressAddr); + } + else + { + hostAddress = !context.HasPtc ? + Const((long)context.Stubs.DispatchStub) : + Const((long)context.Stubs.DispatchStub, Ptc.DispatchStubSymbol); + } + + if (isJump) + { + context.Tailcall(hostAddress, nativeContext); + } + else + { + OpCode op = context.CurrOp; + + Operand returnAddress = context.Call(hostAddress, OperandType.I64, nativeContext); + + context.LoadFromContext(); + + // Note: The return value of a translated function is always an Int64 with the address execution has + // returned to. We expect this address to be immediately after the current instruction, if it isn't we + // keep returning until we reach the dispatcher. + Operand nextAddr = Const((long)op.Address + op.OpCodeSizeInBytes); + + // Try to continue within this block. + // If the return address isn't to our next instruction, we need to return so the JIT can figure out + // what to do. + Operand lblContinue = context.GetLabel(nextAddr.Value); + context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold); + + context.Return(returnAddress); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHash.cs b/src/ARMeilleure/Instructions/InstEmitHash.cs new file mode 100644 index 000000000..82b3e3534 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHash.cs @@ -0,0 +1,69 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHashHelper; +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + private const int ByteSizeLog2 = 0; + private const int HWordSizeLog2 = 1; + private const int WordSizeLog2 = 2; + private const int DWordSizeLog2 = 3; + + 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 Crc32x(ArmEmitterContext context) + { + EmitCrc32Call(context, DWordSizeLog2, 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); + } + + public static void Crc32cx(ArmEmitterContext context) + { + EmitCrc32Call(context, DWordSizeLog2, true); + } + + private static void EmitCrc32Call(ArmEmitterContext context, int size, bool c) + { + OpCodeAluBinary op = (OpCodeAluBinary)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = EmitCrc32(context, n, m, size, c); + + SetIntOrZR(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHash32.cs b/src/ARMeilleure/Instructions/InstEmitHash32.cs new file mode 100644 index 000000000..30c893a78 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHash32.cs @@ -0,0 +1,53 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitHashHelper; +using static ARMeilleure.Instructions.InstEmitHelper; + +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); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHashHelper.cs b/src/ARMeilleure/Instructions/InstEmitHashHelper.cs new file mode 100644 index 000000000..9b1ad8721 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHashHelper.cs @@ -0,0 +1,124 @@ +// 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.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +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); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHelper.cs b/src/ARMeilleure/Instructions/InstEmitHelper.cs new file mode 100644 index 000000000..7a515f94f --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHelper.cs @@ -0,0 +1,249 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitHelper + { + public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type) + { + Operand value = GetIntOrZR(context, rm); + + switch (type) + { + case IntType.UInt8: + value = context.ZeroExtend8(value.Type, value); + break; + case IntType.UInt16: + value = context.ZeroExtend16(value.Type, value); + break; + case IntType.UInt32: + value = context.ZeroExtend32(value.Type, value); + break; + + case IntType.Int8: + value = context.SignExtend8(value.Type, value); + break; + case IntType.Int16: + value = context.SignExtend16(value.Type, value); + break; + case IntType.Int32: + value = context.SignExtend32(value.Type, value); + break; + } + + return value; + } + + public static Operand GetIntA32(ArmEmitterContext context, int regIndex) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + OpCode32 op = (OpCode32)context.CurrOp; + + return Const((int)op.GetPc()); + } + else + { + return Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32); + } + } + + public static Operand GetIntA32AlignedPC(ArmEmitterContext context, int regIndex) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + OpCode32 op = (OpCode32)context.CurrOp; + + return Const((int)(op.GetPc() & 0xfffffffc)); + } + else + { + return Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32); + } + } + + public static Operand GetVecA32(int regIndex) + { + return Register(regIndex, RegisterType.Vector, OperandType.V128); + } + + public static void SetIntA32(ArmEmitterContext context, int regIndex, Operand value) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + if (!IsA32Return(context)) + { + context.StoreToContext(); + } + + EmitBxWritePc(context, value); + } + else + { + if (value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + Operand reg = Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32); + + context.Copy(reg, value); + } + } + + public static int GetRegisterAlias(Aarch32Mode mode, int regIndex) + { + // Only registers >= 8 are banked, + // with registers in the range [8, 12] being + // banked for the FIQ mode, and registers + // 13 and 14 being banked for all modes. + if ((uint)regIndex < 8) + { + return regIndex; + } + + return GetBankedRegisterAlias(mode, regIndex); + } + + public static int GetBankedRegisterAlias(Aarch32Mode mode, int regIndex) + { + return regIndex switch + { +#pragma warning disable IDE0055 // Disable formatting + 8 => mode == Aarch32Mode.Fiq ? RegisterAlias.R8Fiq : RegisterAlias.R8Usr, + 9 => mode == Aarch32Mode.Fiq ? RegisterAlias.R9Fiq : RegisterAlias.R9Usr, + 10 => mode == Aarch32Mode.Fiq ? RegisterAlias.R10Fiq : RegisterAlias.R10Usr, + 11 => mode == Aarch32Mode.Fiq ? RegisterAlias.R11Fiq : RegisterAlias.R11Usr, + 12 => mode == Aarch32Mode.Fiq ? RegisterAlias.R12Fiq : RegisterAlias.R12Usr, + 13 => mode switch + { + Aarch32Mode.User or Aarch32Mode.System => RegisterAlias.SpUsr, + Aarch32Mode.Fiq => RegisterAlias.SpFiq, + Aarch32Mode.Irq => RegisterAlias.SpIrq, + Aarch32Mode.Supervisor => RegisterAlias.SpSvc, + Aarch32Mode.Abort => RegisterAlias.SpAbt, + Aarch32Mode.Hypervisor => RegisterAlias.SpHyp, + Aarch32Mode.Undefined => RegisterAlias.SpUnd, + _ => throw new ArgumentException($"No such AArch32Mode: {mode}", nameof(mode)), + }, + 14 => mode switch + { + Aarch32Mode.User or Aarch32Mode.Hypervisor or Aarch32Mode.System => RegisterAlias.LrUsr, + Aarch32Mode.Fiq => RegisterAlias.LrFiq, + Aarch32Mode.Irq => RegisterAlias.LrIrq, + Aarch32Mode.Supervisor => RegisterAlias.LrSvc, + Aarch32Mode.Abort => RegisterAlias.LrAbt, + Aarch32Mode.Undefined => RegisterAlias.LrUnd, + _ => throw new ArgumentException($"No such AArch32Mode: {mode}", nameof(mode)), + }, + _ => throw new ArgumentOutOfRangeException(nameof(regIndex), regIndex, null), +#pragma warning restore IDE0055 + }; + } + + public static bool IsA32Return(ArmEmitterContext context) + { + return context.CurrOp switch + { + IOpCode32MemMult => true, // Setting PC using LDM is nearly always a return. + OpCode32AluRsImm op => op.Rm == RegisterAlias.Aarch32Lr, + OpCode32AluRsReg op => op.Rm == RegisterAlias.Aarch32Lr, + OpCode32AluReg op => op.Rm == RegisterAlias.Aarch32Lr, + OpCode32Mem op => op.Rn == RegisterAlias.Aarch32Sp && op.WBack && !op.Index, // Setting PC to an address stored on the stack is nearly always a return. + _ => false, + }; + } + + public static void EmitBxWritePc(ArmEmitterContext context, Operand pc, int sourceRegister = 0) + { + bool isReturn = sourceRegister == RegisterAlias.Aarch32Lr || IsA32Return(context); + Operand mode = context.BitwiseAnd(pc, Const(1)); + + SetFlag(context, PState.TFlag, mode); + + Operand addr = context.ConditionalSelect(mode, context.BitwiseAnd(pc, Const(~1)), context.BitwiseAnd(pc, Const(~3))); + + InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn); + } + + public static Operand GetIntOrZR(ArmEmitterContext context, int regIndex) + { + if (regIndex == RegisterConsts.ZeroIndex) + { + OperandType type = context.CurrOp.GetOperandType(); + + return type == OperandType.I32 ? Const(0) : Const(0L); + } + else + { + return GetIntOrSP(context, regIndex); + } + } + + public static void SetIntOrZR(ArmEmitterContext context, int regIndex, Operand value) + { + if (regIndex == RegisterConsts.ZeroIndex) + { + return; + } + + SetIntOrSP(context, regIndex, value); + } + + public static Operand GetIntOrSP(ArmEmitterContext context, int regIndex) + { + Operand value = Register(regIndex, RegisterType.Integer, OperandType.I64); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + value = context.ConvertI64ToI32(value); + } + + return value; + } + + public static void SetIntOrSP(ArmEmitterContext context, int regIndex, Operand value) + { + Operand reg = Register(regIndex, RegisterType.Integer, OperandType.I64); + + if (value.Type == OperandType.I32) + { + value = context.ZeroExtend32(OperandType.I64, value); + } + + context.Copy(reg, value); + } + + public static Operand GetVec(int regIndex) + { + return Register(regIndex, RegisterType.Vector, OperandType.V128); + } + + public static Operand GetFlag(PState stateFlag) + { + return Register((int)stateFlag, RegisterType.Flag, OperandType.I32); + } + + public static Operand GetFpFlag(FPState stateFlag) + { + return Register((int)stateFlag, RegisterType.FpFlag, OperandType.I32); + } + + public static void SetFlag(ArmEmitterContext context, PState stateFlag, Operand value) + { + context.Copy(GetFlag(stateFlag), value); + + context.MarkFlagSet(stateFlag); + } + + public static void SetFpFlag(ArmEmitterContext context, FPState stateFlag, Operand value) + { + context.Copy(GetFpFlag(stateFlag), value); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemory.cs b/src/ARMeilleure/Instructions/InstEmitMemory.cs new file mode 100644 index 000000000..840099f9c --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemory.cs @@ -0,0 +1,184 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Adr(ArmEmitterContext context) + { + OpCodeAdr op = (OpCodeAdr)context.CurrOp; + + SetIntOrZR(context, op.Rd, Const(op.Address + (ulong)op.Immediate)); + } + + public static void Adrp(ArmEmitterContext context) + { + OpCodeAdr op = (OpCodeAdr)context.CurrOp; + + ulong address = (op.Address & ~0xfffUL) + ((ulong)op.Immediate << 12); + + SetIntOrZR(context, op.Rd, Const(address)); + } + + public static void Ldr(ArmEmitterContext context) => EmitLdr(context, signed: false); + public static void Ldrs(ArmEmitterContext context) => EmitLdr(context, signed: true); + + private static void EmitLdr(ArmEmitterContext context, bool signed) + { + OpCodeMem op = (OpCodeMem)context.CurrOp; + + Operand address = GetAddress(context); + + if (signed && op.Extend64) + { + EmitLoadSx64(context, address, op.Rt, op.Size); + } + else if (signed) + { + EmitLoadSx32(context, address, op.Rt, op.Size); + } + else + { + EmitLoadZx(context, address, op.Rt, op.Size); + } + + EmitWBackIfNeeded(context, address); + } + + public static void Ldr_Literal(ArmEmitterContext context) + { + IOpCodeLit op = (IOpCodeLit)context.CurrOp; + + if (op.Prefetch) + { + return; + } + + if (op.Signed) + { + EmitLoadSx64(context, Const(op.Immediate), op.Rt, op.Size); + } + else + { + EmitLoadZx(context, Const(op.Immediate), op.Rt, op.Size); + } + } + + public static void Ldp(ArmEmitterContext context) + { + OpCodeMemPair op = (OpCodeMemPair)context.CurrOp; + + void EmitLoad(int rt, Operand ldAddr) + { + if (op.Extend64) + { + EmitLoadSx64(context, ldAddr, rt, op.Size); + } + else + { + EmitLoadZx(context, ldAddr, rt, op.Size); + } + } + + Operand address = GetAddress(context); + Operand address2 = GetAddress(context, 1L << op.Size); + + EmitLoad(op.Rt, address); + EmitLoad(op.Rt2, address2); + + EmitWBackIfNeeded(context, address); + } + + public static void Str(ArmEmitterContext context) + { + OpCodeMem op = (OpCodeMem)context.CurrOp; + + Operand address = GetAddress(context); + + EmitStore(context, address, op.Rt, op.Size); + + EmitWBackIfNeeded(context, address); + } + + public static void Stp(ArmEmitterContext context) + { + OpCodeMemPair op = (OpCodeMemPair)context.CurrOp; + + Operand address = GetAddress(context); + Operand address2 = GetAddress(context, 1L << op.Size); + + EmitStore(context, address, op.Rt, op.Size); + EmitStore(context, address2, op.Rt2, op.Size); + + EmitWBackIfNeeded(context, address); + } + + private static Operand GetAddress(ArmEmitterContext context, long addend = 0) + { + Operand address = default; + + switch (context.CurrOp) + { + case OpCodeMemImm op: + { + address = context.Copy(GetIntOrSP(context, op.Rn)); + + // Pre-indexing. + if (!op.PostIdx) + { + address = context.Add(address, Const(op.Immediate + addend)); + } + else if (addend != 0) + { + address = context.Add(address, Const(addend)); + } + + break; + } + + case OpCodeMemReg op: + { + Operand n = GetIntOrSP(context, op.Rn); + + Operand m = GetExtendedM(context, op.Rm, op.IntType); + + if (op.Shift) + { + m = context.ShiftLeft(m, Const(op.Size)); + } + + address = context.Add(n, m); + + if (addend != 0) + { + address = context.Add(address, Const(addend)); + } + + break; + } + } + + return address; + } + + private static void EmitWBackIfNeeded(ArmEmitterContext context, Operand address) + { + // Check whenever the current OpCode has post-indexed write back, if so write it. + if (context.CurrOp is OpCodeMemImm op && op.WBack) + { + if (op.PostIdx) + { + address = context.Add(address, Const(op.Immediate)); + } + + SetIntOrSP(context, op.Rn, address); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemory32.cs b/src/ARMeilleure/Instructions/InstEmitMemory32.cs new file mode 100644 index 000000000..cee06700d --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemory32.cs @@ -0,0 +1,264 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + private const int ByteSizeLog2 = 0; + private const int HWordSizeLog2 = 1; + private const int WordSizeLog2 = 2; + private const int DWordSizeLog2 = 3; + + [Flags] + enum AccessType + { + Store = 0, + Signed = 1, + Load = 2, + Ordered = 4, + Exclusive = 8, + + LoadZx = Load, + LoadSx = Load | Signed, + } + + public static void Ldm(ArmEmitterContext context) + { + IOpCode32MemMult op = (IOpCode32MemMult)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + bool writesToPc = (op.RegisterMask & (1 << RegisterAlias.Aarch32Pc)) != 0; + + bool writeBack = op.PostOffset != 0 && (op.Rn != RegisterAlias.Aarch32Pc || !writesToPc); + + if (writeBack) + { + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + int mask = op.RegisterMask; + int offset = 0; + + for (int register = 0; mask != 0; mask >>= 1, register++) + { + if ((mask & 1) != 0) + { + Operand address = context.Add(baseAddress, Const(offset)); + + EmitLoadZx(context, address, register, WordSizeLog2); + + offset += 4; + } + } + } + + public static void Ldr(ArmEmitterContext context) + { + EmitLoadOrStore(context, WordSizeLog2, AccessType.LoadZx); + } + + public static void Ldrb(ArmEmitterContext context) + { + EmitLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx); + } + + public static void Ldrd(ArmEmitterContext context) + { + EmitLoadOrStore(context, DWordSizeLog2, AccessType.LoadZx); + } + + public static void Ldrh(ArmEmitterContext context) + { + EmitLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx); + } + + public static void Ldrsb(ArmEmitterContext context) + { + EmitLoadOrStore(context, ByteSizeLog2, AccessType.LoadSx); + } + + public static void Ldrsh(ArmEmitterContext context) + { + EmitLoadOrStore(context, HWordSizeLog2, AccessType.LoadSx); + } + + public static void Stm(ArmEmitterContext context) + { + IOpCode32MemMult op = (IOpCode32MemMult)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + int mask = op.RegisterMask; + int offset = 0; + + for (int register = 0; mask != 0; mask >>= 1, register++) + { + if ((mask & 1) != 0) + { + Operand address = context.Add(baseAddress, Const(offset)); + + EmitStore(context, address, register, WordSizeLog2); + + // Note: If Rn is also specified on the register list, + // and Rn is the first register on this list, then the + // value that is written to memory is the unmodified value, + // before the write back. If it is on the list, but it's + // not the first one, then the value written to memory + // varies between CPUs. + if (offset == 0 && op.PostOffset != 0) + { + // Emit write back after the first write. + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + offset += 4; + } + } + } + + public static void Str(ArmEmitterContext context) + { + EmitLoadOrStore(context, WordSizeLog2, AccessType.Store); + } + + public static void Strb(ArmEmitterContext context) + { + EmitLoadOrStore(context, ByteSizeLog2, AccessType.Store); + } + + public static void Strd(ArmEmitterContext context) + { + EmitLoadOrStore(context, DWordSizeLog2, AccessType.Store); + } + + public static void Strh(ArmEmitterContext context) + { + EmitLoadOrStore(context, HWordSizeLog2, AccessType.Store); + } + + private static void EmitLoadOrStore(ArmEmitterContext context, int size, AccessType accType) + { + IOpCode32Mem op = (IOpCode32Mem)context.CurrOp; + + Operand n = context.Copy(GetIntA32AlignedPC(context, op.Rn)); + Operand m = GetMemM(context, setCarry: false); + + Operand temp = default; + + if (op.Index || op.WBack) + { + temp = op.Add + ? context.Add(n, m) + : context.Subtract(n, m); + } + + if (op.WBack) + { + SetIntA32(context, op.Rn, temp); + } + + Operand address; + + if (op.Index) + { + address = temp; + } + else + { + address = n; + } + + if ((accType & AccessType.Load) != 0) + { + void Load(int rt, int offs, int loadSize) + { + Operand addr = context.Add(address, Const(offs)); + + if ((accType & AccessType.Signed) != 0) + { + EmitLoadSx32(context, addr, rt, loadSize); + } + else + { + EmitLoadZx(context, addr, rt, loadSize); + } + } + + if (size == DWordSizeLog2) + { + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + Load(op.Rt, 0, WordSizeLog2); + Load(op.Rt2, 4, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Load(op.Rt2, 0, WordSizeLog2); + Load(op.Rt, 4, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + else + { + Load(op.Rt, 0, size); + } + } + else + { + void Store(int rt, int offs, int storeSize) + { + Operand addr = context.Add(address, Const(offs)); + + EmitStore(context, addr, rt, storeSize); + } + + if (size == DWordSizeLog2) + { + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + Store(op.Rt, 0, WordSizeLog2); + Store(op.Rt2, 4, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Store(op.Rt2, 0, WordSizeLog2); + Store(op.Rt, 4, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + else + { + Store(op.Rt, 0, size); + } + } + } + + public static void Adr(ArmEmitterContext context) + { + IOpCode32Adr op = (IOpCode32Adr)context.CurrOp; + SetIntA32(context, op.Rd, Const(op.Immediate)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryEx.cs b/src/ARMeilleure/Instructions/InstEmitMemoryEx.cs new file mode 100644 index 000000000..8c95b33c5 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryEx.cs @@ -0,0 +1,177 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryExHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + [Flags] + private enum AccessType + { + None = 0, + Ordered = 1, + Exclusive = 2, + OrderedEx = Ordered | Exclusive, + } + + public static void Clrex(ArmEmitterContext context) + { + EmitClearExclusive(context); + } + + public static void Csdb(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Dmb(ArmEmitterContext context) => EmitBarrier(context); + public static void Dsb(ArmEmitterContext context) => EmitBarrier(context); + + public static void Ldar(ArmEmitterContext context) => EmitLdr(context, AccessType.Ordered); + public static void Ldaxr(ArmEmitterContext context) => EmitLdr(context, AccessType.OrderedEx); + public static void Ldxr(ArmEmitterContext context) => EmitLdr(context, AccessType.Exclusive); + public static void Ldxp(ArmEmitterContext context) => EmitLdp(context, AccessType.Exclusive); + public static void Ldaxp(ArmEmitterContext context) => EmitLdp(context, AccessType.OrderedEx); + + private static void EmitLdr(ArmEmitterContext context, AccessType accType) + { + EmitLoadEx(context, accType, pair: false); + } + + private static void EmitLdp(ArmEmitterContext context, AccessType accType) + { + EmitLoadEx(context, accType, pair: true); + } + + private static void EmitLoadEx(ArmEmitterContext context, AccessType accType, bool pair) + { + OpCodeMemEx op = (OpCodeMemEx)context.CurrOp; + + bool ordered = (accType & AccessType.Ordered) != 0; + bool exclusive = (accType & AccessType.Exclusive) != 0; + + if (ordered) + { + EmitBarrier(context); + } + + Operand address = context.Copy(GetIntOrSP(context, op.Rn)); + + if (pair) + { + // Exclusive loads should be atomic. For pairwise loads, we need to + // read all the data at once. For a 32-bits pairwise load, we do a + // simple 64-bits load, for a 128-bits load, we need to call a special + // method to read 128-bits atomically. + if (op.Size == 2) + { + Operand value = EmitLoadExclusive(context, address, exclusive, 3); + + Operand valueLow = context.ConvertI64ToI32(value); + + valueLow = context.ZeroExtend32(OperandType.I64, valueLow); + + Operand valueHigh = context.ShiftRightUI(value, Const(32)); + + SetIntOrZR(context, op.Rt, valueLow); + SetIntOrZR(context, op.Rt2, valueHigh); + } + else if (op.Size == 3) + { + Operand value = EmitLoadExclusive(context, address, exclusive, 4); + + Operand valueLow = context.VectorExtract(OperandType.I64, value, 0); + Operand valueHigh = context.VectorExtract(OperandType.I64, value, 1); + + SetIntOrZR(context, op.Rt, valueLow); + SetIntOrZR(context, op.Rt2, valueHigh); + } + else + { + throw new InvalidOperationException($"Invalid load size of {1 << op.Size} bytes."); + } + } + else + { + // 8, 16, 32 or 64-bits (non-pairwise) load. + Operand value = EmitLoadExclusive(context, address, exclusive, op.Size); + + SetIntOrZR(context, op.Rt, value); + } + } + + public static void Prfm(ArmEmitterContext context) + { + // Memory Prefetch, execute as no-op. + } + + public static void Stlr(ArmEmitterContext context) => EmitStr(context, AccessType.Ordered); + public static void Stlxr(ArmEmitterContext context) => EmitStr(context, AccessType.OrderedEx); + public static void Stxr(ArmEmitterContext context) => EmitStr(context, AccessType.Exclusive); + public static void Stxp(ArmEmitterContext context) => EmitStp(context, AccessType.Exclusive); + public static void Stlxp(ArmEmitterContext context) => EmitStp(context, AccessType.OrderedEx); + + private static void EmitStr(ArmEmitterContext context, AccessType accType) + { + EmitStoreEx(context, accType, pair: false); + } + + private static void EmitStp(ArmEmitterContext context, AccessType accType) + { + EmitStoreEx(context, accType, pair: true); + } + + private static void EmitStoreEx(ArmEmitterContext context, AccessType accType, bool pair) + { + OpCodeMemEx op = (OpCodeMemEx)context.CurrOp; + + bool ordered = (accType & AccessType.Ordered) != 0; + bool exclusive = (accType & AccessType.Exclusive) != 0; + + Operand address = context.Copy(GetIntOrSP(context, op.Rn)); + + Operand t = GetIntOrZR(context, op.Rt); + + if (pair) + { + Debug.Assert(op.Size == 2 || op.Size == 3, "Invalid size for pairwise store."); + + Operand t2 = GetIntOrZR(context, op.Rt2); + + Operand value; + + if (op.Size == 2) + { + value = context.BitwiseOr(t, context.ShiftLeft(t2, Const(32))); + } + else /* if (op.Size == 3) */ + { + value = context.VectorInsert(context.VectorZero(), t, 0); + value = context.VectorInsert(value, t2, 1); + } + + EmitStoreExclusive(context, address, value, exclusive, op.Size + 1, op.Rs, a32: false); + } + else + { + EmitStoreExclusive(context, address, t, exclusive, op.Size, op.Rs, a32: false); + } + + if (ordered) + { + EmitBarrier(context); + } + } + + private static void EmitBarrier(ArmEmitterContext context) + { + context.MemoryBarrier(); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs b/src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs new file mode 100644 index 000000000..150218827 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs @@ -0,0 +1,237 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryExHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Clrex(ArmEmitterContext context) + { + EmitClearExclusive(context); + } + + public static void Csdb(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Dmb(ArmEmitterContext context) => EmitBarrier(context); + + public static void Dsb(ArmEmitterContext context) => EmitBarrier(context); + + public static void Ldrex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Ldrexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Ldrexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Ldrexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Lda(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.LoadZx | AccessType.Ordered); + } + + public static void Ldab(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx | AccessType.Ordered); + } + + public static void Ldaex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldaexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldaexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldaexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldah(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx | AccessType.Ordered); + } + + // Stores. + + public static void Strex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Strexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Strexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Strexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Stl(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.Store | AccessType.Ordered); + } + + public static void Stlb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.Store | AccessType.Ordered); + } + + public static void Stlex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.Store | AccessType.Ordered); + } + + private static void EmitExLoadOrStore(ArmEmitterContext context, int size, AccessType accType) + { + IOpCode32MemEx op = (IOpCode32MemEx)context.CurrOp; + + Operand address = context.Copy(GetIntA32(context, op.Rn)); + + var exclusive = (accType & AccessType.Exclusive) != 0; + var ordered = (accType & AccessType.Ordered) != 0; + + if ((accType & AccessType.Load) != 0) + { + if (ordered) + { + EmitBarrier(context); + } + + if (size == DWordSizeLog2) + { + // Keep loads atomic - make the call to get the whole region and then decompose it into parts + // for the registers. + + Operand value = EmitLoadExclusive(context, address, exclusive, size); + + Operand valueLow = context.ConvertI64ToI32(value); + + valueLow = context.ZeroExtend32(OperandType.I64, valueLow); + + Operand valueHigh = context.ShiftRightUI(value, Const(32)); + + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + SetIntA32(context, op.Rt, valueLow); + SetIntA32(context, op.Rt2, valueHigh); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + SetIntA32(context, op.Rt2, valueLow); + SetIntA32(context, op.Rt, valueHigh); + + context.MarkLabel(lblEnd); + } + else + { + SetIntA32(context, op.Rt, EmitLoadExclusive(context, address, exclusive, size)); + } + } + else + { + if (size == DWordSizeLog2) + { + // Split the result into 2 words (based on endianness) + + Operand lo = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt)); + Operand hi = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt2)); + + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + Operand leResult = context.BitwiseOr(lo, context.ShiftLeft(hi, Const(32))); + EmitStoreExclusive(context, address, leResult, exclusive, size, op.Rd, a32: true); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Operand beResult = context.BitwiseOr(hi, context.ShiftLeft(lo, Const(32))); + EmitStoreExclusive(context, address, beResult, exclusive, size, op.Rd, a32: true); + + context.MarkLabel(lblEnd); + } + else + { + Operand value = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt)); + EmitStoreExclusive(context, address, value, exclusive, size, op.Rd, a32: true); + } + + if (ordered) + { + EmitBarrier(context); + } + } + } + + private static void EmitBarrier(ArmEmitterContext context) + { + // Note: This barrier is most likely not necessary, and probably + // doesn't make any difference since we need to do a ton of stuff + // (software MMU emulation) to read or write anything anyway. + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs b/src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs new file mode 100644 index 000000000..7fca5b853 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs @@ -0,0 +1,173 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitMemoryExHelper + { + private const int ErgSizeLog2 = 4; + + public static Operand EmitLoadExclusive(ArmEmitterContext context, Operand address, bool exclusive, int size) + { + if (exclusive) + { + Operand value; + + if (size == 4) + { + // Only 128-bit CAS is guaranteed to have a atomic load. + Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, default, write: false, 4); + + Operand zero = context.VectorZero(); + + value = context.CompareAndSwap(physAddr, zero, zero); + } + else + { + value = InstEmitMemoryHelper.EmitReadIntAligned(context, address, size); + } + + Operand arg0 = context.LoadArgument(OperandType.I64, 0); + + Operand exAddrPtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveAddressOffset())); + Operand exValuePtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveValueOffset())); + + context.Store(exAddrPtr, context.BitwiseAnd(address, Const(address.Type, GetExclusiveAddressMask()))); + + // Make sure the unused higher bits of the value are cleared. + if (size < 3) + { + context.Store(exValuePtr, Const(0UL)); + } + if (size < 4) + { + context.Store(context.Add(exValuePtr, Const(exValuePtr.Type, 8L)), Const(0UL)); + } + + // Store the new exclusive value. + context.Store(exValuePtr, value); + + return value; + } + else + { + return InstEmitMemoryHelper.EmitReadIntAligned(context, address, size); + } + } + + public static void EmitStoreExclusive( + ArmEmitterContext context, + Operand address, + Operand value, + bool exclusive, + int size, + int rs, + bool a32) + { + if (size < 3) + { + value = context.ConvertI64ToI32(value); + } + + if (exclusive) + { + // We overwrite one of the register (Rs), + // keep a copy of the values to ensure we are working with the correct values. + address = context.Copy(address); + value = context.Copy(value); + + void SetRs(Operand value) + { + if (a32) + { + SetIntA32(context, rs, value); + } + else + { + SetIntOrZR(context, rs, value); + } + } + + Operand arg0 = context.LoadArgument(OperandType.I64, 0); + + Operand exAddrPtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveAddressOffset())); + Operand exAddr = context.Load(address.Type, exAddrPtr); + + // STEP 1: Check if we have exclusive access to this memory region. If not, fail and skip store. + Operand maskedAddress = context.BitwiseAnd(address, Const(address.Type, GetExclusiveAddressMask())); + + Operand exFailed = context.ICompareNotEqual(exAddr, maskedAddress); + + Operand lblExit = Label(); + + SetRs(Const(1)); + + context.BranchIfTrue(lblExit, exFailed); + + // STEP 2: We have exclusive access and the address is valid, attempt the store using CAS. + Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, default, write: true, size); + + Operand exValuePtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveValueOffset())); + Operand exValue = size switch + { + 0 => context.Load8(exValuePtr), + 1 => context.Load16(exValuePtr), + 2 => context.Load(OperandType.I32, exValuePtr), + 3 => context.Load(OperandType.I64, exValuePtr), + _ => context.Load(OperandType.V128, exValuePtr), + }; + + Operand currValue = size switch + { + 0 => context.CompareAndSwap8(physAddr, exValue, value), + 1 => context.CompareAndSwap16(physAddr, exValue, value), + _ => context.CompareAndSwap(physAddr, exValue, value), + }; + + // STEP 3: Check if we succeeded by comparing expected and in-memory values. + Operand storeFailed; + + if (size == 4) + { + Operand currValueLow = context.VectorExtract(OperandType.I64, currValue, 0); + Operand currValueHigh = context.VectorExtract(OperandType.I64, currValue, 1); + + Operand exValueLow = context.VectorExtract(OperandType.I64, exValue, 0); + Operand exValueHigh = context.VectorExtract(OperandType.I64, exValue, 1); + + storeFailed = context.BitwiseOr( + context.ICompareNotEqual(currValueLow, exValueLow), + context.ICompareNotEqual(currValueHigh, exValueHigh)); + } + else + { + storeFailed = context.ICompareNotEqual(currValue, exValue); + } + + SetRs(storeFailed); + + context.MarkLabel(lblExit); + } + else + { + InstEmitMemoryHelper.EmitWriteIntAligned(context, address, value, size); + } + } + + public static void EmitClearExclusive(ArmEmitterContext context) + { + Operand arg0 = context.LoadArgument(OperandType.I64, 0); + + Operand exAddrPtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveAddressOffset())); + + // We store ULONG max to force any exclusive address checks to fail, + // since this value is not aligned to the ERG mask. + context.Store(exAddrPtr, Const(ulong.MaxValue)); + } + + private static long GetExclusiveAddressMask() => ~((4L << ErgSizeLog2) - 1); + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs new file mode 100644 index 000000000..a807eed51 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs @@ -0,0 +1,757 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.Translation; +using ARMeilleure.Translation.PTC; +using System; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitMemoryHelper + { + private const int PageBits = 12; + private const int PageMask = (1 << PageBits) - 1; + + private enum Extension + { + Zx, + Sx32, + Sx64, + } + + public static void EmitLoadZx(ArmEmitterContext context, Operand address, int rt, int size) + { + EmitLoad(context, address, Extension.Zx, rt, size); + } + + public static void EmitLoadSx32(ArmEmitterContext context, Operand address, int rt, int size) + { + EmitLoad(context, address, Extension.Sx32, rt, size); + } + + public static void EmitLoadSx64(ArmEmitterContext context, Operand address, int rt, int size) + { + EmitLoad(context, address, Extension.Sx64, rt, size); + } + + private static void EmitLoad(ArmEmitterContext context, Operand address, Extension ext, int rt, int size) + { + bool isSimd = IsSimd(context); + + if ((uint)size > (isSimd ? 4 : 3)) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (isSimd) + { + EmitReadVector(context, address, context.VectorZero(), rt, 0, size); + } + else + { + EmitReadInt(context, address, rt, size); + } + + if (!isSimd && !(context.CurrOp is OpCode32 && rt == State.RegisterAlias.Aarch32Pc)) + { + Operand value = GetInt(context, rt); + + if (ext == Extension.Sx32 || ext == Extension.Sx64) + { + OperandType destType = ext == Extension.Sx64 ? OperandType.I64 : OperandType.I32; + + switch (size) + { + case 0: + value = context.SignExtend8(destType, value); + break; + case 1: + value = context.SignExtend16(destType, value); + break; + case 2: + value = context.SignExtend32(destType, value); + break; + } + } + + SetInt(context, rt, value); + } + } + + public static void EmitLoadSimd( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + EmitReadVector(context, address, vector, rt, elem, size); + } + + public static void EmitStore(ArmEmitterContext context, Operand address, int rt, int size) + { + bool isSimd = IsSimd(context); + + if ((uint)size > (isSimd ? 4 : 3)) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (isSimd) + { + EmitWriteVector(context, address, rt, 0, size); + } + else + { + EmitWriteInt(context, address, rt, size); + } + } + + public static void EmitStoreSimd( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + EmitWriteVector(context, address, rt, elem, size); + } + + private static bool IsSimd(ArmEmitterContext context) + { + return context.CurrOp is IOpCodeSimd && + !(context.CurrOp is OpCodeSimdMemMs || + context.CurrOp is OpCodeSimdMemSs); + } + + public static Operand EmitReadInt(ArmEmitterContext context, Operand address, int size) + { + Operand temp = context.AllocateLocal(size == 3 ? OperandType.I64 : OperandType.I32); + + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); + + Operand value = default; + + switch (size) + { + case 0: + value = context.Load8(physAddr); + break; + case 1: + value = context.Load16(physAddr); + break; + case 2: + value = context.Load(OperandType.I32, physAddr); + break; + case 3: + value = context.Load(OperandType.I64, physAddr); + break; + } + + context.Copy(temp, value); + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + context.Copy(temp, EmitReadIntFallback(context, address, size)); + + context.MarkLabel(lblEnd); + } + + return temp; + } + + private static void EmitReadInt(ArmEmitterContext context, Operand address, int rt, int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); + + Operand value = default; + + switch (size) + { + case 0: + value = context.Load8(physAddr); + break; + case 1: + value = context.Load16(physAddr); + break; + case 2: + value = context.Load(OperandType.I32, physAddr); + break; + case 3: + value = context.Load(OperandType.I64, physAddr); + break; + } + + SetInt(context, rt, value); + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitReadIntFallback(context, address, rt, size); + + context.MarkLabel(lblEnd); + } + } + + public static Operand EmitReadIntAligned(ArmEmitterContext context, Operand address, int size) + { + if ((uint)size > 4) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + Operand physAddr = EmitPtPointerLoad(context, address, default, write: false, size); + + return size switch + { + 0 => context.Load8(physAddr), + 1 => context.Load16(physAddr), + 2 => context.Load(OperandType.I32, physAddr), + 3 => context.Load(OperandType.I64, physAddr), + _ => context.Load(OperandType.V128, physAddr), + }; + } + + private static void EmitReadVector( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); + + Operand value = default; + + switch (size) + { + case 0: + value = context.VectorInsert8(vector, context.Load8(physAddr), elem); + break; + case 1: + value = context.VectorInsert16(vector, context.Load16(physAddr), elem); + break; + case 2: + value = context.VectorInsert(vector, context.Load(OperandType.I32, physAddr), elem); + break; + case 3: + value = context.VectorInsert(vector, context.Load(OperandType.I64, physAddr), elem); + break; + case 4: + value = context.Load(OperandType.V128, physAddr); + break; + } + + context.Copy(GetVec(rt), value); + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitReadVectorFallback(context, address, vector, rt, elem, size); + + context.MarkLabel(lblEnd); + } + } + + private static Operand VectorCreate(ArmEmitterContext context, Operand value) + { + return context.VectorInsert(context.VectorZero(), value, 0); + } + + private static void EmitWriteInt(ArmEmitterContext context, Operand address, int rt, int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size); + + Operand value = GetInt(context, rt); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + switch (size) + { + case 0: + context.Store8(physAddr, value); + break; + case 1: + context.Store16(physAddr, value); + break; + case 2: + context.Store(physAddr, value); + break; + case 3: + context.Store(physAddr, value); + break; + } + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitWriteIntFallback(context, address, rt, size); + + context.MarkLabel(lblEnd); + } + } + + public static void EmitWriteIntAligned(ArmEmitterContext context, Operand address, Operand value, int size) + { + if ((uint)size > 4) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + Operand physAddr = EmitPtPointerLoad(context, address, default, write: true, size); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + if (size == 0) + { + context.Store8(physAddr, value); + } + else if (size == 1) + { + context.Store16(physAddr, value); + } + else + { + context.Store(physAddr, value); + } + } + + private static void EmitWriteVector( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size); + + Operand value = GetVec(rt); + + switch (size) + { + case 0: + context.Store8(physAddr, context.VectorExtract8(value, elem)); + break; + case 1: + context.Store16(physAddr, context.VectorExtract16(value, elem)); + break; + case 2: + context.Store(physAddr, context.VectorExtract(OperandType.I32, value, elem)); + break; + case 3: + context.Store(physAddr, context.VectorExtract(OperandType.I64, value, elem)); + break; + case 4: + context.Store(physAddr, value); + break; + } + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitWriteVectorFallback(context, address, rt, elem, size); + + context.MarkLabel(lblEnd); + } + } + + public static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand address, Operand lblSlowPath, bool write, int size) + { + if (context.Memory.Type.IsHostMapped()) + { + return EmitHostMappedPointer(context, address); + } + + int ptLevelBits = context.Memory.AddressSpaceBits - PageBits; + int ptLevelSize = 1 << ptLevelBits; + int ptLevelMask = ptLevelSize - 1; + + Operand addrRotated = size != 0 ? context.RotateRight(address, Const(size)) : address; + Operand addrShifted = context.ShiftRightUI(addrRotated, Const(PageBits - size)); + + Operand pte = !context.HasPtc + ? Const(context.Memory.PageTablePointer.ToInt64()) + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); + + Operand pteOffset = context.BitwiseAnd(addrShifted, Const(addrShifted.Type, ptLevelMask)); + + if (pteOffset.Type == OperandType.I32) + { + pteOffset = context.ZeroExtend32(OperandType.I64, pteOffset); + } + + pte = context.Load(OperandType.I64, context.Add(pte, context.ShiftLeft(pteOffset, Const(3)))); + + if (addrShifted.Type == OperandType.I32) + { + addrShifted = context.ZeroExtend32(OperandType.I64, addrShifted); + } + + // If the VA is out of range, or not aligned to the access size, force PTE to 0 by masking it. + pte = context.BitwiseAnd(pte, context.ShiftRightSI(context.Add(addrShifted, Const(-(long)ptLevelSize)), Const(63))); + + if (lblSlowPath != default) + { + if (write) + { + context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual); + pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access) + } + else + { + pte = context.ShiftLeft(pte, Const(1)); + context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual); + pte = context.ShiftRightUI(pte, Const(1)); + } + } + else + { + // When no label is provided to jump to a slow path if the address is invalid, + // we do the validation ourselves, and throw if needed. + + Operand lblNotWatched = Label(); + + // Is the page currently being tracked for read/write? If so we need to call SignalMemoryTracking. + context.BranchIf(lblNotWatched, pte, Const(0L), Comparison.GreaterOrEqual, BasicBlockFrequency.Cold); + + // Signal memory tracking. Size here doesn't matter as address is assumed to be size aligned here. + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)), address, Const(1UL), Const(write ? 1 : 0)); + context.MarkLabel(lblNotWatched); + + pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access) + + Operand lblNonNull = Label(); + + // Skip exception if the PTE address is non-null (not zero). + context.BranchIfTrue(lblNonNull, pte, BasicBlockFrequency.Cold); + + // The call is not expected to return (it should throw). + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)), address); + context.MarkLabel(lblNonNull); + } + + Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, PageMask)); + + if (pageOffset.Type == OperandType.I32) + { + pageOffset = context.ZeroExtend32(OperandType.I64, pageOffset); + } + + return context.Add(pte, pageOffset); + } + + public static Operand EmitHostMappedPointer(ArmEmitterContext context, Operand address) + { + if (address.Type == OperandType.I32) + { + address = context.ZeroExtend32(OperandType.I64, address); + } + + if (context.Memory.Type == MemoryManagerType.HostMapped) + { + Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits)); + address = context.BitwiseAnd(address, mask); + } + + Operand baseAddr = !context.HasPtc + ? Const(context.Memory.PageTablePointer.ToInt64()) + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); + + return context.Add(baseAddr, address); + } + + private static void EmitReadIntFallback(ArmEmitterContext context, Operand address, int rt, int size) + { + SetInt(context, rt, EmitReadIntFallback(context, address, size)); + } + + private static Operand EmitReadIntFallback(ArmEmitterContext context, Operand address, int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); + break; + case 1: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); + break; + case 2: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); + break; + case 3: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); + break; + } + + return context.Call(info, address); + } + + private static void EmitReadVectorFallback( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); + break; + case 1: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); + break; + case 2: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); + break; + case 3: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); + break; + case 4: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)); + break; + } + + Operand value = context.Call(info, address); + + switch (size) + { + case 0: + value = context.VectorInsert8(vector, value, elem); + break; + case 1: + value = context.VectorInsert16(vector, value, elem); + break; + case 2: + value = context.VectorInsert(vector, value, elem); + break; + case 3: + value = context.VectorInsert(vector, value, elem); + break; + } + + context.Copy(GetVec(rt), value); + } + + private static void EmitWriteIntFallback(ArmEmitterContext context, Operand address, int rt, int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); + break; + case 1: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); + break; + case 2: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); + break; + case 3: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); + break; + } + + Operand value = GetInt(context, rt); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + context.Call(info, address, value); + } + + private static void EmitWriteVectorFallback( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); + break; + case 1: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); + break; + case 2: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); + break; + case 3: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); + break; + case 4: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteVector128)); + break; + } + + Operand value = default; + + if (size < 4) + { + switch (size) + { + case 0: + value = context.VectorExtract8(GetVec(rt), elem); + break; + case 1: + value = context.VectorExtract16(GetVec(rt), elem); + break; + case 2: + value = context.VectorExtract(OperandType.I32, GetVec(rt), elem); + break; + case 3: + value = context.VectorExtract(OperandType.I64, GetVec(rt), elem); + break; + } + } + else + { + value = GetVec(rt); + } + + context.Call(info, address, value); + } + + private static Operand GetInt(ArmEmitterContext context, int rt) + { + return context.CurrOp is OpCode32 ? GetIntA32(context, rt) : GetIntOrZR(context, rt); + } + + private static void SetInt(ArmEmitterContext context, int rt, Operand value) + { + if (context.CurrOp is OpCode32) + { + SetIntA32(context, rt, value); + } + else + { + SetIntOrZR(context, rt, value); + } + } + + // ARM32 helpers. + public static Operand GetMemM(ArmEmitterContext context, bool setCarry = true) + { + return context.CurrOp switch + { + IOpCode32MemRsImm op => GetMShiftedByImmediate(context, op, setCarry), + IOpCode32MemReg op => GetIntA32(context, op.Rm), + IOpCode32Mem op => Const(op.Immediate), + OpCode32SimdMemImm op => Const(op.Immediate), + _ => throw InvalidOpCodeType(context.CurrOp), + }; + } + + private static Exception InvalidOpCodeType(OpCode opCode) + { + return new InvalidOperationException($"Invalid OpCode type \"{opCode?.GetType().Name ?? "null"}\"."); + } + + public static Operand GetMShiftedByImmediate(ArmEmitterContext context, IOpCode32MemRsImm op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + + int shift = op.Immediate; + + if (shift == 0) + { + switch (op.ShiftType) + { + case ShiftType.Lsr: + shift = 32; + break; + case ShiftType.Asr: + shift = 32; + break; + case ShiftType.Ror: + shift = 1; + break; + } + } + + if (shift != 0) + { + setCarry &= false; + + switch (op.ShiftType) + { + case ShiftType.Lsl: + m = InstEmitAluHelper.GetLslC(context, m, setCarry, shift); + break; + case ShiftType.Lsr: + m = InstEmitAluHelper.GetLsrC(context, m, setCarry, shift); + break; + case ShiftType.Asr: + m = InstEmitAluHelper.GetAsrC(context, m, setCarry, shift); + break; + case ShiftType.Ror: + if (op.Immediate != 0) + { + m = InstEmitAluHelper.GetRorC(context, m, setCarry, shift); + } + else + { + m = InstEmitAluHelper.GetRrxC(context, m, setCarry); + } + break; + } + } + + return m; + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMove.cs b/src/ARMeilleure/Instructions/InstEmitMove.cs new file mode 100644 index 000000000..f23ac333b --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMove.cs @@ -0,0 +1,41 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Movk(ArmEmitterContext context) + { + OpCodeMov op = (OpCodeMov)context.CurrOp; + + OperandType type = op.GetOperandType(); + + Operand res = GetIntOrZR(context, op.Rd); + + res = context.BitwiseAnd(res, Const(type, ~(0xffffL << op.Bit))); + + res = context.BitwiseOr(res, Const(type, op.Immediate)); + + SetIntOrZR(context, op.Rd, res); + } + + public static void Movn(ArmEmitterContext context) + { + OpCodeMov op = (OpCodeMov)context.CurrOp; + + SetIntOrZR(context, op.Rd, Const(op.GetOperandType(), ~op.Immediate)); + } + + public static void Movz(ArmEmitterContext context) + { + OpCodeMov op = (OpCodeMov)context.CurrOp; + + SetIntOrZR(context, op.Rd, Const(op.GetOperandType(), op.Immediate)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMul.cs b/src/ARMeilleure/Instructions/InstEmitMul.cs new file mode 100644 index 000000000..89dc09938 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMul.cs @@ -0,0 +1,101 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics.CodeAnalysis; +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Madd(ArmEmitterContext context) => EmitMul(context, isAdd: true); + public static void Msub(ArmEmitterContext context) => EmitMul(context, isAdd: false); + + private static void EmitMul(ArmEmitterContext context, bool isAdd) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand a = GetIntOrZR(context, op.Ra); + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand res = context.Multiply(n, m); + + res = isAdd ? context.Add(a, res) : context.Subtract(a, res); + + SetIntOrZR(context, op.Rd, res); + } + + public static void Smaddl(ArmEmitterContext context) => EmitMull(context, MullFlags.SignedAdd); + public static void Smsubl(ArmEmitterContext context) => EmitMull(context, MullFlags.SignedSubtract); + public static void Umaddl(ArmEmitterContext context) => EmitMull(context, MullFlags.Add); + public static void Umsubl(ArmEmitterContext context) => EmitMull(context, MullFlags.Subtract); + + [Flags] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + private enum MullFlags + { + Subtract = 0, + Add = 1 << 0, + Signed = 1 << 1, + + SignedAdd = Signed | Add, + SignedSubtract = Signed | Subtract, + } + + private static void EmitMull(ArmEmitterContext context, MullFlags flags) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand GetExtendedRegister32(int index) + { + Operand value = GetIntOrZR(context, index); + + if ((flags & MullFlags.Signed) != 0) + { + return context.SignExtend32(value.Type, value); + } + else + { + return context.ZeroExtend32(value.Type, value); + } + } + + Operand a = GetIntOrZR(context, op.Ra); + + Operand n = GetExtendedRegister32(op.Rn); + Operand m = GetExtendedRegister32(op.Rm); + + Operand res = context.Multiply(n, m); + + res = (flags & MullFlags.Add) != 0 ? context.Add(a, res) : context.Subtract(a, res); + + SetIntOrZR(context, op.Rd, res); + } + + public static void Smulh(ArmEmitterContext context) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = context.Multiply64HighSI(n, m); + + SetIntOrZR(context, op.Rd, d); + } + + public static void Umulh(ArmEmitterContext context) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = context.Multiply64HighUI(n, m); + + SetIntOrZR(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMul32.cs b/src/ARMeilleure/Instructions/InstEmitMul32.cs new file mode 100644 index 000000000..b9966ad1d --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMul32.cs @@ -0,0 +1,378 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + [Flags] + private enum MullFlags + { + Subtract = 1, + Add = 1 << 1, + Signed = 1 << 2, + + SignedAdd = Signed | Add, + SignedSubtract = Signed | Subtract, + } + + public static void Mla(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + Operand a = GetIntA32(context, op.Ra); + + Operand res = context.Add(a, context.Multiply(n, m)); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Mls(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + Operand a = GetIntA32(context, op.Ra); + + Operand res = context.Subtract(a, context.Multiply(n, m)); + + EmitAluStore(context, res); + } + + public static void Smmla(ArmEmitterContext context) + { + EmitSmmul(context, MullFlags.SignedAdd); + } + + public static void Smmls(ArmEmitterContext context) + { + EmitSmmul(context, MullFlags.SignedSubtract); + } + + public static void Smmul(ArmEmitterContext context) + { + EmitSmmul(context, MullFlags.Signed); + } + + private static void EmitSmmul(ArmEmitterContext context, MullFlags flags) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + + Operand res = context.Multiply(n, m); + + if (flags.HasFlag(MullFlags.Add) && op.Ra != 0xf) + { + res = context.Add(context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Ra)), Const(32)), res); + } + else if (flags.HasFlag(MullFlags.Subtract)) + { + res = context.Subtract(context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Ra)), Const(32)), res); + } + + if (op.R) + { + res = context.Add(res, Const(0x80000000L)); + } + + Operand hi = context.ConvertI64ToI32(context.ShiftRightSI(res, Const(32))); + + EmitGenericAluStoreA32(context, op.Rd, false, hi); + } + + public static void Smla__(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + Operand a = GetIntA32(context, op.Ra); + + if (op.NHigh) + { + n = context.SignExtend16(OperandType.I64, context.ShiftRightUI(n, Const(16))); + } + else + { + n = context.SignExtend16(OperandType.I64, n); + } + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(n, m); + + Operand toAdd = context.SignExtend32(OperandType.I64, a); + res = context.Add(res, toAdd); + Operand q = context.ICompareNotEqual(res, context.SignExtend32(OperandType.I64, res)); + res = context.ConvertI64ToI32(res); + + UpdateQFlag(context, q); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Smlal(ArmEmitterContext context) + { + EmitMlal(context, true); + } + + public static void Smlal__(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (op.NHigh) + { + n = context.SignExtend16(OperandType.I64, context.ShiftRightUI(n, Const(16))); + } + else + { + n = context.SignExtend16(OperandType.I64, n); + } + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(n, m); + + Operand toAdd = context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdHi)), Const(32)); + toAdd = context.BitwiseOr(toAdd, context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdLo))); + res = context.Add(res, toAdd); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + EmitGenericAluStoreA32(context, op.RdHi, false, hi); + EmitGenericAluStoreA32(context, op.RdLo, false, lo); + } + + public static void Smlaw_(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + Operand a = GetIntA32(context, op.Ra); + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(context.SignExtend32(OperandType.I64, n), m); + + Operand toAdd = context.ShiftLeft(context.SignExtend32(OperandType.I64, a), Const(16)); + res = context.Add(res, toAdd); + res = context.ShiftRightSI(res, Const(16)); + Operand q = context.ICompareNotEqual(res, context.SignExtend32(OperandType.I64, res)); + res = context.ConvertI64ToI32(res); + + UpdateQFlag(context, q); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Smul__(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (op.NHigh) + { + n = context.ShiftRightSI(n, Const(16)); + } + else + { + n = context.SignExtend16(OperandType.I32, n); + } + + if (op.MHigh) + { + m = context.ShiftRightSI(m, Const(16)); + } + else + { + m = context.SignExtend16(OperandType.I32, m); + } + + Operand res = context.Multiply(n, m); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Smull(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + + Operand res = context.Multiply(n, m); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi); + EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo); + } + + public static void Smulw_(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(context.SignExtend32(OperandType.I64, n), m); + + res = context.ShiftRightUI(res, Const(16)); + res = context.ConvertI64ToI32(res); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Umaal(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + Operand dHi = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdHi)); + Operand dLo = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdLo)); + + Operand res = context.Multiply(n, m); + res = context.Add(res, dHi); + res = context.Add(res, dLo); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + EmitGenericAluStoreA32(context, op.RdHi, false, hi); + EmitGenericAluStoreA32(context, op.RdLo, false, lo); + } + + public static void Umlal(ArmEmitterContext context) + { + EmitMlal(context, false); + } + + public static void Umull(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + + Operand res = context.Multiply(n, m); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi); + EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo); + } + + private static void EmitMlal(ArmEmitterContext context, bool signed) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (signed) + { + n = context.SignExtend32(OperandType.I64, n); + m = context.SignExtend32(OperandType.I64, m); + } + else + { + n = context.ZeroExtend32(OperandType.I64, n); + m = context.ZeroExtend32(OperandType.I64, m); + } + + Operand res = context.Multiply(n, m); + + Operand toAdd = context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdHi)), Const(32)); + toAdd = context.BitwiseOr(toAdd, context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdLo))); + res = context.Add(res, toAdd); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi); + EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo); + } + + private static void UpdateQFlag(ArmEmitterContext context, Operand q) + { + Operand lblSkipSetQ = Label(); + + context.BranchIfFalse(lblSkipSetQ, q); + + SetFlag(context, PState.QFlag, Const(1)); + + context.MarkLabel(lblSkipSetQ); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs new file mode 100644 index 000000000..543aab023 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs @@ -0,0 +1,5276 @@ +// https://github.com/intel/ARM_NEON_2_x86_SSE/blob/master/NEON_2_SSE.h +// https://www.agner.org/optimize/#vectorclass @ vectori128.h + +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +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; + + static partial class InstEmit + { + public static void Abs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOp(context, Intrinsic.Arm64AbsS); + } + else + { + EmitScalarUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Abs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64AbsV); + } + else + { + EmitVectorUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Add_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64AddS); + } + else + { + EmitScalarBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Add_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64AddV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + Operand res = context.AddIntrinsic(addInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Addhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64AddhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Add(op1, op2), round: false); + } + } + + public static void Addp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOp(context, Intrinsic.Arm64AddpS); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne0 = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + Operand ne1 = EmitVectorExtractZx(context, op.Rn, 1, op.Size); + + Operand res = context.Add(ne0, ne1); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, op.Size)); + } + } + + public static void Addp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64AddpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PaddInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Addv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64AddvV); + } + else + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Cls_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64ClsV); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + int eSize = 8 << op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand de = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingSigns)), ne, Const(eSize)); + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Clz_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64ClzV); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand res = eSize switch + { + 8 => Clz_V_I8(context, GetVec(op.Rn)), + 16 => Clz_V_I16(context, GetVec(op.Rn)), + 32 => Clz_V_I32(context, GetVec(op.Rn)), + _ => default, + }; + + if (res != default) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + } + else + { + int elems = op.GetBytesCount() >> op.Size; + + res = context.VectorZero(); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand de = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingZeros)), ne, Const(eSize)); + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static Operand Clz_V_I8(ArmEmitterContext context, Operand arg) + { + if (!Optimizations.UseSsse3) + { + return default; + } + + // CLZ nibble table. + Operand clzTable = X86GetScalar(context, 0x01_01_01_01_02_02_03_04); + + Operand maskLow = X86GetAllElements(context, 0x0f_0f_0f_0f); + Operand c04 = X86GetAllElements(context, 0x04_04_04_04); + + // CLZ of low 4 bits of elements in arg. + Operand loClz = context.AddIntrinsic(Intrinsic.X86Pshufb, clzTable, arg); + + // Get the high 4 bits of elements in arg. + Operand hiArg = context.AddIntrinsic(Intrinsic.X86Psrlw, arg, Const(4)); + hiArg = context.AddIntrinsic(Intrinsic.X86Pand, hiArg, maskLow); + + // CLZ of high 4 bits of elements in arg. + Operand hiClz = context.AddIntrinsic(Intrinsic.X86Pshufb, clzTable, hiArg); + + // If high 4 bits are not all zero, we discard the CLZ of the low 4 bits. + Operand mask = context.AddIntrinsic(Intrinsic.X86Pcmpeqb, hiClz, c04); + loClz = context.AddIntrinsic(Intrinsic.X86Pand, loClz, mask); + + return context.AddIntrinsic(Intrinsic.X86Paddb, loClz, hiClz); + } + + private static Operand Clz_V_I16(ArmEmitterContext context, Operand arg) + { + if (!Optimizations.UseSsse3) + { + return default; + } + + Operand maskSwap = X86GetElements(context, 0x80_0f_80_0d_80_0b_80_09, 0x80_07_80_05_80_03_80_01); + Operand maskLow = X86GetAllElements(context, 0x00ff_00ff); + Operand c0008 = X86GetAllElements(context, 0x0008_0008); + + // CLZ pair of high 8 and low 8 bits of elements in arg. + Operand hiloClz = Clz_V_I8(context, arg); + // Get CLZ of low 8 bits in each pair. + Operand loClz = context.AddIntrinsic(Intrinsic.X86Pand, hiloClz, maskLow); + // Get CLZ of high 8 bits in each pair. + Operand hiClz = context.AddIntrinsic(Intrinsic.X86Pshufb, hiloClz, maskSwap); + + // If high 8 bits are not all zero, we discard the CLZ of the low 8 bits. + Operand mask = context.AddIntrinsic(Intrinsic.X86Pcmpeqw, hiClz, c0008); + loClz = context.AddIntrinsic(Intrinsic.X86Pand, loClz, mask); + + return context.AddIntrinsic(Intrinsic.X86Paddw, loClz, hiClz); + } + + private static Operand Clz_V_I32(ArmEmitterContext context, Operand arg) + { + // TODO: Use vplzcntd when AVX-512 is supported. + if (!Optimizations.UseSse2) + { + return default; + } + +#pragma warning disable IDE0055 // Disable formatting + Operand AddVectorI32(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Paddd, op0, op1); + Operand SubVectorI32(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Psubd, op0, op1); + Operand ShiftRightVectorUI32(Operand op0, int imm8) => context.AddIntrinsic(Intrinsic.X86Psrld, op0, Const(imm8)); + Operand OrVector(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Por, op0, op1); + Operand AndVector(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Pand, op0, op1); + Operand NotVector(Operand op0) => context.AddIntrinsic(Intrinsic.X86Pandn, op0, context.VectorOne()); +#pragma warning restore IDE0055 + + Operand c55555555 = X86GetAllElements(context, 0x55555555); + Operand c33333333 = X86GetAllElements(context, 0x33333333); + Operand c0f0f0f0f = X86GetAllElements(context, 0x0f0f0f0f); + Operand c0000003f = X86GetAllElements(context, 0x0000003f); + + Operand tmp0; + Operand tmp1; + Operand res; + + // Set all bits after highest set bit to 1. + res = OrVector(ShiftRightVectorUI32(arg, 1), arg); + res = OrVector(ShiftRightVectorUI32(res, 2), res); + res = OrVector(ShiftRightVectorUI32(res, 4), res); + res = OrVector(ShiftRightVectorUI32(res, 8), res); + res = OrVector(ShiftRightVectorUI32(res, 16), res); + + // Make leading 0s into leading 1s. + res = NotVector(res); + + // Count leading 1s, which is the population count. + tmp0 = ShiftRightVectorUI32(res, 1); + tmp0 = AndVector(tmp0, c55555555); + res = SubVectorI32(res, tmp0); + + tmp0 = ShiftRightVectorUI32(res, 2); + tmp0 = AndVector(tmp0, c33333333); + tmp1 = AndVector(res, c33333333); + res = AddVectorI32(tmp0, tmp1); + + tmp0 = ShiftRightVectorUI32(res, 4); + tmp0 = AddVectorI32(tmp0, res); + res = AndVector(tmp0, c0f0f0f0f); + + tmp0 = ShiftRightVectorUI32(res, 8); + res = AddVectorI32(tmp0, res); + + tmp0 = ShiftRightVectorUI32(res, 16); + res = AddVectorI32(tmp0, res); + + res = AndVector(res, c0000003f); + + return res; + } + + public static void Cnt_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64CntV); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + 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; + + if (Optimizations.UsePopCnt) + { + de = context.AddIntrinsicLong(Intrinsic.X86Popcnt, ne); + } + else + { + de = EmitCountSetBits8(context, ne); + } + + res = EmitVectorInsert(context, res, de, index, 0); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fabd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FabdS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subss, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, true, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subsd, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, false, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + + return EmitUnaryMathCall(context, nameof(Math.Abs), res); + }); + } + } + + public static void Fabd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FabdV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subps, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, true, true); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subpd, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, false, true); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + + return EmitUnaryMathCall(context, nameof(Math.Abs), res); + }); + } + } + + public static void Fabs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FabsS); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0) + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), true, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), false, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Abs), op1); + }); + } + } + + public static void Fabs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FabsV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), true, true); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), false, true); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Abs), op1); + }); + } + } + + public static void Fadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Fadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FaddV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Faddp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FaddpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse3) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Haddps, GetVec(op.Rn), GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if ((op.Size & 1) == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Haddpd, GetVec(op.Rn), GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Faddp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FaddpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd; + + return context.AddIntrinsic(addInst, op1, op2); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Fdiv_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FdivS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Divss, Intrinsic.X86Divsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Divide(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPDiv), op1, op2); + }); + } + } + + public static void Fdiv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FdivV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Divps, Intrinsic.X86Divpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Divide(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPDiv), op1, op2); + }); + } + } + + public static void Fmadd_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, a, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addss, a, res); + } + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, a, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Fmax_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmaxS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmax_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmaxnm_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmaxnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnmp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FmaxnmpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnmp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxnmpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnmv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FmaxnmvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FmaxpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmaxv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FmaxvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmin_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FminS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fmin_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fminnm_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FminnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnmp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FminnmpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnmp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminnmpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnmv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FminnmvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FminpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fminp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fminv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FminvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fmla_Se(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaSe); + } + else if (Optimizations.UseFma) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, d, n, res); + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, d, n, res); + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryOpByElemF(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Fmla_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRd(context, Intrinsic.Arm64FmlaV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addps, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); + } + + context.Copy(d, res); + } + } + else + { + EmitVectorTernaryOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Fmla_Ve(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaVe); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res); + res = context.AddIntrinsic(Intrinsic.X86Addps, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); + res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); + } + + context.Copy(d, res); + } + } + else + { + EmitVectorTernaryOpByElemF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Fmls_Se(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsSe); + } + else if (Optimizations.UseFma) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, d, n, res); + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, d, n, res); + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryOpByElemF(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Fmls_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRd(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subps, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); + } + + context.Copy(d, res); + } + } + else + { + EmitVectorTernaryOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Fmls_Ve(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsVe); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res); + res = context.AddIntrinsic(Intrinsic.X86Subps, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); + res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); + } + + context.Copy(d, res); + } + } + else + { + EmitVectorTernaryOpByElemF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Fmsub_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, a, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, a, res); + } + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, a, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Fmul_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmulS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Fmul_Se(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpFByElem(context, Intrinsic.Arm64FmulSe); + } + else + { + EmitScalarBinaryOpByElemF(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Fmul_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmulV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Fmul_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpFByElem(context, Intrinsic.Arm64FmulVe); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Fmulx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmulxS); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fmulx_Se(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpFByElem(context, Intrinsic.Arm64FmulxSe); + } + else + { + EmitScalarBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fmulx_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmulxV); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fmulx_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpFByElem(context, Intrinsic.Arm64FmulxVe); + } + else + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fneg_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FnegS); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0) + { + Operand mask = X86GetScalar(context, -0f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorps, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + Operand mask = X86GetScalar(context, -0d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarUnaryOpF(context, (op1) => context.Negate(op1)); + } + } + + public static void Fneg_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FnegV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = X86GetAllElements(context, -0f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorps, mask, GetVec(op.Rn)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetAllElements(context, -0d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorUnaryOpF(context, (op1) => context.Negate(op1)); + } + } + + public static void Fnmadd_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FnmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231ss, a, n, m); + } + else + { + Operand mask = X86GetScalar(context, -0f); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); + + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231sd, a, n, m); + } + else + { + Operand mask = X86GetScalar(context, -0d); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); + + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulAdd), op1, op2, op3); + }); + } + } + + public static void Fnmsub_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FnmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmsub231ss, a, n, m); + } + else + { + Operand mask = X86GetScalar(context, -0f); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); + + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmsub231sd, a, n, m); + } + else + { + Operand mask = X86GetScalar(context, -0d); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); + + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulSub), op1, op2, op3); + }); + } + } + + public static void Fnmul_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FnmulS); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Negate(context.Multiply(op1, op2))); + } + } + + public static void Frecpe_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrecpeS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rcpss, GetVec(op.Rn)), scalar: true); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipEstimate), op1); + }); + } + } + + public static void Frecpe_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrecpeV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rcpps, GetVec(op.Rn)), scalar: false); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipEstimate), op1); + }); + } + } + + public static void Frecps_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FrecpsS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand mask = X86GetScalar(context, 2f); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, mask, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res); + } + + res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetScalar(context, 2d); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, mask, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res); + } + + res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipStepFused), op1, op2); + }); + } + } + + public static void Frecps_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FrecpsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand mask = X86GetAllElements(context, 2f); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, mask, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res); + } + + res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetAllElements(context, 2d); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, mask, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res); + } + + res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipStepFused), op1, op2); + }); + } + } + + public static void Frecpx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FrecpxS); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecpX), op1); + }); + } + } + + public static void Frinta_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintaS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.ToNearestAway); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1); + }); + } + } + + public static void Frinta_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintaV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.ToNearestAway); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1); + }); + } + } + + public static void Frinti_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintiS); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frinti_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintiV); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frintm_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintmS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.TowardsMinusInfinity); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Floor), op1); + }); + } + } + + public static void Frintm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintmV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.TowardsMinusInfinity); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Floor), op1); + }); + } + } + + public static void Frintn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintnS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.ToNearest); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.ToEven, op1); + }); + } + } + + public static void Frintn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintnV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.ToNearest); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.ToEven, op1); + }); + } + } + + public static void Frintp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintpS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.TowardsPlusInfinity); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Ceiling), op1); + }); + } + } + + public static void Frintp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintpV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.TowardsPlusInfinity); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Ceiling), op1); + }); + } + } + + public static void Frintx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintxS); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frintx_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintxV); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frintz_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintzS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.TowardsZero); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Truncate), op1); + }); + } + } + + public static void Frintz_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintzV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.TowardsZero); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Truncate), op1); + }); + } + } + + public static void Frsqrte_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrsqrteS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtss, GetVec(op.Rn)), scalar: true); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtEstimate), op1); + }); + } + } + + public static void Frsqrte_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrsqrteV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtps, GetVec(op.Rn)), scalar: false); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtEstimate), op1); + }); + } + } + + public static void Frsqrts_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FrsqrtsS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand maskHalf = X86GetScalar(context, 0.5f); + Operand maskThree = X86GetScalar(context, 3f); + Operand maskOneHalf = X86GetScalar(context, 1.5f); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, maskThree, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand maskHalf = X86GetScalar(context, 0.5d); + Operand maskThree = X86GetScalar(context, 3d); + Operand maskOneHalf = X86GetScalar(context, 1.5d); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, maskThree, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtStepFused), op1, op2); + }); + } + } + + public static void Frsqrts_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FrsqrtsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand maskHalf = X86GetAllElements(context, 0.5f); + Operand maskThree = X86GetAllElements(context, 3f); + Operand maskOneHalf = X86GetAllElements(context, 1.5f); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, maskThree, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand maskHalf = X86GetAllElements(context, 0.5d); + Operand maskThree = X86GetAllElements(context, 3d); + Operand maskOneHalf = X86GetAllElements(context, 1.5d); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, maskThree, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtStepFused), op1, op2); + }); + } + } + + public static void Fsqrt_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FsqrtS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF(context, Intrinsic.X86Sqrtss, Intrinsic.X86Sqrtsd); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSqrt), op1); + }); + } + } + + public static void Fsqrt_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FsqrtV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpF(context, Intrinsic.X86Sqrtps, Intrinsic.X86Sqrtpd); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSqrt), op1); + }); + } + } + + public static void Fsub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Subtract(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + }); + } + } + + public static void Fsub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FsubV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Subtract(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + }); + } + } + + public static void Mla_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64MlaV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorMul_AddSub(context, AddSub.Add); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mla_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64MlaVe); + } + else + { + EmitVectorTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mls_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64MlsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorMul_AddSub(context, AddSub.Subtract); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mls_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64MlsVe); + } + else + { + EmitVectorTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mul_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64MulV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorMul_AddSub(context, AddSub.None); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Mul_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpByElem(context, Intrinsic.Arm64MulVe); + } + else + { + EmitVectorBinaryOpByElemZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Neg_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOp(context, Intrinsic.Arm64NegS); + } + else + { + EmitScalarUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Neg_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64NegV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + Operand res = context.AddIntrinsic(subInst, context.VectorZero(), GetVec(op.Rn)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Pmull_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseArm64Pmull) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64PmullV); + } + else if (Optimizations.UsePclmulqdq && op.Size == 3) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int imm8 = op.RegisterSize == RegisterSize.Simd64 ? 0b0000_0000 : 0b0001_0001; + + Operand res = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, n, m, Const(imm8)); + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + n = context.VectorZeroUpper64(n); + m = context.VectorZeroUpper64(m); + } + else /* if (op.RegisterSize == RegisterSize.Simd128) */ + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Operand res = context.VectorZero(); + + if (op.Size == 0) + { + n = context.AddIntrinsic(Intrinsic.X86Pmovzxbw, n); + m = context.AddIntrinsic(Intrinsic.X86Pmovzxbw, m); + + for (int i = 0; i < 8; i++) + { + Operand mask = context.AddIntrinsic(Intrinsic.X86Psllw, n, Const(15 - i)); + mask = context.AddIntrinsic(Intrinsic.X86Psraw, mask, Const(15)); + + Operand tmp = context.AddIntrinsic(Intrinsic.X86Psllw, m, Const(i)); + tmp = context.AddIntrinsic(Intrinsic.X86Pand, tmp, mask); + + res = context.AddIntrinsic(Intrinsic.X86Pxor, res, tmp); + } + } + else /* if (op.Size == 3) */ + { + Operand zero = context.VectorZero(); + + for (int i = 0; i < 64; i++) + { + Operand mask = context.AddIntrinsic(Intrinsic.X86Movlhps, n, n); + mask = context.AddIntrinsic(Intrinsic.X86Psllq, mask, Const(63 - i)); + mask = context.AddIntrinsic(Intrinsic.X86Psrlq, mask, Const(63)); + mask = context.AddIntrinsic(Intrinsic.X86Psubq, zero, mask); + + Operand tmp = EmitSse2Sll_128(context, m, i); + tmp = context.AddIntrinsic(Intrinsic.X86Pand, tmp, mask); + + res = context.AddIntrinsic(Intrinsic.X86Pxor, res, tmp); + } + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + res = context.VectorZero(); + + int part = op.RegisterSize == RegisterSize.Simd64 ? 0 : 8; + + for (int index = 0; index < 8; index++) + { + Operand ne = context.VectorExtract8(n, part + index); + Operand me = context.VectorExtract8(m, part + index); + + Operand de = EmitPolynomialMultiply(context, ne, me, 8); + + res = EmitVectorInsert(context, res, de, index, 1); + } + } + else /* if (op.Size == 3) */ + { + int part = op.RegisterSize == RegisterSize.Simd64 ? 0 : 1; + + Operand ne = context.VectorExtract(OperandType.I64, n, part); + Operand me = context.VectorExtract(OperandType.I64, m, part); + + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.PolynomialMult64_128)), ne, me); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Raddhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64RaddhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Add(op1, op2), round: true); + } + } + + public static void Rsubhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64RsubhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Subtract(op1, op2), round: true); + } + } + + public static void Saba_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SabaV); + } + else + { + EmitVectorTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Sabal_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SabalV); + } + else + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Sabd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SabdV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + EmitSse41VectorSabdOp(context, op, n, m, isLong: false); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Sabdl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SabdlV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 + ? Intrinsic.X86Pmovsxbw + : Intrinsic.X86Pmovsxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + EmitSse41VectorSabdOp(context, op, n, m, isLong: true); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Sadalp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpRd(context, Intrinsic.Arm64SadalpV); + } + else + { + EmitAddLongPairwise(context, signed: true, accumulate: true); + } + } + + public static void Saddl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SaddlV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Saddlp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SaddlpV); + } + else + { + EmitAddLongPairwise(context, signed: true, accumulate: false); + } + } + + public static void Saddlv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SaddlvV); + } + else + { + EmitVectorLongAcrossVectorOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Saddw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SaddwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Shadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64ShaddV); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, n, m); + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + Intrinsic shiftInst = op.Size == 1 ? Intrinsic.X86Psraw : Intrinsic.X86Psrad; + + res2 = context.AddIntrinsic(shiftInst, res2, Const(1)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, res2); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + return context.ShiftRightSI(context.Add(op1, op2), Const(1)); + }); + } + } + + public static void Shsub_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64ShsubV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand mask = X86GetAllElements(context, (int)(op.Size == 0 ? 0x80808080u : 0x80008000u)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + Operand nPlusMask = context.AddIntrinsic(addInst, n, mask); + Operand mPlusMask = context.AddIntrinsic(addInst, m, mask); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, nPlusMask, mPlusMask); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + res = context.AddIntrinsic(subInst, nPlusMask, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + return context.ShiftRightSI(context.Subtract(op1, op2), Const(1)); + }); + } + } + + public static void Smax_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SmaxV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxsInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: true)); + } + } + + public static void Smaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SmaxpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxsInstruction); + } + else + { + EmitVectorPairwiseOpSx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: true)); + } + } + + public static void Smaxv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SmaxvV); + } + else + { + EmitVectorAcrossVectorOpSx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: true)); + } + } + + public static void Smin_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SminV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic minInst = X86PminsInstruction[op.Size]; + + Operand res = context.AddIntrinsic(minInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: true)); + } + } + + public static void Sminp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SminpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminsInstruction); + } + else + { + EmitVectorPairwiseOpSx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: true)); + } + } + + public static void Sminv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SminvV); + } + else + { + EmitVectorAcrossVectorOpSx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: true)); + } + } + + public static void Smlal_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SmlalV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(addInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smlal_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64SmlalVe); + } + else + { + EmitVectorWidenTernaryOpByElemSx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smlsl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SmlslV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 ? Intrinsic.X86Pmovsxbw : Intrinsic.X86Pmovsxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(subInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smlsl_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64SmlslVe); + } + else + { + EmitVectorWidenTernaryOpByElemSx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smull_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SmullV); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Smull_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpByElem(context, Intrinsic.Arm64SmullVe); + } + else + { + EmitVectorWidenBinaryOpByElemSx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Sqabs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingUnaryOp(context, Intrinsic.Arm64SqabsS); + } + else + { + EmitScalarSaturatingUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Sqabs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingUnaryOp(context, Intrinsic.Arm64SqabsV); + } + else + { + EmitVectorSaturatingUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Sqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqaddS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, flags: SaturatingFlags.Add); + } + } + + public static void Sqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqaddV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, flags: SaturatingFlags.Add); + } + } + + public static void Sqdmulh_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqdmulhS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false)); + } + } + + public static void Sqdmulh_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqdmulhV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false)); + } + } + + public static void Sqdmulh_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpByElem(context, Intrinsic.Arm64SqdmulhVe); + } + else + { + EmitVectorSaturatingBinaryOpByElemSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false)); + } + } + + public static void Sqneg_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingUnaryOp(context, Intrinsic.Arm64SqnegS); + } + else + { + EmitScalarSaturatingUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Sqneg_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingUnaryOp(context, Intrinsic.Arm64SqnegV); + } + else + { + EmitVectorSaturatingUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Sqrdmulh_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqrdmulhS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true)); + } + } + + public static void Sqrdmulh_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqrdmulhV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true)); + } + } + + public static void Sqrdmulh_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpByElem(context, Intrinsic.Arm64SqrdmulhVe); + } + else + { + EmitVectorSaturatingBinaryOpByElemSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true)); + } + } + + public static void Sqsub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqsubS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, flags: SaturatingFlags.Sub); + } + } + + public static void Sqsub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqsubV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, flags: SaturatingFlags.Sub); + } + } + + public static void Sqxtn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtnS); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarSxSx); + } + } + + public static void Sqxtn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtnV); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorSxSx); + } + } + + public static void Sqxtun_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtunS); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarSxZx); + } + } + + public static void Sqxtun_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtunV); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorSxZx); + } + } + + public static void Srhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SrhaddV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand mask = X86GetAllElements(context, (int)(op.Size == 0 ? 0x80808080u : 0x80008000u)); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + Operand nMinusMask = context.AddIntrinsic(subInst, n, mask); + Operand mMinusMask = context.AddIntrinsic(subInst, m, mask); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, nMinusMask, mMinusMask); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, mask, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + Operand res = context.Add(op1, op2); + + res = context.Add(res, Const(1L)); + + return context.ShiftRightSI(res, Const(1)); + }); + } + } + + public static void Ssubl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SsublV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Ssubw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SsubwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpSx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Sub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64SubS); + } + else + { + EmitScalarBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Sub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SubV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + Operand res = context.AddIntrinsic(subInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Subhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SubhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Subtract(op1, op2), round: false); + } + } + + public static void Suqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64SuqaddS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, flags: SaturatingFlags.Accumulate); + } + } + + public static void Suqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64SuqaddV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, flags: SaturatingFlags.Accumulate); + } + } + + public static void Uaba_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UabaV); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Uabal_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UabalV); + } + else + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Uabd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UabdV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + EmitSse41VectorUabdOp(context, op, n, m, isLong: false); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Uabdl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UabdlV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 + ? Intrinsic.X86Pmovzxbw + : Intrinsic.X86Pmovzxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + EmitSse41VectorUabdOp(context, op, n, m, isLong: true); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Uadalp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpRd(context, Intrinsic.Arm64UadalpV); + } + else + { + EmitAddLongPairwise(context, signed: false, accumulate: true); + } + } + + public static void Uaddl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UaddlV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Uaddlp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UaddlpV); + } + else + { + EmitAddLongPairwise(context, signed: false, accumulate: false); + } + } + + public static void Uaddlv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UaddlvV); + } + else + { + EmitVectorLongAcrossVectorOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Uaddw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UaddwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Uhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UhaddV); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, n, m); + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + Intrinsic shiftInst = op.Size == 1 ? Intrinsic.X86Psrlw : Intrinsic.X86Psrld; + + res2 = context.AddIntrinsic(shiftInst, res2, Const(1)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, res2); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.ShiftRightUI(context.Add(op1, op2), Const(1)); + }); + } + } + + public static void Uhsub_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UhsubV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, n, m); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + res = context.AddIntrinsic(subInst, n, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.ShiftRightUI(context.Subtract(op1, op2), Const(1)); + }); + } + } + + public static void Umax_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UmaxV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: false)); + } + } + + public static void Umaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UmaxpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxuInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: false)); + } + } + + public static void Umaxv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UmaxvV); + } + else + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: false)); + } + } + + public static void Umin_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UminV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic minInst = X86PminuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(minInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: false)); + } + } + + public static void Uminp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UminpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminuInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: false)); + } + } + + public static void Uminv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UminvV); + } + else + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: false)); + } + } + + public static void Umlal_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UmlalV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(addInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umlal_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64UmlalVe); + } + else + { + EmitVectorWidenTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umlsl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UmlslV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 ? Intrinsic.X86Pmovzxbw : Intrinsic.X86Pmovzxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(subInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umlsl_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64UmlslVe); + } + else + { + EmitVectorWidenTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umull_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UmullV); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Umull_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpByElem(context, Intrinsic.Arm64UmullVe); + } + else + { + EmitVectorWidenBinaryOpByElemZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Uqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64UqaddS); + } + else + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Add); + } + } + + public static void Uqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqaddV); + } + else + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Add); + } + } + + public static void Uqsub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64UqsubS); + } + else + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Sub); + } + } + + public static void Uqsub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqsubV); + } + else + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Sub); + } + } + + public static void Uqxtn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64UqxtnS); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarZxZx); + } + } + + public static void Uqxtn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64UqxtnV); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorZxZx); + } + } + + public static void Urhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UrhaddV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + Operand res = context.Add(op1, op2); + + res = context.Add(res, Const(1L)); + + return context.ShiftRightUI(res, Const(1)); + }); + } + } + + public static void Usqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64UsqaddS); + } + else + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Accumulate); + } + } + + public static void Usqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64UsqaddV); + } + else + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Accumulate); + } + } + + public static void Usubl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UsublV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Usubw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UsubwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + private static Operand EmitAbs(ArmEmitterContext context, Operand value) + { + Operand isPositive = context.ICompareGreaterOrEqual(value, Const(value.Type, 0)); + + return context.ConditionalSelect(isPositive, value, context.Negate(value)); + } + + private static void EmitAddLongPairwise(ArmEmitterContext context, bool signed, bool accumulate) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand ne0 = EmitVectorExtract(context, op.Rn, pairIndex, op.Size, signed); + Operand ne1 = EmitVectorExtract(context, op.Rn, pairIndex + 1, op.Size, signed); + + Operand e = context.Add(ne0, ne1); + + if (accumulate) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size + 1, signed); + + e = context.Add(e, de); + } + + res = EmitVectorInsert(context, res, e, index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitDoublingMultiplyHighHalf( + ArmEmitterContext context, + Operand n, + Operand m, + bool round) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand res = context.Multiply(n, m); + + if (!round) + { + res = context.ShiftRightSI(res, Const(eSize - 1)); + } + else + { + long roundConst = 1L << (eSize - 1); + + res = context.ShiftLeft(res, Const(1)); + + res = context.Add(res, Const(roundConst)); + + res = context.ShiftRightSI(res, Const(eSize)); + + Operand isIntMin = context.ICompareEqual(res, Const((long)int.MinValue)); + + res = context.ConditionalSelect(isIntMin, context.Negate(res), res); + } + + return res; + } + + private static void EmitHighNarrow(ArmEmitterContext context, Func2I emit, bool round) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int elems = 8 >> op.Size; + int eSize = 8 << op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + long roundConst = 1L << (eSize - 1); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size + 1); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size + 1); + + Operand de = emit(ne, me); + + if (round) + { + de = context.Add(de, Const(roundConst)); + } + + de = context.ShiftRightUI(de, Const(eSize)); + + res = EmitVectorInsert(context, res, de, part + index, op.Size); + } + + context.Copy(d, res); + } + + private static Operand EmitMax64Op(ArmEmitterContext context, Operand op1, Operand op2, bool signed) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand cmp = signed + ? context.ICompareGreaterOrEqual(op1, op2) + : context.ICompareGreaterOrEqualUI(op1, op2); + + return context.ConditionalSelect(cmp, op1, op2); + } + + private static Operand EmitMin64Op(ArmEmitterContext context, Operand op1, Operand op2, bool signed) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand cmp = signed + ? context.ICompareLessOrEqual(op1, op2) + : context.ICompareLessOrEqualUI(op1, op2); + + return context.ConditionalSelect(cmp, op1, op2); + } + + private static void EmitSse41ScalarRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (roundMode != FPRoundingMode.ToNearestAway) + { + Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundsd : Intrinsic.X86Roundss; + + res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode))); + } + else + { + res = EmitSse41RoundToNearestWithTiesToAwayOpF(context, n, scalar: true); + } + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSse41VectorRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (roundMode != FPRoundingMode.ToNearestAway) + { + Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundpd : Intrinsic.X86Roundps; + + res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode))); + } + else + { + res = EmitSse41RoundToNearestWithTiesToAwayOpF(context, n, scalar: false); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitSse41Round32Exp8OpF(ArmEmitterContext context, Operand value, bool scalar) + { + Operand roundMask; + Operand truncMask; + Operand expMask; + + if (scalar) + { + roundMask = X86GetScalar(context, 0x4000); + truncMask = X86GetScalar(context, unchecked((int)0xFFFF8000)); + expMask = X86GetScalar(context, 0x7F800000); + } + else + { + roundMask = X86GetAllElements(context, 0x4000); + truncMask = X86GetAllElements(context, unchecked((int)0xFFFF8000)); + expMask = X86GetAllElements(context, 0x7F800000); + } + + Operand oValue = value; + Operand masked = context.AddIntrinsic(Intrinsic.X86Pand, value, expMask); + Operand isNaNInf = context.AddIntrinsic(Intrinsic.X86Pcmpeqd, masked, expMask); + + value = context.AddIntrinsic(Intrinsic.X86Paddd, value, roundMask); + value = context.AddIntrinsic(Intrinsic.X86Pand, value, truncMask); + + return context.AddIntrinsic(Intrinsic.X86Blendvps, value, oValue, isNaNInf); + } + + private static Operand EmitSse41RecipStepSelectOpF( + ArmEmitterContext context, + Operand n, + Operand m, + Operand res, + Operand mask, + bool scalar, + int sizeF) + { + Intrinsic cmpOp; + Intrinsic shlOp; + Intrinsic blendOp; + Operand zero = context.VectorZero(); + Operand expMask; + + if (sizeF == 0) + { + cmpOp = Intrinsic.X86Pcmpeqd; + shlOp = Intrinsic.X86Pslld; + blendOp = Intrinsic.X86Blendvps; + expMask = scalar ? X86GetScalar(context, 0x7F800000 << 1) : X86GetAllElements(context, 0x7F800000 << 1); + } + else /* if (sizeF == 1) */ + { + cmpOp = Intrinsic.X86Pcmpeqq; + shlOp = Intrinsic.X86Psllq; + blendOp = Intrinsic.X86Blendvpd; + expMask = scalar ? X86GetScalar(context, 0x7FF0000000000000L << 1) : X86GetAllElements(context, 0x7FF0000000000000L << 1); + } + + n = context.AddIntrinsic(shlOp, n, Const(1)); + m = context.AddIntrinsic(shlOp, m, Const(1)); + + Operand nZero = context.AddIntrinsic(cmpOp, n, zero); + Operand mZero = context.AddIntrinsic(cmpOp, m, zero); + Operand nInf = context.AddIntrinsic(cmpOp, n, expMask); + Operand mInf = context.AddIntrinsic(cmpOp, m, expMask); + + Operand nmZero = context.AddIntrinsic(Intrinsic.X86Por, nZero, mZero); + Operand nmInf = context.AddIntrinsic(Intrinsic.X86Por, nInf, mInf); + Operand nmZeroInf = context.AddIntrinsic(Intrinsic.X86Pand, nmZero, nmInf); + + return context.AddIntrinsic(blendOp, res, mask, nmZeroInf); + } + + public static void EmitSse2VectorIsNaNOpF( + ArmEmitterContext context, + Operand opF, + out Operand qNaNMask, + out Operand sNaNMask, + bool? isQNaN = null) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + const int QBit = 22; + + Operand qMask = X86GetAllElements(context, 1 << QBit); + + Operand mask1 = context.AddIntrinsic(Intrinsic.X86Cmpps, opF, opF, Const((int)CmpCondition.UnorderedQ)); + + Operand mask2 = context.AddIntrinsic(Intrinsic.X86Pand, opF, qMask); + mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, mask2, qMask, Const((int)CmpCondition.Equal)); + + qNaNMask = isQNaN == null || (bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andps, mask2, mask1) : default; + sNaNMask = isQNaN == null || !(bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andnps, mask2, mask1) : default; + } + else /* if ((op.Size & 1) == 1) */ + { + const int QBit = 51; + + Operand qMask = X86GetAllElements(context, 1L << QBit); + + Operand mask1 = context.AddIntrinsic(Intrinsic.X86Cmppd, opF, opF, Const((int)CmpCondition.UnorderedQ)); + + Operand mask2 = context.AddIntrinsic(Intrinsic.X86Pand, opF, qMask); + mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, mask2, qMask, Const((int)CmpCondition.Equal)); + + qNaNMask = isQNaN == null || (bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andpd, mask2, mask1) : default; + sNaNMask = isQNaN == null || !(bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andnpd, mask2, mask1) : default; + } + } + + public static Operand EmitSse41ProcessNaNsOpF( + ArmEmitterContext context, + Func2I emit, + bool scalar, + Operand n = default, + Operand m = default) + { + Operand nCopy = n == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rn)) : n; + Operand mCopy = m == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rm)) : m; + + EmitSse2VectorIsNaNOpF(context, nCopy, out Operand nQNaNMask, out Operand nSNaNMask); + EmitSse2VectorIsNaNOpF(context, mCopy, out _, out Operand mSNaNMask, isQNaN: false); + + int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1; + + if (sizeF == 0) + { + const int QBit = 22; + + Operand qMask = scalar ? X86GetScalar(context, 1 << QBit) : X86GetAllElements(context, 1 << QBit); + + Operand resNaNMask = context.AddIntrinsic(Intrinsic.X86Pandn, mSNaNMask, nQNaNMask); + resNaNMask = context.AddIntrinsic(Intrinsic.X86Por, resNaNMask, nSNaNMask); + + Operand resNaN = context.AddIntrinsic(Intrinsic.X86Blendvps, mCopy, nCopy, resNaNMask); + resNaN = context.AddIntrinsic(Intrinsic.X86Por, resNaN, qMask); + + Operand resMask = context.AddIntrinsic(Intrinsic.X86Cmpps, nCopy, mCopy, Const((int)CmpCondition.OrderedQ)); + + Operand res = context.AddIntrinsic(Intrinsic.X86Blendvps, resNaN, emit(nCopy, mCopy), resMask); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + else /* if (sizeF == 1) */ + { + const int QBit = 51; + + Operand qMask = scalar ? X86GetScalar(context, 1L << QBit) : X86GetAllElements(context, 1L << QBit); + + Operand resNaNMask = context.AddIntrinsic(Intrinsic.X86Pandn, mSNaNMask, nQNaNMask); + resNaNMask = context.AddIntrinsic(Intrinsic.X86Por, resNaNMask, nSNaNMask); + + Operand resNaN = context.AddIntrinsic(Intrinsic.X86Blendvpd, mCopy, nCopy, resNaNMask); + resNaN = context.AddIntrinsic(Intrinsic.X86Por, resNaN, qMask); + + Operand resMask = context.AddIntrinsic(Intrinsic.X86Cmppd, nCopy, mCopy, Const((int)CmpCondition.OrderedQ)); + + Operand res = context.AddIntrinsic(Intrinsic.X86Blendvpd, resNaN, emit(nCopy, mCopy), resMask); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + } + + private static Operand EmitSse2VectorMaxMinOpF(ArmEmitterContext context, Operand n, Operand m, bool isMax) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + Operand mask = X86GetAllElements(context, -0f); + + Operand res = context.AddIntrinsic(isMax ? Intrinsic.X86Maxps : Intrinsic.X86Minps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Andnps, mask, res); + + Operand resSign = context.AddIntrinsic(isMax ? Intrinsic.X86Pand : Intrinsic.X86Por, n, m); + resSign = context.AddIntrinsic(Intrinsic.X86Andps, mask, resSign); + + return context.AddIntrinsic(Intrinsic.X86Por, res, resSign); + } + else /* if ((op.Size & 1) == 1) */ + { + Operand mask = X86GetAllElements(context, -0d); + + Operand res = context.AddIntrinsic(isMax ? Intrinsic.X86Maxpd : Intrinsic.X86Minpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Andnpd, mask, res); + + Operand resSign = context.AddIntrinsic(isMax ? Intrinsic.X86Pand : Intrinsic.X86Por, n, m); + resSign = context.AddIntrinsic(Intrinsic.X86Andpd, mask, resSign); + + return context.AddIntrinsic(Intrinsic.X86Por, res, resSign); + } + } + + private static Operand EmitSse41MaxMinNumOpF( + ArmEmitterContext context, + bool isMaxNum, + bool scalar, + Operand n = default, + Operand m = default) + { + Operand nCopy = n == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rn)) : n; + Operand mCopy = m == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rm)) : m; + + EmitSse2VectorIsNaNOpF(context, nCopy, out Operand nQNaNMask, out _, isQNaN: true); + EmitSse2VectorIsNaNOpF(context, mCopy, out Operand mQNaNMask, out _, isQNaN: true); + + int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1; + + if (sizeF == 0) + { + Operand negInfMask = scalar + ? X86GetScalar(context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity) + : X86GetAllElements(context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnps, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnps, nQNaNMask, mQNaNMask); + + nCopy = context.AddIntrinsic(Intrinsic.X86Blendvps, nCopy, negInfMask, nMask); + mCopy = context.AddIntrinsic(Intrinsic.X86Blendvps, mCopy, negInfMask, mMask); + + Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum); + }, scalar: scalar, nCopy, mCopy); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + else /* if (sizeF == 1) */ + { + Operand negInfMask = scalar + ? X86GetScalar(context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity) + : X86GetAllElements(context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnpd, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnpd, nQNaNMask, mQNaNMask); + + nCopy = context.AddIntrinsic(Intrinsic.X86Blendvpd, nCopy, negInfMask, nMask); + mCopy = context.AddIntrinsic(Intrinsic.X86Blendvpd, mCopy, negInfMask, mMask); + + Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum); + }, scalar: scalar, nCopy, mCopy); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + } + + private enum AddSub + { + None, + Add, + Subtract, + } + + private static void EmitSse41VectorMul_AddSub(ArmEmitterContext context, AddSub addSub) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + Operand ns8 = context.AddIntrinsic(Intrinsic.X86Psrlw, n, Const(8)); + Operand ms8 = context.AddIntrinsic(Intrinsic.X86Psrlw, m, Const(8)); + + res = context.AddIntrinsic(Intrinsic.X86Pmullw, ns8, ms8); + + res = context.AddIntrinsic(Intrinsic.X86Psllw, res, Const(8)); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pmullw, n, m); + + Operand mask = X86GetAllElements(context, 0x00FF00FF); + + res = context.AddIntrinsic(Intrinsic.X86Pblendvb, res, res2, mask); + } + else if (op.Size == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Pmullw, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Pmulld, n, m); + } + + Operand d = GetVec(op.Rd); + + if (addSub == AddSub.Add) + { + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, d, res); + } + else if (addSub == AddSub.Subtract) + { + Intrinsic subInst = X86PsubInstruction[op.Size]; + + res = context.AddIntrinsic(subInst, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + + private static void EmitSse41VectorSabdOp( + ArmEmitterContext context, + OpCodeSimdReg op, + Operand n, + Operand m, + bool isLong) + { + int size = isLong ? op.Size + 1 : op.Size; + + Intrinsic cmpgtInst = X86PcmpgtInstruction[size]; + + Operand cmpMask = context.AddIntrinsic(cmpgtInst, n, m); + + Intrinsic subInst = X86PsubInstruction[size]; + + Operand res = context.AddIntrinsic(subInst, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Pand, cmpMask, res); + + Operand res2 = context.AddIntrinsic(subInst, m, n); + + res2 = context.AddIntrinsic(Intrinsic.X86Pandn, cmpMask, res2); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + + if (!isLong && op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSse41VectorUabdOp( + ArmEmitterContext context, + OpCodeSimdReg op, + Operand n, + Operand m, + bool isLong) + { + int size = isLong ? op.Size + 1 : op.Size; + + Intrinsic maxInst = X86PmaxuInstruction[size]; + + Operand max = context.AddIntrinsic(maxInst, m, n); + + Intrinsic cmpeqInst = X86PcmpeqInstruction[size]; + + Operand cmpMask = context.AddIntrinsic(cmpeqInst, max, m); + + Operand onesMask = X86GetAllElements(context, -1L); + + cmpMask = context.AddIntrinsic(Intrinsic.X86Pandn, cmpMask, onesMask); + + Intrinsic subInst = X86PsubInstruction[size]; + + Operand res = context.AddIntrinsic(subInst, n, m); + Operand res2 = context.AddIntrinsic(subInst, m, n); + + res = context.AddIntrinsic(Intrinsic.X86Pand, cmpMask, res); + res2 = context.AddIntrinsic(Intrinsic.X86Pandn, cmpMask, res2); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + + if (!isLong && op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitSse2Sll_128(ArmEmitterContext context, Operand op, int shift) + { + // The upper part of op is assumed to be zero. + Debug.Assert(shift >= 0 && shift < 64); + + if (shift == 0) + { + return op; + } + + Operand high = context.AddIntrinsic(Intrinsic.X86Pslldq, op, Const(8)); + high = context.AddIntrinsic(Intrinsic.X86Psrlq, high, Const(64 - shift)); + + Operand low = context.AddIntrinsic(Intrinsic.X86Psllq, op, Const(shift)); + + return context.AddIntrinsic(Intrinsic.X86Por, high, low); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs new file mode 100644 index 000000000..27608ebf8 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs @@ -0,0 +1,1702 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using static ARMeilleure.Instructions.InstEmitFlowHelper; +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 Vabd_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpI32(context, (op1, op2) => EmitAbs(context, context.Subtract(op1, op2)), !op.U); + } + + public static void Vabdl_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + EmitVectorBinaryLongOpI32(context, (op1, op2) => EmitAbs(context, context.Subtract(op1, op2)), !op.U); + } + + public static void Vabs_S(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FabsS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + return EmitFloatAbs(context, m, (op.Size & 1) == 0, false); + }); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Abs), op1)); + } + } + + public static void Vabs_V(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FabsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return EmitFloatAbs(context, m, (op.Size & 1) == 0, true); + }); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Abs), op1)); + } + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => EmitAbs(context, op1)); + } + } + + private static Operand EmitAbs(ArmEmitterContext context, Operand value) + { + Operand isPositive = context.ICompareGreaterOrEqual(value, Const(value.Type, 0)); + + return context.ConditionalSelect(isPositive, value, context.Negate(value)); + } + + public static void Vadd_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2)); + } + } + + public static void Vadd_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FaddV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF32(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPAddFpscr), op1, op2)); + } + } + + public static void Vadd_I(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PaddInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Vaddl_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Add(op1, op2), !op.U); + } + + public static void Vaddw_I(ArmEmitterContext context) + { + OpCode32SimdRegWide op = (OpCode32SimdRegWide)context.CurrOp; + + EmitVectorBinaryWideOpI32(context, (op1, op2) => context.Add(op1, op2), !op.U); + } + + public static void Vcnt(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount(); + + for (int index = 0; index < elems; index++) + { + Operand de; + Operand me = EmitVectorExtractZx32(context, op.Qm, op.Im + index, op.Size); + + if (Optimizations.UsePopCnt) + { + de = context.AddIntrinsicInt(Intrinsic.X86Popcnt, me); + } + else + { + de = EmitCountSetBits8(context, me); + } + + res = EmitVectorInsert(context, res, de, op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vdup(ArmEmitterContext context) + { + OpCode32SimdDupGP op = (OpCode32SimdDupGP)context.CurrOp; + + Operand insert = GetIntA32(context, op.Rt); + + // Zero extend into an I64, then replicate. Saves the most time over elementwise inserts. + insert = op.Size switch + { + 2 => context.Multiply(context.ZeroExtend32(OperandType.I64, insert), Const(0x0000000100000001u)), + 1 => context.Multiply(context.ZeroExtend16(OperandType.I64, insert), Const(0x0001000100010001u)), + 0 => context.Multiply(context.ZeroExtend8(OperandType.I64, insert), Const(0x0101010101010101u)), + _ => throw new InvalidOperationException($"Invalid Vdup size \"{op.Size}\"."), + }; + + InsertScalar(context, op.Vd, insert); + if (op.Q) + { + InsertScalar(context, op.Vd + 1, insert); + } + } + + public static void Vdup_1(ArmEmitterContext context) + { + OpCode32SimdDupElem op = (OpCode32SimdDupElem)context.CurrOp; + + Operand insert = EmitVectorExtractZx32(context, op.Vm >> 1, ((op.Vm & 1) << (3 - op.Size)) + op.Index, op.Size); + + // Zero extend into an I64, then replicate. Saves the most time over elementwise inserts. + insert = op.Size switch + { + 2 => context.Multiply(context.ZeroExtend32(OperandType.I64, insert), Const(0x0000000100000001u)), + 1 => context.Multiply(context.ZeroExtend16(OperandType.I64, insert), Const(0x0001000100010001u)), + 0 => context.Multiply(context.ZeroExtend8(OperandType.I64, insert), Const(0x0101010101010101u)), + _ => throw new InvalidOperationException($"Invalid Vdup size \"{op.Size}\"."), + }; + + InsertScalar(context, op.Vd, insert); + if (op.Q) + { + InsertScalar(context, op.Vd | 1, insert); + } + } + + private static (long, long) MaskHelperByteSequence(int start, int length, int startByte) + { + int end = start + length; + int b = startByte; + long result = 0; + long result2 = 0; + for (int i = 0; i < 8; i++) + { + result |= (long)((i >= end || i < start) ? 0x80 : b++) << (i * 8); + } + for (int i = 8; i < 16; i++) + { + result2 |= (long)((i >= end || i < start) ? 0x80 : b++) << ((i - 8) * 8); + } + return (result2, result); + } + + public static void Vext(ArmEmitterContext context) + { + OpCode32SimdExt op = (OpCode32SimdExt)context.CurrOp; + int elems = op.GetBytesCount(); + int byteOff = op.Immediate; + + if (Optimizations.UseSsse3) + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + // Writing low to high of d: start into n, overlap into m. + // Then rotate n down by , m up by (elems)-imm. + // Then OR them together for the result. + + (long nMaskHigh, long nMaskLow) = MaskHelperByteSequence(0, elems - byteOff, byteOff); + (long mMaskHigh, long mMaskLow) = MaskHelperByteSequence(elems - byteOff, byteOff, 0); + Operand nMask, mMask; + if (!op.Q) + { + // Do the same operation to the bytes in the top doubleword too, as our target could be in either. + nMaskHigh = nMaskLow + 0x0808080808080808L; + mMaskHigh = mMaskLow + 0x0808080808080808L; + } + nMask = X86GetElements(context, nMaskHigh, nMaskLow); + mMask = X86GetElements(context, mMaskHigh, mMaskLow); + Operand nPart = context.AddIntrinsic(Intrinsic.X86Pshufb, n, nMask); + Operand mPart = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mMask); + + return context.AddIntrinsic(Intrinsic.X86Por, nPart, mPart); + }); + } + else + { + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand extract; + + if (byteOff >= elems) + { + extract = EmitVectorExtractZx32(context, op.Qm, op.Im + (byteOff - elems), op.Size); + } + else + { + extract = EmitVectorExtractZx32(context, op.Qn, op.In + byteOff, op.Size); + } + byteOff++; + + res = EmitVectorInsert(context, res, extract, op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + } + + public static void Vfma_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfmadd231ss, Intrinsic.X86Vfmadd231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Vfma_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlaV); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Vfmadd231ps); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulAddFpscr), op1, op2, op3); + }); + } + } + + public static void Vfms_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfnmadd231ss, Intrinsic.X86Vfnmadd231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Vfms_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Vfnmadd231ps); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulSubFpscr), op1, op2, op3); + }); + } + } + + public static void Vfnma_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfnmsub231ss, Intrinsic.X86Vfnmsub231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd, isNegD: true); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulAdd), op1, op2, op3); + }); + } + } + + public static void Vfnms_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfmsub231ss, Intrinsic.X86Vfmsub231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd, isNegD: true); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulSub), op1, op2, op3); + }); + } + } + + public static void Vhadd(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.ShiftRightUI(context.Add(op1, op2), Const(1))); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.ShiftRightSI(context.Add(op1, op2), Const(1))); + } + } + + public static void Vmov_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF32(context, 0, 0); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => op1); + } + } + + public static void Vmovn(ArmEmitterContext context) + { + EmitVectorUnaryNarrowOp32(context, (op1) => op1); + } + + public static void Vneg_S(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FnegS); + } + else if (Optimizations.UseSse2) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + if ((op.Size & 1) == 0) + { + Operand mask = X86GetScalar(context, -0f); + return context.AddIntrinsic(Intrinsic.X86Xorps, mask, m); + } + else + { + Operand mask = X86GetScalar(context, -0d); + return context.AddIntrinsic(Intrinsic.X86Xorpd, mask, m); + } + }); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => context.Negate(op1)); + } + } + + public static void Vnmul_S(ArmEmitterContext context) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FnmulS); + } + else if (Optimizations.UseSse2) + { + EmitScalarBinaryOpSimd32(context, (n, m) => + { + if ((op.Size & 1) == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + Operand mask = X86GetScalar(context, -0f); + return context.AddIntrinsic(Intrinsic.X86Xorps, mask, res); + } + else + { + Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + Operand mask = X86GetScalar(context, -0d); + return context.AddIntrinsic(Intrinsic.X86Xorpd, mask, res); + } + }); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Negate(context.Multiply(op1, op2))); + } + } + + public static void Vnmla_S(ArmEmitterContext context) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd, isNegD: true); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Subtract(context.Negate(op1), context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), context.Negate(op1), res); + }); + } + } + + public static void Vnmls_S(ArmEmitterContext context) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd, isNegD: true); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Add(context.Negate(op1), context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), context.Negate(op1), res); + }); + } + } + + public static void Vneg_V(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FnegV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + if ((op.Size & 1) == 0) + { + Operand mask = X86GetAllElements(context, -0f); + return context.AddIntrinsic(Intrinsic.X86Xorps, mask, m); + } + else + { + Operand mask = X86GetAllElements(context, -0d); + return context.AddIntrinsic(Intrinsic.X86Xorpd, mask, m); + } + }); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => context.Negate(op1)); + } + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => context.Negate(op1)); + } + } + + public static void Vdiv_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FdivS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Divss, Intrinsic.X86Divsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Divide(op1, op2)); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPDiv), op1, op2); + }); + } + } + + public static void Vmaxnm_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FmaxnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, true, true); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2)); + } + } + + public static void Vmaxnm_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FmaxnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, true, false); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMaxNumFpscr), op1, op2)); + } + } + + public static void Vminnm_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FminnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, false, true); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2)); + } + } + + public static void Vminnm_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FminnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, false, false); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMinNumFpscr), op1, op2)); + } + } + + public static void Vmax_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FmaxV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Maxps, Intrinsic.X86Maxpd); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMaxFpscr), op1, op2); + }); + } + } + + public static void Vmax_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PmaxuInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareGreaterUI(op1, op2), op1, op2)); + } + } + else + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PmaxsInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareGreater(op1, op2), op1, op2)); + } + } + } + + public static void Vmin_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FminV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Minps, Intrinsic.X86Minpd); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMinFpscr), op1, op2); + }); + } + } + + public static void Vmin_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PminuInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareLessUI(op1, op2), op1, op2)); + } + } + else + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PminsInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareLess(op1, op2), op1, op2)); + } + } + } + + public static void Vmla_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, res); + }); + } + } + + public static void Vmla_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlaV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulAddFpscr), op1, op2, op3); + }); + } + } + + public static void Vmla_I(ArmEmitterContext context) + { + EmitVectorTernaryOpZx32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3))); + } + + public static void Vmla_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorsByScalarOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulAddFpscr), op1, op2, op3)); + } + } + else + { + EmitVectorsByScalarOpI32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3)), false); + } + } + + public static void Vmlal_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorTernaryLongOpI32(context, (d, n, m) => context.Add(d, context.Multiply(n, m)), !op.U); + } + + public static void Vmls_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, res); + }); + } + } + + public static void Vmls_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else if (Optimizations.FastFP) + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulSubFpscr), op1, op2, op3); + }); + } + } + + public static void Vmls_I(ArmEmitterContext context) + { + EmitVectorTernaryOpZx32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3))); + } + + public static void Vmls_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorsByScalarOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else if (Optimizations.FastFP) + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulSubFpscr), op1, op2, op3)); + } + } + else + { + EmitVectorsByScalarOpI32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3)), false); + } + } + + public static void Vmlsl_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorTernaryLongOpI32(context, (opD, op1, op2) => context.Subtract(opD, context.Multiply(op1, op2)), !op.U); + } + + public static void Vmul_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FmulS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Vmul_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FmulV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF32(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulFpscr), op1, op2); + }); + } + } + + public static void Vmul_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) // This instruction is always signed, U indicates polynomial mode. + { + EmitVectorBinaryOpZx32(context, (op1, op2) => EmitPolynomialMultiply(context, op1, op2, 8 << op.Size)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Vmul_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorByScalarOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd); + } + else if (Optimizations.FastFP) + { + EmitVectorByScalarOpF32(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorByScalarOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulFpscr), op1, op2)); + } + } + else + { + EmitVectorByScalarOpI32(context, (op1, op2) => context.Multiply(op1, op2), false); + } + } + + public static void Vmull_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + EmitVectorByScalarLongOpI32(context, (op1, op2) => context.Multiply(op1, op2), !op.U); + } + + public static void Vmull_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + if (op.Polynomial) + { + if (op.Size == 0) // P8 + { + EmitVectorBinaryLongOpI32(context, (op1, op2) => EmitPolynomialMultiply(context, op1, op2, 8 << op.Size), false); + } + else /* if (op.Size == 2) // P64 */ + { + Operand ne = context.VectorExtract(OperandType.I64, GetVec(op.Qn), op.Vn & 1); + Operand me = context.VectorExtract(OperandType.I64, GetVec(op.Qm), op.Vm & 1); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.PolynomialMult64_128)), ne, me); + + context.Copy(GetVecA32(op.Qd), res); + } + } + else + { + EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Multiply(op1, op2), !op.U); + } + } + + public static void Vpadd_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorPairwiseOpF32(context, Intrinsic.Arm64FaddpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF32(context, Intrinsic.X86Addps); + } + else + { + EmitVectorPairwiseOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPAddFpscr), op1, op2)); + } + } + + public static void Vpadd_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp32(context, X86PaddInstruction); + } + else + { + EmitVectorPairwiseOpI32(context, (op1, op2) => context.Add(op1, op2), !op.U); + } + } + + public static void Vpaddl(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + EmitVectorPairwiseLongOpI32(context, (op1, op2) => context.Add(op1, op2), (op.Opc & 1) == 0); + } + + public static void Vpmax_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorPairwiseOpF32(context, Intrinsic.Arm64FmaxpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF32(context, Intrinsic.X86Maxps); + } + else + { + EmitVectorPairwiseOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat64.FPMaxFpscr), op1, op2)); + } + } + + public static void Vpmax_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp32(context, op.U ? X86PmaxuInstruction : X86PmaxsInstruction); + } + else + { + EmitVectorPairwiseOpI32(context, (op1, op2) => + { + Operand greater = op.U ? context.ICompareGreaterUI(op1, op2) : context.ICompareGreater(op1, op2); + return context.ConditionalSelect(greater, op1, op2); + }, !op.U); + } + } + + public static void Vpmin_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorPairwiseOpF32(context, Intrinsic.Arm64FminpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF32(context, Intrinsic.X86Minps); + } + else + { + EmitVectorPairwiseOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMinFpscr), op1, op2)); + } + } + + public static void Vpmin_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp32(context, op.U ? X86PminuInstruction : X86PminsInstruction); + } + else + { + EmitVectorPairwiseOpI32(context, (op1, op2) => + { + Operand greater = op.U ? context.ICompareLessUI(op1, op2) : context.ICompareLess(op1, op2); + return context.ConditionalSelect(greater, op1, op2); + }, !op.U); + } + } + + public static void Vqadd(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitSaturatingAddSubBinaryOp(context, add: true, !op.U); + } + + public static void Vqdmulh(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + int eSize = 8 << op.Size; + + EmitVectorBinaryOpI32(context, (op1, op2) => + { + if (op.Size == 2) + { + op1 = context.SignExtend32(OperandType.I64, op1); + op2 = context.SignExtend32(OperandType.I64, op2); + } + + Operand res = context.Multiply(op1, op2); + res = context.ShiftRightSI(res, Const(eSize - 1)); + res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + }, signed: true); + } + + public static void Vqmovn(ArmEmitterContext context) + { + OpCode32SimdMovn op = (OpCode32SimdMovn)context.CurrOp; + + bool signed = !op.Q; + + EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signed, signed), signed); + } + + public static void Vqmovun(ArmEmitterContext context) + { + OpCode32SimdMovn op = (OpCode32SimdMovn)context.CurrOp; + + EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true); + } + + public static void Vqsub(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitSaturatingAddSubBinaryOp(context, add: false, !op.U); + } + + public static void Vrev(ArmEmitterContext context) + { + OpCode32SimdRev op = (OpCode32SimdRev)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitVectorUnaryOpSimd32(context, (op1) => + { + Operand mask; + switch (op.Size) + { + case 3: + // Rev64 + switch (op.Opc) + { + case 0: + mask = X86GetElements(context, 0x08090a0b0c0d0e0fL, 0x0001020304050607L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + case 1: + mask = X86GetElements(context, 0x09080b0a0d0c0f0eL, 0x0100030205040706L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + case 2: + return context.AddIntrinsic(Intrinsic.X86Shufps, op1, op1, Const(1 | (0 << 2) | (3 << 4) | (2 << 6))); + } + break; + case 2: + // Rev32 + switch (op.Opc) + { + case 0: + mask = X86GetElements(context, 0x0c0d0e0f_08090a0bL, 0x04050607_00010203L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + case 1: + mask = X86GetElements(context, 0x0d0c0f0e_09080b0aL, 0x05040706_01000302L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + } + break; + case 1: + // Rev16 + mask = X86GetElements(context, 0x0e0f_0c0d_0a0b_0809L, 0x_0607_0405_0203_0001L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + } + + throw new InvalidOperationException("Invalid VREV Opcode + Size combo."); // Should be unreachable. + }); + } + else + { + EmitVectorUnaryOpZx32(context, (op1) => + { + switch (op.Opc) + { + case 0: + switch (op.Size) // Swap bytes. + { + case 1: + return InstEmitAluHelper.EmitReverseBytes16_32Op(context, op1); + case 2: + case 3: + return context.ByteSwap(op1); + } + break; + case 1: + switch (op.Size) + { + case 2: + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0xffff0000)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x0000ffff)), Const(16))); + case 3: + return context.BitwiseOr( + context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0xffff000000000000ul)), Const(48)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x000000000000fffful)), Const(48))), + context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0x0000ffff00000000ul)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x00000000ffff0000ul)), Const(16)))); + } + break; + case 2: + // Swap upper and lower halves. + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0xffffffff00000000ul)), Const(32)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x00000000fffffffful)), Const(32))); + } + + throw new InvalidOperationException("Invalid VREV Opcode + Size combo."); // Should be unreachable. + }); + } + } + + public static void Vrecpe(ArmEmitterContext context) + { + OpCode32SimdSqrte op = (OpCode32SimdSqrte)context.CurrOp; + + if (op.F) + { + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrecpeV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2 && sizeF == 0) + { + EmitVectorUnaryOpF32(context, Intrinsic.X86Rcpps, 0); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPRecipEstimateFpscr), op1); + }); + } + } + else + { + throw new NotImplementedException("Integer Vrecpe not currently implemented."); + } + } + + public static void Vrecps(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FrecpsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + bool single = (op.Size & 1) == 0; + + // (2 - (n*m)) + EmitVectorBinaryOpSimd32(context, (n, m) => + { + if (single) + { + Operand maskTwo = X86GetAllElements(context, 2f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + + return context.AddIntrinsic(Intrinsic.X86Subps, maskTwo, res); + } + else + { + Operand maskTwo = X86GetAllElements(context, 2d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + + return context.AddIntrinsic(Intrinsic.X86Subpd, maskTwo, res); + } + }); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipStep), op1, op2); + }); + } + } + + public static void Vrhadd(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpI32(context, (op1, op2) => + { + if (op.Size == 2) + { + op1 = context.ZeroExtend32(OperandType.I64, op1); + op2 = context.ZeroExtend32(OperandType.I64, op2); + } + + Operand res = context.Add(context.Add(op1, op2), Const(op1.Type, 1L)); + res = context.ShiftRightUI(res, Const(1)); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + }, !op.U); + } + + public static void Vrsqrte(ArmEmitterContext context) + { + OpCode32SimdSqrte op = (OpCode32SimdSqrte)context.CurrOp; + + if (op.F) + { + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrsqrteV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2 && sizeF == 0) + { + EmitVectorUnaryOpF32(context, Intrinsic.X86Rsqrtps, 0); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPRSqrtEstimateFpscr), op1); + }); + } + } + else + { + throw new NotImplementedException("Integer Vrsqrte not currently implemented."); + } + } + + public static void Vrsqrts(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FrsqrtsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + bool single = (op.Size & 1) == 0; + + // (3 - (n*m)) / 2 + EmitVectorBinaryOpSimd32(context, (n, m) => + { + if (single) + { + Operand maskHalf = X86GetAllElements(context, 0.5f); + Operand maskThree = X86GetAllElements(context, 3f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res); + return context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res); + } + else + { + Operand maskHalf = X86GetAllElements(context, 0.5d); + Operand maskThree = X86GetAllElements(context, 3d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res); + return context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res); + } + }); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtStep), op1, op2); + }); + } + } + + public static void Vsel(ArmEmitterContext context) + { + OpCode32SimdSel op = (OpCode32SimdSel)context.CurrOp; + + Operand condition = default; + + switch (op.Cc) + { + case OpCode32SimdSelMode.Eq: + condition = GetCondTrue(context, Condition.Eq); + break; + case OpCode32SimdSelMode.Ge: + condition = GetCondTrue(context, Condition.Ge); + break; + case OpCode32SimdSelMode.Gt: + condition = GetCondTrue(context, Condition.Gt); + break; + case OpCode32SimdSelMode.Vs: + condition = GetCondTrue(context, Condition.Vs); + break; + } + + EmitScalarBinaryOpI32(context, (op1, op2) => + { + return context.ConditionalSelect(condition, op1, op2); + }); + } + + public static void Vsqrt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FsqrtS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF32(context, Intrinsic.X86Sqrtss, Intrinsic.X86Sqrtsd); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSqrt), op1); + }); + } + } + + public static void Vsub_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Vsub_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FsubV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Vsub_I(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PsubInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Vsubl_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Subtract(op1, op2), !op.U); + } + + public static void Vsubw_I(ArmEmitterContext context) + { + OpCode32SimdRegWide op = (OpCode32SimdRegWide)context.CurrOp; + + EmitVectorBinaryWideOpI32(context, (op1, op2) => context.Subtract(op1, op2), !op.U); + } + + private static void EmitSaturatingAddSubBinaryOp(ArmEmitterContext context, bool add, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + EmitVectorBinaryOpI32(context, (ne, me) => + { + if (op.Size <= 2) + { + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + Operand res = add ? context.Add(ne, me) : context.Subtract(ne, me); + + res = EmitSatQ(context, res, 8 << op.Size, signedSrc: true, signed); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + } + else if (add) /* if (op.Size == 3) */ + { + return signed + ? EmitBinarySignedSatQAdd(context, ne, me) + : EmitBinaryUnsignedSatQAdd(context, ne, me); + } + else /* if (sub) */ + { + return signed + ? EmitBinarySignedSatQSub(context, ne, me) + : EmitBinaryUnsignedSatQSub(context, ne, me); + } + }, signed); + } + + private static void EmitSse41MaxMinNumOpF32(ArmEmitterContext context, bool isMaxNum, bool scalar) + { + IOpCode32Simd op = (IOpCode32Simd)context.CurrOp; + + Operand genericEmit(Operand n, Operand m) + { + Operand nNum = context.Copy(n); + Operand mNum = context.Copy(m); + + InstEmit.EmitSse2VectorIsNaNOpF(context, nNum, out Operand nQNaNMask, out _, isQNaN: true); + InstEmit.EmitSse2VectorIsNaNOpF(context, mNum, out Operand mQNaNMask, out _, isQNaN: true); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand negInfMask = X86GetAllElements(context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnps, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnps, nQNaNMask, mQNaNMask); + + nNum = context.AddIntrinsic(Intrinsic.X86Blendvps, nNum, negInfMask, nMask); + mNum = context.AddIntrinsic(Intrinsic.X86Blendvps, mNum, negInfMask, mMask); + + return context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxps : Intrinsic.X86Minps, nNum, mNum); + } + else /* if (sizeF == 1) */ + { + Operand negInfMask = X86GetAllElements(context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnpd, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnpd, nQNaNMask, mQNaNMask); + + nNum = context.AddIntrinsic(Intrinsic.X86Blendvpd, nNum, negInfMask, nMask); + mNum = context.AddIntrinsic(Intrinsic.X86Blendvpd, mNum, negInfMask, mMask); + + return context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxpd : Intrinsic.X86Minpd, nNum, mNum); + } + } + + if (scalar) + { + EmitScalarBinaryOpSimd32(context, genericEmit); + } + else + { + EmitVectorBinaryOpSimd32(context, genericEmit); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCmp.cs b/src/ARMeilleure/Instructions/InstEmitSimdCmp.cs new file mode 100644 index 000000000..aab677869 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCmp.cs @@ -0,0 +1,798 @@ +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; + + 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) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitFcmpOrFcmpe(context, signalNaNs: false); + } + else + { + EmitFcmpOrFcmpe(context, signalNaNs: false); + } + } + + public static void Fcmpe_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitFcmpOrFcmpe(context, signalNaNs: true); + } + else + { + 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) + { + static 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 not OpCodeSimdFcond && op.Bit3; + + 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); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs new file mode 100644 index 000000000..1d68bce6b --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs @@ -0,0 +1,437 @@ +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; + + static partial class InstEmit32 + { + public static void Vceq_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.Equal, false); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.Equal, true); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThanOrEqual, false); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThanOrEqual, true); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThan, false); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThan, true); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.LessThanOrEqual, true); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.LessThan, true); + } + else 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) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVcmpOrVcmpe(context, false); + } + else + { + EmitVcmpOrVcmpe(context, false); + } + } + + public static void Vcmpe(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVcmpOrVcmpe(context, true); + } + else + { + 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)); + }); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs b/src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs new file mode 100644 index 000000000..6226e35ae --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs @@ -0,0 +1,115 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Aesd_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesdV, d, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Decrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aese_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AeseV, d, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesenclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Encrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aesimc_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesimcV, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesimc, n); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.InverseMixColumns)), n); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Aesmc_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesmcV, n); + } + else if (Optimizations.UseAesni) + { + Operand roundKey = context.VectorZero(); + + // Inverse Shift Rows, Inverse Sub Bytes, xor 0 so nothing happens + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, n, roundKey); + + // Shift Rows, Sub Bytes, Mix Columns (!), xor 0 so nothing happens + res = context.AddIntrinsic(Intrinsic.X86Aesenc, res, roundKey); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.MixColumns)), n); + } + + context.Copy(GetVec(op.Rd), res); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs new file mode 100644 index 000000000..7a0c981e7 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs @@ -0,0 +1,115 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + partial class InstEmit32 + { + public static void Aesd_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesdV, d, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Decrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aese_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AeseV, d, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesenclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Encrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aesimc_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesimcV, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesimc, n); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.InverseMixColumns)), n); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Aesmc_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesmcV, n); + } + else if (Optimizations.UseAesni) + { + Operand roundKey = context.VectorZero(); + + // Inverse Shift Rows, Inverse Sub Bytes, xor 0 so nothing happens. + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, n, roundKey); + + // Shift Rows, Sub Bytes, Mix Columns (!), xor 0 so nothing happens. + res = context.AddIntrinsic(Intrinsic.X86Aesenc, res, roundKey); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.MixColumns)), n); + } + + context.Copy(GetVecA32(op.Qd), res); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCvt.cs b/src/ARMeilleure/Instructions/InstEmitSimdCvt.cs new file mode 100644 index 000000000..3363a7c77 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCvt.cs @@ -0,0 +1,1890 @@ +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.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + + static partial class InstEmit + { + public static void Fcvt_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0 && op.Opc == 1) // Single -> Double. + { + if (Optimizations.UseSse2) + { + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), n); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + Operand res = context.ConvertToFP(OperandType.FP64, ne); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else if (op.Size == 1 && op.Opc == 0) // Double -> Single. + { + if (Optimizations.UseSse2) + { + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), n); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP64, GetVec(op.Rn), 0); + + Operand res = context.ConvertToFP(OperandType.FP32, ne); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else if (op.Size == 0 && op.Opc == 3) // Single -> Half. + { + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, n, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + res = context.AddIntrinsic(Intrinsic.X86Pslldq, res, Const(14)); // VectorZeroUpper112() + res = context.AddIntrinsic(Intrinsic.X86Psrldq, res, Const(14)); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)), ne); + context.LoadFromContext(); + + res = context.ZeroExtend16(OperandType.I64, res); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, 1)); + } + } + else if (op.Size == 3 && op.Opc == 0) // Half -> Single. + { + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, GetVec(op.Rn)); + res = context.VectorZeroUpper96(res); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, 1); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)), ne); + context.LoadFromContext(); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else if (op.Size == 1 && op.Opc == 3) // Double -> Half. + { + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), n); + res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, res, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP64, GetVec(op.Rn), 0); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert)), ne); + context.LoadFromContext(); + + res = context.ZeroExtend16(OperandType.I64, res); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, 1)); + } + } + else if (op.Size == 3 && op.Opc == 1) // Half -> Double. + { + if (Optimizations.UseF16c) + { + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, GetVec(op.Rn)); + res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), res); + res = context.VectorZeroUpper64(res); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, 1); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert)), ne); + context.LoadFromContext(); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else // Invalid encoding. + { + Debug.Assert(false, $"type == {op.Size} && opc == {op.Opc}"); + } + } + + public static void Fcvtas_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtasGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.ToNearestAway, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1)); + } + } + + public static void Fcvtas_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtasS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearestAway, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: true); + } + } + + public static void Fcvtas_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtasS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearestAway, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: false); + } + } + + public static void Fcvtau_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtauGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.ToNearestAway, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1)); + } + } + + public static void Fcvtau_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtauS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearestAway, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: true); + } + } + + public static void Fcvtau_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtauV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearestAway, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: false); + } + } + + public static void Fcvtl_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtlV); + } + else if (Optimizations.UseSse2 && sizeF == 1) + { + Operand n = GetVec(op.Rn); + + Operand res = op.RegisterSize == RegisterSize.Simd128 ? context.AddIntrinsic(Intrinsic.X86Movhlps, n, n) : n; + res = context.AddIntrinsic(Intrinsic.X86Cvtps2pd, res); + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseF16c && sizeF == 0) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand n = GetVec(op.Rn); + + Operand res = op.RegisterSize == RegisterSize.Simd128 ? context.AddIntrinsic(Intrinsic.X86Movhlps, n, n) : n; + res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, res); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int elems = 4 >> sizeF; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + if (sizeF == 0) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, part + index, 1); + + context.StoreToContext(); + Operand e = context.Call(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)), ne); + context.LoadFromContext(); + + res = context.VectorInsert(res, e, index); + } + else /* if (sizeF == 1) */ + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), part + index); + + Operand e = context.ConvertToFP(OperandType.FP64, ne); + + res = context.VectorInsert(res, e, index); + } + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fcvtms_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtmsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + } + } + + public static void Fcvtms_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtmsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsMinusInfinity, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1), signed: true, scalar: false); + } + } + + public static void Fcvtmu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtmuGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + } + } + + public static void Fcvtn_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpFRd(context, Intrinsic.Arm64FcvtnV); + } + else if (Optimizations.UseSse2 && sizeF == 1) + { + Operand d = GetVec(op.Rd); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 ? Intrinsic.X86Movlhps : Intrinsic.X86Movhlps; + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtpd2ps, GetVec(op.Rn)); + nInt = context.AddIntrinsic(Intrinsic.X86Movlhps, nInt, nInt); + + Operand res = context.VectorZeroUpper64(d); + res = context.AddIntrinsic(movInst, res, nInt); + + context.Copy(d, res); + } + else if (Optimizations.UseF16c && sizeF == 0) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 ? Intrinsic.X86Movlhps : Intrinsic.X86Movhlps; + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, n, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + nInt = context.AddIntrinsic(Intrinsic.X86Movlhps, nInt, nInt); + + Operand res = context.VectorZeroUpper64(d); + res = context.AddIntrinsic(movInst, res, nInt); + + context.Copy(d, res); + } + else + { + OperandType type = sizeF == 0 ? OperandType.FP32 : OperandType.FP64; + + int elems = 4 >> sizeF; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + + if (sizeF == 0) + { + context.StoreToContext(); + Operand e = context.Call(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)), ne); + context.LoadFromContext(); + + res = EmitVectorInsert(context, res, e, part + index, 1); + } + else /* if (sizeF == 1) */ + { + Operand e = context.ConvertToFP(OperandType.FP32, ne); + + res = context.VectorInsert(res, e, part + index); + } + } + + context.Copy(d, res); + } + } + + public static void Fcvtns_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtnsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.ToNearest, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1)); + } + } + + public static void Fcvtns_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtnsS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearest, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: true, scalar: true); + } + } + + public static void Fcvtns_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtnsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearest, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: true, scalar: false); + } + } + + public static void Fcvtnu_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtnuS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearest, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: false, scalar: true); + } + } + + public static void Fcvtnu_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtnuV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearest, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: false, scalar: false); + } + } + + public static void Fcvtps_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtpsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + } + } + + public static void Fcvtpu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtpuGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + } + } + + public static void Fcvtzs_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtzsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => op1); + } + } + + public static void Fcvtzs_Gp_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFToGp(context, Intrinsic.Arm64FcvtzsGpFixed, op.FBits); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzs_Gp_Fixed(context); + } + } + + public static void Fcvtzs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtzsS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsZero, scalar: true); + } + else + { + EmitFcvtz(context, signed: true, scalar: true); + } + } + + public static void Fcvtzs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtzsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: true, scalar: false); + } + } + + public static void Fcvtzs_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64FcvtzsVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: true, scalar: false); + } + } + + public static void Fcvtzu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtzuGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => op1); + } + } + + public static void Fcvtzu_Gp_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFToGp(context, Intrinsic.Arm64FcvtzuGpFixed, op.FBits); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzu_Gp_Fixed(context); + } + } + + public static void Fcvtzu_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtzuS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.TowardsZero, scalar: true); + } + else + { + EmitFcvtz(context, signed: false, scalar: true); + } + } + + public static void Fcvtzu_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtzuV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: false, scalar: false); + } + } + + public static void Fcvtzu_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64FcvtzuVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: false, scalar: false); + } + } + + public static void Scvtf_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFFromGp(context, Intrinsic.Arm64ScvtfGp); + } + else + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + if (op.RegisterSize == RegisterSize.Int32) + { + res = context.SignExtend32(OperandType.I64, res); + } + + res = EmitFPConvert(context, res, op.Size, signed: true); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Scvtf_Gp_Fixed(ArmEmitterContext context) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFFromGp(context, Intrinsic.Arm64ScvtfGpFixed, op.FBits); + } + else + { + Operand res = GetIntOrZR(context, op.Rn); + + if (op.RegisterSize == RegisterSize.Int32) + { + res = context.SignExtend32(OperandType.I64, res); + } + + res = EmitFPConvert(context, res, op.Size, signed: true); + + res = EmitI2fFBitsMul(context, res, op.FBits); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Scvtf_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64ScvtfS); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: true, scalar: true); + } + } + + public static void Scvtf_S_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpF(context, Intrinsic.Arm64ScvtfSFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: true, scalar: true); + } + } + + public static void Scvtf_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64ScvtfV); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: true, scalar: false); + } + } + + public static void Scvtf_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64ScvtfVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: true, scalar: false); + } + } + + public static void Ucvtf_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFFromGp(context, Intrinsic.Arm64UcvtfGp); + } + else + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + res = EmitFPConvert(context, res, op.Size, signed: false); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Ucvtf_Gp_Fixed(ArmEmitterContext context) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFFromGp(context, Intrinsic.Arm64UcvtfGpFixed, op.FBits); + } + else + { + Operand res = GetIntOrZR(context, op.Rn); + + res = EmitFPConvert(context, res, op.Size, signed: false); + + res = EmitI2fFBitsMul(context, res, op.FBits); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Ucvtf_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64UcvtfS); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: false, scalar: true); + } + } + + public static void Ucvtf_S_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpF(context, Intrinsic.Arm64UcvtfSFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: false, scalar: true); + } + } + + public static void Ucvtf_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64UcvtfV); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: false, scalar: false); + } + } + + public static void Ucvtf_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64UcvtfVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: false, scalar: false); + } + } + + private static void EmitFcvt(ArmEmitterContext context, Func1I emit, bool signed, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand n = GetVec(op.Rn); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + OperandType type = sizeF == 0 ? OperandType.FP32 : OperandType.FP64; + + int elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, n, index); + + Operand e = emit(ne); + + if (sizeF == 0) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)); + + e = context.Call(info, e); + + e = context.ZeroExtend32(OperandType.I64, e); + } + else /* if (sizeF == 1) */ + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)); + + e = context.Call(info, e); + } + + res = EmitVectorInsert(context, res, e, index, sizeI); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitFcvtz(ArmEmitterContext context, bool signed, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand n = GetVec(op.Rn); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + OperandType type = sizeF == 0 ? OperandType.FP32 : OperandType.FP64; + + int fBits = GetFBits(context); + + int elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, n, index); + + Operand e = EmitF2iFBitsMul(context, ne, fBits); + + if (sizeF == 0) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)); + + e = context.Call(info, e); + + e = context.ZeroExtend32(OperandType.I64, e); + } + else /* if (sizeF == 1) */ + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)); + + e = context.Call(info, e); + } + + res = EmitVectorInsert(context, res, e, index, sizeI); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitFcvt_s_Gp(ArmEmitterContext context, Func1I emit) + { + EmitFcvt___Gp(context, emit, signed: true); + } + + private static void EmitFcvt_u_Gp(ArmEmitterContext context, Func1I emit) + { + EmitFcvt___Gp(context, emit, signed: false); + } + + private static void EmitFcvt___Gp(ArmEmitterContext context, Func1I emit, bool signed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + Operand res = signed + ? EmitScalarFcvts(context, emit(ne), 0) + : EmitScalarFcvtu(context, emit(ne), 0); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitFcvtzs_Gp_Fixed(ArmEmitterContext context) + { + EmitFcvtz__Gp_Fixed(context, signed: true); + } + + private static void EmitFcvtzu_Gp_Fixed(ArmEmitterContext context) + { + EmitFcvtz__Gp_Fixed(context, signed: false); + } + + private static void EmitFcvtz__Gp_Fixed(ArmEmitterContext context, bool signed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + Operand res = signed + ? EmitScalarFcvts(context, ne, op.FBits) + : EmitScalarFcvtu(context, ne, op.FBits); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitCvtf(ArmEmitterContext context, bool signed, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + int fBits = GetFBits(context); + + int elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorLongExtract(context, op.Rn, index, sizeI); + + Operand e = EmitFPConvert(context, ne, sizeF, signed); + + e = EmitI2fFBitsMul(context, e, fBits); + + res = context.VectorInsert(res, e, index); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static int GetFBits(ArmEmitterContext context) + { + if (context.CurrOp is OpCodeSimdShImm op) + { + return GetImmShr(op); + } + + return 0; + } + + private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, int size, bool signed) + { + Debug.Assert(value.Type == OperandType.I32 || value.Type == OperandType.I64); + Debug.Assert((uint)size < 2); + + OperandType type = size == 0 ? OperandType.FP32 : OperandType.FP64; + + if (signed) + { + return context.ConvertToFP(type, value); + } + else + { + return context.ConvertToFPUI(type, value); + } + } + + private static Operand EmitScalarFcvts(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + value = EmitF2iFBitsMul(context, value, fBits); + + MethodInfo info; + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS32)); + } + else + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)); + } + + return context.Call(info, value); + } + + private static Operand EmitScalarFcvtu(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + value = EmitF2iFBitsMul(context, value, fBits); + + MethodInfo info; + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU32)); + } + else + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)); + } + + return context.Call(info, value); + } + + private static Operand EmitF2iFBitsMul(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + if (fBits == 0) + { + return value; + } + + if (value.Type == OperandType.FP32) + { + return context.Multiply(value, ConstF(MathF.Pow(2f, fBits))); + } + else /* if (value.Type == OperandType.FP64) */ + { + return context.Multiply(value, ConstF(Math.Pow(2d, fBits))); + } + } + + private static Operand EmitI2fFBitsMul(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + if (fBits == 0) + { + return value; + } + + if (value.Type == OperandType.FP32) + { + return context.Multiply(value, ConstF(1f / MathF.Pow(2f, fBits))); + } + else /* if (value.Type == OperandType.FP64) */ + { + return context.Multiply(value, ConstF(1d / Math.Pow(2d, fBits))); + } + } + + public static Operand EmitSse2CvtDoubleToInt64OpF(ArmEmitterContext context, Operand opF, bool scalar) + { + Debug.Assert(opF.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, opF); // opFL + Operand res = context.VectorCreateScalar(longL); + + if (!scalar) + { + Operand opFH = context.AddIntrinsic(Intrinsic.X86Movhlps, res, opF); // res doesn't matter. + Operand longH = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, opFH); + Operand resH = context.VectorCreateScalar(longH); + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static Operand EmitSse2CvtInt64ToDoubleOp(ArmEmitterContext context, Operand op, bool scalar) + { + Debug.Assert(op.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, op); // opL + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtsi2sd, context.VectorZero(), longL); + + if (!scalar) + { + Operand opH = context.AddIntrinsic(Intrinsic.X86Movhlps, res, op); // res doesn't matter. + Operand longH = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, opH); + Operand resH = context.AddIntrinsic(Intrinsic.X86Cvtsi2sd, res, longH); // res doesn't matter. + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static void EmitSse2ScvtfOp(ArmEmitterContext context, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); + } + + 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) */ + { + Operand res = EmitSse2CvtInt64ToDoubleOp(context, n, scalar); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse2UcvtfOp(ArmEmitterContext context, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = scalar // 65536.000f (1 << 16) + ? X86GetScalar(context, 0x47800000) + : 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); + + res = context.AddIntrinsic(Intrinsic.X86Addps, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); + } + + 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) */ + { + Operand mask = scalar // 4294967296.0000000d (1L << 32) + ? X86GetScalar(context, 0x41F0000000000000L) + : X86GetAllElements(context, 0x41F0000000000000L); + + Operand res = context.AddIntrinsic(Intrinsic.X86Psrlq, n, Const(32)); + res = EmitSse2CvtInt64ToDoubleOp(context, res, scalar); + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, mask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Psllq, n, Const(32)); + res2 = context.AddIntrinsic(Intrinsic.X86Psrlq, res2, Const(32)); + res2 = EmitSse2CvtInt64ToDoubleOp(context, res2, scalar); + + res = context.AddIntrinsic(Intrinsic.X86Addpd, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse41FcvtsOpF(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + 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); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 + fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar(context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, nRes); + + if (scalar) + { + dRes = context.VectorZeroUpper96(dRes); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); + + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar(context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong, nRes); + + if (scalar) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + } + + private static void EmitSse41FcvtuOpF(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + 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); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 + fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar(context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + 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); + + Operand nInt2 = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddd, dRes, nInt); + + if (scalar) + { + dRes = context.VectorZeroUpper96(dRes); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar(context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); + + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); + + 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); + + Operand nLong2 = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddq, dRes, nLong); + + if (scalar) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + } + + private static void EmitSse41Fcvts_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if (op.Size == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nInt); + + SetIntOrZR(context, op.Rd, dRes); + } + else /* if (op.Size == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nLong); + + SetIntOrZR(context, op.Rd, dRes); + } + } + + private static void EmitSse41Fcvtu_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if (op.Size == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + 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 = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + 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); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nInt); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); + } + else /* if (op.Size == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + 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 = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + 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); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nLong); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); + } + } + + private static Operand EmitVectorLongExtract(ArmEmitterContext context, int reg, int index, int size) + { + OperandType type = size == 3 ? OperandType.I64 : OperandType.I32; + + return context.VectorExtract(type, GetVec(reg), index); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs new file mode 100644 index 000000000..630e114c4 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs @@ -0,0 +1,858 @@ +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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, unsigned ? Intrinsic.Arm64FcvtzuV : Intrinsic.Arm64FcvtzsV); + } + else 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_V_Fixed(ArmEmitterContext context) + { + OpCode32SimdCvtFFixed op = (OpCode32SimdCvtFFixed)context.CurrOp; + + var toFixed = op.Opc == 1; + int fracBits = op.Fbits; + var unsigned = op.U; + + if (toFixed) // F32 to S32 or U32 (fixed) + { + EmitVectorUnaryOpF32(context, (op1) => + { + var scaledValue = context.Multiply(op1, ConstF(MathF.Pow(2f, fracBits))); + MethodInfo info = unsigned ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)) : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)); + + return context.Call(info, scaledValue); + }); + } + else // S32 or U32 (fixed) to F32 + { + EmitVectorUnaryOpI32(context, (op1) => + { + var floatValue = unsigned ? context.ConvertToFPUI(OperandType.FP32, op1) : context.ConvertToFP(OperandType.FP32, op1); + + return context.Multiply(floatValue, ConstF(1f / MathF.Pow(2f, fracBits))); + }, !unsigned); + } + } + + 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.UseAdvSimd) + { + bool doubleSize = floatSize == OperandType.FP64; + + if (doubleSize) + { + Operand m = GetVecA32(op.Vm >> 1); + + Operand toConvert = InstEmitSimdHelper32Arm64.EmitExtractScalar(context, m, op.Vm, true); + + Intrinsic inst = (unsigned ? Intrinsic.Arm64FcvtzuGp : Intrinsic.Arm64FcvtzsGp) | Intrinsic.Arm64VDouble; + + Operand asInteger = context.AddIntrinsicInt(inst, toConvert); + + InsertScalar(context, op.Vd, asInteger); + } + else + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, unsigned ? Intrinsic.Arm64FcvtzuS : Intrinsic.Arm64FcvtzsS, false); + } + } + else 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) + { + return rm switch + { + 0b00 => FPRoundingMode.ToNearestAway, + 0b01 => FPRoundingMode.ToNearest, + 0b10 => FPRoundingMode.TowardsPlusInfinity, + 0b11 => FPRoundingMode.TowardsMinusInfinity, + _ => throw new ArgumentOutOfRangeException(nameof(rm)), + }; + } + + // 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; + + Intrinsic inst; + + if (Optimizations.UseAdvSimd) + { + bool doubleSize = floatSize == OperandType.FP64; + + if (doubleSize) + { + Operand m = GetVecA32(op.Vm >> 1); + + Operand toConvert = InstEmitSimdHelper32Arm64.EmitExtractScalar(context, m, op.Vm, true); + + if (unsigned) + { + inst = rm switch + { + 0b00 => Intrinsic.Arm64FcvtauGp, + 0b01 => Intrinsic.Arm64FcvtnuGp, + 0b10 => Intrinsic.Arm64FcvtpuGp, + 0b11 => Intrinsic.Arm64FcvtmuGp, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + } + else + { + inst = rm switch + { + 0b00 => Intrinsic.Arm64FcvtasGp, + 0b01 => Intrinsic.Arm64FcvtnsGp, + 0b10 => Intrinsic.Arm64FcvtpsGp, + 0b11 => Intrinsic.Arm64FcvtmsGp, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + } + + Operand asInteger = context.AddIntrinsicInt(inst | Intrinsic.Arm64VDouble, toConvert); + + InsertScalar(context, op.Vd, asInteger); + } + else + { + if (unsigned) + { + inst = rm switch + { + 0b00 => Intrinsic.Arm64FcvtauS, + 0b01 => Intrinsic.Arm64FcvtnuS, + 0b10 => Intrinsic.Arm64FcvtpuS, + 0b11 => Intrinsic.Arm64FcvtmuS, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + } + else + { + inst = rm switch + { + 0b00 => Intrinsic.Arm64FcvtasS, + 0b01 => Intrinsic.Arm64FcvtnsS, + 0b10 => Intrinsic.Arm64FcvtpsS, + 0b11 => Intrinsic.Arm64FcvtmsS, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + } + + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, inst); + } + } + else 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.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(method, src); + context.LoadFromContext(); + context.EnterArmFpMode(); + + 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.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(method, src); + context.LoadFromContext(); + context.EnterArmFpMode(); + + 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.UseAdvSimd) + { + Intrinsic inst = rm switch + { + 0b00 => Intrinsic.Arm64FrintaS, + 0b01 => Intrinsic.Arm64FrintnS, + 0b10 => Intrinsic.Arm64FrintpS, + 0b11 => Intrinsic.Arm64FrintmS, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, inst); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintaS); + } + else + { + EmitVectorUnaryOpF32(context, (m) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, m)); + } + } + + // VRINTM (vector). + public static void Vrintm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintmS); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintnS); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintpS); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintzS); + } + else 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) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintxS); + } + else + { + 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); + } + } + }); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHash.cs b/src/ARMeilleure/Instructions/InstEmitSimdHash.cs new file mode 100644 index 000000000..aee12d7dc --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHash.cs @@ -0,0 +1,147 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + #region "Sha1" + public static void Sha1c_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashChoose)), d, ne, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1h_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.FixedRotate)), ne); + + context.Copy(GetVec(op.Rd), context.VectorCreateScalar(res)); + } + + public static void Sha1m_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashMajority)), d, ne, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1p_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashParity)), d, ne, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1su0_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart1)), d, n, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1su1_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart2)), d, n); + + context.Copy(GetVec(op.Rd), res); + } + #endregion + + #region "Sha256" + public static void Sha256h_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, d, n, m, part2: false); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha256h2_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, n, d, m, part2: true); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha256su0_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res = InstEmitSimdHashHelper.EmitSha256su0(context, d, n); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha256su1_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = InstEmitSimdHashHelper.EmitSha256su1(context, d, n, m); + + context.Copy(GetVec(op.Rd), res); + } + #endregion + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHash32.cs b/src/ARMeilleure/Instructions/InstEmitSimdHash32.cs new file mode 100644 index 000000000..c2bb951ab --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHash32.cs @@ -0,0 +1,64 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + #region "Sha256" + public static void Sha256h_V(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, d, n, m, part2: false); + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Sha256h2_V(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, n, d, m, part2: true); + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Sha256su0_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256su0(context, d, m); + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Sha256su1_V(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256su1(context, d, n, m); + + context.Copy(GetVecA32(op.Qd), res); + } + #endregion + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs b/src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs new file mode 100644 index 000000000..a672b159f --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs @@ -0,0 +1,56 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitSimdHashHelper + { + public static Operand EmitSha256h(ArmEmitterContext context, Operand x, Operand y, Operand w, bool part2) + { + if (Optimizations.UseSha) + { + Operand src1 = context.AddIntrinsic(Intrinsic.X86Shufps, y, x, Const(0xbb)); + Operand src2 = context.AddIntrinsic(Intrinsic.X86Shufps, y, x, Const(0x11)); + Operand w2 = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, w, w); + + Operand round2 = context.AddIntrinsic(Intrinsic.X86Sha256Rnds2, src1, src2, w); + Operand round4 = context.AddIntrinsic(Intrinsic.X86Sha256Rnds2, src2, round2, w2); + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, round4, round2, Const(part2 ? 0x11 : 0xbb)); + + return res; + } + + String method = part2 ? nameof(SoftFallback.HashUpper) : nameof(SoftFallback.HashLower); + return context.Call(typeof(SoftFallback).GetMethod(method), x, y, w); + } + + public static Operand EmitSha256su0(ArmEmitterContext context, Operand x, Operand y) + { + if (Optimizations.UseSha) + { + return context.AddIntrinsic(Intrinsic.X86Sha256Msg1, x, y); + } + + return context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart1)), x, y); + } + + public static Operand EmitSha256su1(ArmEmitterContext context, Operand x, Operand y, Operand z) + { + if (Optimizations.UseSha && Optimizations.UseSsse3) + { + Operand extr = context.AddIntrinsic(Intrinsic.X86Palignr, z, y, Const(4)); + Operand tmp = context.AddIntrinsic(Intrinsic.X86Paddd, extr, x); + + Operand res = context.AddIntrinsic(Intrinsic.X86Sha256Msg2, tmp, z); + + return res; + } + + return context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart2)), x, y, z); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper.cs new file mode 100644 index 000000000..abd0d9acc --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper.cs @@ -0,0 +1,2108 @@ +using ARMeilleure.CodeGen.X86; +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.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + using Func2I = Func; + using Func3I = Func; + + static class InstEmitSimdHelper + { + #region "Masks" + public static readonly long[] EvenMasks = new long[] + { + 14L << 56 | 12L << 48 | 10L << 40 | 08L << 32 | 06L << 24 | 04L << 16 | 02L << 8 | 00L << 0, // B + 13L << 56 | 12L << 48 | 09L << 40 | 08L << 32 | 05L << 24 | 04L << 16 | 01L << 8 | 00L << 0, // H + 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 03L << 24 | 02L << 16 | 01L << 8 | 00L << 0, // S + }; + + public static readonly long[] OddMasks = new long[] + { + 15L << 56 | 13L << 48 | 11L << 40 | 09L << 32 | 07L << 24 | 05L << 16 | 03L << 8 | 01L << 0, // B + 15L << 56 | 14L << 48 | 11L << 40 | 10L << 32 | 07L << 24 | 06L << 16 | 03L << 8 | 02L << 0, // H + 15L << 56 | 14L << 48 | 13L << 40 | 12L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0, // S + }; + + public const long ZeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0; + + public static ulong X86GetGf2p8LogicalShiftLeft(int shift) + { + ulong identity = (0b00000001UL << 56) | (0b00000010UL << 48) | (0b00000100UL << 40) | (0b00001000UL << 32) | + (0b00010000UL << 24) | (0b00100000UL << 16) | (0b01000000UL << 8) | (0b10000000UL << 0); + + return shift >= 0 ? identity >> (shift * 8) : identity << (-shift * 8); + } + #endregion + + #region "X86 SSE Intrinsics" + public static readonly Intrinsic[] X86PaddInstruction = new Intrinsic[] + { + Intrinsic.X86Paddb, + Intrinsic.X86Paddw, + Intrinsic.X86Paddd, + Intrinsic.X86Paddq, + }; + + public static readonly Intrinsic[] X86PcmpeqInstruction = new Intrinsic[] + { + Intrinsic.X86Pcmpeqb, + Intrinsic.X86Pcmpeqw, + Intrinsic.X86Pcmpeqd, + Intrinsic.X86Pcmpeqq, + }; + + public static readonly Intrinsic[] X86PcmpgtInstruction = new Intrinsic[] + { + Intrinsic.X86Pcmpgtb, + Intrinsic.X86Pcmpgtw, + Intrinsic.X86Pcmpgtd, + Intrinsic.X86Pcmpgtq, + }; + + public static readonly Intrinsic[] X86PmaxsInstruction = new Intrinsic[] + { + Intrinsic.X86Pmaxsb, + Intrinsic.X86Pmaxsw, + Intrinsic.X86Pmaxsd, + }; + + public static readonly Intrinsic[] X86PmaxuInstruction = new Intrinsic[] + { + Intrinsic.X86Pmaxub, + Intrinsic.X86Pmaxuw, + Intrinsic.X86Pmaxud, + }; + + public static readonly Intrinsic[] X86PminsInstruction = new Intrinsic[] + { + Intrinsic.X86Pminsb, + Intrinsic.X86Pminsw, + Intrinsic.X86Pminsd, + }; + + public static readonly Intrinsic[] X86PminuInstruction = new Intrinsic[] + { + Intrinsic.X86Pminub, + Intrinsic.X86Pminuw, + Intrinsic.X86Pminud, + }; + + public static readonly Intrinsic[] X86PmovsxInstruction = new Intrinsic[] + { + Intrinsic.X86Pmovsxbw, + Intrinsic.X86Pmovsxwd, + Intrinsic.X86Pmovsxdq, + }; + + public static readonly Intrinsic[] X86PmovzxInstruction = new Intrinsic[] + { + Intrinsic.X86Pmovzxbw, + Intrinsic.X86Pmovzxwd, + Intrinsic.X86Pmovzxdq, + }; + + public static readonly Intrinsic[] X86PsllInstruction = new Intrinsic[] + { + 0, + Intrinsic.X86Psllw, + Intrinsic.X86Pslld, + Intrinsic.X86Psllq, + }; + + public static readonly Intrinsic[] X86PsraInstruction = new Intrinsic[] + { + 0, + Intrinsic.X86Psraw, + Intrinsic.X86Psrad, + }; + + public static readonly Intrinsic[] X86PsrlInstruction = new Intrinsic[] + { + 0, + Intrinsic.X86Psrlw, + Intrinsic.X86Psrld, + Intrinsic.X86Psrlq, + }; + + public static readonly Intrinsic[] X86PsubInstruction = new Intrinsic[] + { + Intrinsic.X86Psubb, + Intrinsic.X86Psubw, + Intrinsic.X86Psubd, + Intrinsic.X86Psubq, + }; + + public static readonly Intrinsic[] X86PunpckhInstruction = new Intrinsic[] + { + Intrinsic.X86Punpckhbw, + Intrinsic.X86Punpckhwd, + Intrinsic.X86Punpckhdq, + Intrinsic.X86Punpckhqdq, + }; + + public static readonly Intrinsic[] X86PunpcklInstruction = new Intrinsic[] + { + Intrinsic.X86Punpcklbw, + Intrinsic.X86Punpcklwd, + Intrinsic.X86Punpckldq, + Intrinsic.X86Punpcklqdq, + }; + #endregion + + public static void EnterArmFpMode(EmitterContext context, Func getFpFlag) + { + if (Optimizations.UseSse2) + { + Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr); + + Operand fzTrue = getFpFlag(FPState.FzFlag); + Operand r0True = getFpFlag(FPState.RMode0Flag); + Operand r1True = getFpFlag(FPState.RMode1Flag); + + mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo))); + + mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(fzTrue, Const((int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Um | Mxcsr.Dm)), Const(0))); + + // X86 round modes in order: nearest, negative, positive, zero + // ARM round modes in order: nearest, positive, negative, zero + // Read the bits backwards to correct this. + + mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r0True, Const((int)Mxcsr.Rhi), Const(0))); + mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r1True, Const((int)Mxcsr.Rlo), Const(0))); + + context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr); + } + else if (Optimizations.UseAdvSimd) + { + Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr); + + Operand fzTrue = getFpFlag(FPState.FzFlag); + Operand r0True = getFpFlag(FPState.RMode0Flag); + Operand r1True = getFpFlag(FPState.RMode1Flag); + + fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1))); + + fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(fzTrue, Const((int)FPCR.Fz), Const(0))); + fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r0True, Const((int)FPCR.RMode0), Const(0))); + fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r1True, Const((int)FPCR.RMode1), Const(0))); + + context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr); + + // TODO: Restore FPSR + } + } + + public static void ExitArmFpMode(EmitterContext context, Action setFpFlag) + { + if (Optimizations.UseSse2) + { + Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr); + + // Unset round mode (to nearest) and ftz. + mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo))); + + context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr); + + // Status flags would be stored here if they were used. + } + else if (Optimizations.UseAdvSimd) + { + Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr); + + // Unset round mode (to nearest) and fz. + fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1))); + + context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr); + + // TODO: Store FPSR + } + } + + public static int GetImmShl(OpCodeSimdShImm op) + { + return op.Imm - (8 << op.Size); + } + + public static int GetImmShr(OpCodeSimdShImm op) + { + return (8 << (op.Size + 1)) - op.Imm; + } + + public static Operand X86GetScalar(ArmEmitterContext context, float value) + { + return X86GetScalar(context, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand X86GetScalar(ArmEmitterContext context, double value) + { + return X86GetScalar(context, BitConverter.DoubleToInt64Bits(value)); + } + + public static Operand X86GetScalar(ArmEmitterContext context, int value) + { + return context.VectorCreateScalar(Const(value)); + } + + public static Operand X86GetScalar(ArmEmitterContext context, long value) + { + return context.VectorCreateScalar(Const(value)); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, float value) + { + return X86GetAllElements(context, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, double value) + { + return X86GetAllElements(context, BitConverter.DoubleToInt64Bits(value)); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, short value) + { + ulong value1 = (ushort)value; + ulong value2 = value1 << 16 | value1; + ulong value4 = value2 << 32 | value2; + + return X86GetAllElements(context, (long)value4); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, int value) + { + Operand vector = context.VectorCreateScalar(Const(value)); + + vector = context.AddIntrinsic(Intrinsic.X86Shufps, vector, vector, Const(0)); + + return vector; + } + + public static Operand X86GetAllElements(ArmEmitterContext context, long value) + { + Operand vector = context.VectorCreateScalar(Const(value)); + + vector = context.AddIntrinsic(Intrinsic.X86Movlhps, vector, vector); + + return vector; + } + + public static Operand X86GetElements(ArmEmitterContext context, long e1, long e0) + { + return X86GetElements(context, (ulong)e1, (ulong)e0); + } + + public static Operand X86GetElements(ArmEmitterContext context, ulong e1, ulong e0) + { + Operand vector0 = context.VectorCreateScalar(Const(e0)); + Operand vector1 = context.VectorCreateScalar(Const(e1)); + + return context.AddIntrinsic(Intrinsic.X86Punpcklqdq, vector0, vector1); + } + + public static int X86GetRoundControl(FPRoundingMode roundMode) + { + return roundMode switch + { +#pragma warning disable IDE0055 // Disable formatting + FPRoundingMode.ToNearest => 8 | 0, // even + FPRoundingMode.TowardsPlusInfinity => 8 | 2, + FPRoundingMode.TowardsMinusInfinity => 8 | 1, + FPRoundingMode.TowardsZero => 8 | 3, + _ => throw new ArgumentException($"Invalid rounding mode \"{roundMode}\"."), +#pragma warning restore IDE0055 + }; + } + + public static Operand EmitSse41RoundToNearestWithTiesToAwayOpF(ArmEmitterContext context, Operand n, bool scalar) + { + Debug.Assert(n.Type == OperandType.V128); + + Operand nCopy = context.Copy(n); + + Operand rC = Const(X86GetRoundControl(FPRoundingMode.TowardsZero)); + + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + Operand signMask = scalar ? X86GetScalar(context, int.MinValue) : X86GetAllElements(context, int.MinValue); + signMask = context.AddIntrinsic(Intrinsic.X86Pand, signMask, nCopy); + + // 0x3EFFFFFF == BitConverter.SingleToInt32Bits(0.5f) - 1 + Operand valueMask = scalar ? X86GetScalar(context, 0x3EFFFFFF) : X86GetAllElements(context, 0x3EFFFFFF); + valueMask = context.AddIntrinsic(Intrinsic.X86Por, valueMask, signMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Addss : Intrinsic.X86Addps, nCopy, valueMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Roundss : Intrinsic.X86Roundps, nCopy, rC); + } + else + { + Operand signMask = scalar ? X86GetScalar(context, long.MinValue) : X86GetAllElements(context, long.MinValue); + signMask = context.AddIntrinsic(Intrinsic.X86Pand, signMask, nCopy); + + // 0x3FDFFFFFFFFFFFFFL == BitConverter.DoubleToInt64Bits(0.5d) - 1L + Operand valueMask = scalar ? X86GetScalar(context, 0x3FDFFFFFFFFFFFFFL) : X86GetAllElements(context, 0x3FDFFFFFFFFFFFFFL); + valueMask = context.AddIntrinsic(Intrinsic.X86Por, valueMask, signMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Addsd : Intrinsic.X86Addpd, nCopy, valueMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Roundsd : Intrinsic.X86Roundpd, nCopy, rC); + } + + return nCopy; + } + + public static Operand EmitCountSetBits8(ArmEmitterContext context, Operand op) // "size" is 8 (SIMD&FP Inst.). + { + Debug.Assert(op.Type == OperandType.I32 || op.Type == OperandType.I64); + + Operand op0 = context.Subtract(op, context.BitwiseAnd(context.ShiftRightUI(op, Const(1)), Const(op.Type, 0x55L))); + + Operand c1 = Const(op.Type, 0x33L); + Operand op1 = context.Add(context.BitwiseAnd(context.ShiftRightUI(op0, Const(2)), c1), context.BitwiseAnd(op0, c1)); + + return context.BitwiseAnd(context.Add(op1, context.ShiftRightUI(op1, Const(4))), Const(op.Type, 0x0fL)); + } + + public static void EmitScalarUnaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n); + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitScalarBinaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n, m); + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorUnaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static Operand EmitUnaryMathCall(ArmEmitterContext context, string name, Operand n) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(MathF).GetMethod(name, new Type[] { typeof(float) }) + : typeof(Math).GetMethod(name, new Type[] { typeof(double) }); + + return context.Call(info, n); + } + + public static Operand EmitRoundMathCall(ArmEmitterContext context, MidpointRounding roundMode, Operand n) + { + IOpCodeSimd op = (IOpCodeSimd)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)); + } + + public static Operand EmitGetRoundingMode(ArmEmitterContext context) + { + Operand rMode = context.ShiftLeft(GetFpFlag(FPState.RMode1Flag), Const(1)); + rMode = context.BitwiseOr(rMode, GetFpFlag(FPState.RMode0Flag)); + + return rMode; + } + + public static Operand EmitRoundByRMode(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.FP32 || op.Type == OperandType.FP64); + + Operand lbl1 = Label(); + Operand lbl2 = Label(); + Operand lbl3 = Label(); + Operand lblEnd = Label(); + + Operand rN = Const((int)FPRoundingMode.ToNearest); + Operand rP = Const((int)FPRoundingMode.TowardsPlusInfinity); + Operand rM = Const((int)FPRoundingMode.TowardsMinusInfinity); + + Operand res = context.AllocateLocal(op.Type); + + Operand rMode = EmitGetRoundingMode(context); + + context.BranchIf(lbl1, rMode, rN, Comparison.NotEqual); + context.Copy(res, EmitRoundMathCall(context, MidpointRounding.ToEven, op)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lbl2, rMode, rP, Comparison.NotEqual); + context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Ceiling), op)); + context.Branch(lblEnd); + + context.MarkLabel(lbl2); + context.BranchIf(lbl3, rMode, rM, Comparison.NotEqual); + context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Floor), op)); + context.Branch(lblEnd); + + context.MarkLabel(lbl3); + context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Truncate), op)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + public static Operand EmitSoftFloatCall(ArmEmitterContext context, string name, params Operand[] callArgs) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(SoftFloat32).GetMethod(name) + : typeof(SoftFloat64).GetMethod(name); + + context.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(info, callArgs); + context.LoadFromContext(); + context.EnterArmFpMode(); + + return res; + } + + public static void EmitScalarBinaryOpByElemF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(n, m), 0)); + } + + public static void EmitScalarTernaryOpByElemF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand d = context.VectorExtract(type, GetVec(op.Rd), 0); + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(d, n, m), 0)); + } + + public static void EmitScalarUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = EmitVectorExtractSx(context, op.Rn, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = EmitVectorExtractSx(context, op.Rn, 0, op.Size); + Operand m = EmitVectorExtractSx(context, op.Rm, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n, m), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarUnaryOpZx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + Operand m = EmitVectorExtractZx(context, op.Rm, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n, m), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarTernaryOpZx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = EmitVectorExtractZx(context, op.Rd, 0, op.Size); + Operand n = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + Operand m = EmitVectorExtractZx(context, op.Rm, 0, op.Size); + + d = EmitVectorInsert(context, context.VectorZero(), emit(d, n, m), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarUnaryOpF(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(n), 0)); + } + + public static void EmitScalarBinaryOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(n, m), 0)); + } + + public static void EmitScalarTernaryRaOpF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand a = context.VectorExtract(type, GetVec(op.Ra), 0); + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(a, n, m), 0)); + } + + public static void EmitVectorUnaryOpF(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + + res = context.VectorInsert(res, emit(ne), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), index); + + res = context.VectorInsert(res, emit(ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVec(op.Rd), index); + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), index); + + res = context.VectorInsert(res, emit(de, ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpByElemF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + res = context.VectorInsert(res, emit(ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpByElemF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVec(op.Rd), index); + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + res = context.VectorInsert(res, emit(de, ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractSx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpSx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractSx(context, op.Rd, index, op.Size); + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractSx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorUnaryOpZx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> 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); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpZx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpByElemSx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtractSx(context, op.Rm, op.Index, op.Size); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpByElemZx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtractZx(context, op.Rm, op.Index, op.Size); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpByElemZx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtractZx(context, op.Rm, op.Index, op.Size); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorImmUnaryOp(ArmEmitterContext context, Func1I emit) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + Operand imm = Const(op.Immediate); + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, emit(imm), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorImmBinaryOp(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + Operand imm = Const(op.Immediate); + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, imm), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenRmBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRmBinaryOp(context, emit, signed: true); + } + + public static void EmitVectorWidenRmBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRmBinaryOp(context, emit, signed: false); + } + + private static void EmitVectorWidenRmBinaryOp(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size + 1, signed); + Operand me = EmitVectorExtract(context, op.Rm, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenRnRmBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRnRmBinaryOp(context, emit, signed: true); + } + + public static void EmitVectorWidenRnRmBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRnRmBinaryOp(context, emit, signed: false); + } + + private static void EmitVectorWidenRnRmBinaryOp(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + Operand me = EmitVectorExtract(context, op.Rm, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenRnRmTernaryOpSx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenRnRmTernaryOp(context, emit, signed: true); + } + + public static void EmitVectorWidenRnRmTernaryOpZx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenRnRmTernaryOp(context, emit, signed: false); + } + + private static void EmitVectorWidenRnRmTernaryOp(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size + 1, signed); + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + Operand me = EmitVectorExtract(context, op.Rm, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenBinaryOpByElemSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenBinaryOpByElem(context, emit, signed: true); + } + + public static void EmitVectorWidenBinaryOpByElemZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenBinaryOpByElem(context, emit, signed: false); + } + + private static void EmitVectorWidenBinaryOpByElem(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenTernaryOpByElemSx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenTernaryOpByElem(context, emit, signed: true); + } + + public static void EmitVectorWidenTernaryOpByElemZx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenTernaryOpByElem(context, emit, signed: false); + } + + private static void EmitVectorWidenTernaryOpByElem(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size + 1, signed); + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorPairwiseOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorPairwiseOp(context, emit, signed: true); + } + + public static void EmitVectorPairwiseOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorPairwiseOp(context, emit, signed: false); + } + + private static void EmitVectorPairwiseOp(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand n0 = EmitVectorExtract(context, op.Rn, pairIndex, op.Size, signed); + Operand n1 = EmitVectorExtract(context, op.Rn, pairIndex + 1, op.Size, signed); + + Operand m0 = EmitVectorExtract(context, op.Rm, pairIndex, op.Size, signed); + Operand m1 = EmitVectorExtract(context, op.Rm, pairIndex + 1, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(n0, n1), index, op.Size); + res = EmitVectorInsert(context, res, emit(m0, m1), pairs + index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitSsse3VectorPairwiseOp(ArmEmitterContext context, Intrinsic[] inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand zeroEvenMask = X86GetElements(context, ZeroMask, EvenMasks[op.Size]); + Operand zeroOddMask = X86GetElements(context, ZeroMask, OddMasks[op.Size]); + + Operand mN = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); // m:n + + Operand left = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroEvenMask); // 0:even from m:n + Operand right = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroOddMask); // 0:odd from m:n + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else if (op.Size < 3) + { + Operand oddEvenMask = X86GetElements(context, OddMasks[op.Size], EvenMasks[op.Size]); + + Operand oddEvenN = context.AddIntrinsic(Intrinsic.X86Pshufb, n, oddEvenMask); // odd:even from n + Operand oddEvenM = context.AddIntrinsic(Intrinsic.X86Pshufb, m, oddEvenMask); // odd:even from m + + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, oddEvenN, oddEvenM); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, oddEvenN, oddEvenM); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else + { + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, n, m); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[3], left, right)); + } + } + + public static void EmitVectorAcrossVectorOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: true, isLong: false); + } + + public static void EmitVectorAcrossVectorOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: false, isLong: false); + } + + public static void EmitVectorLongAcrossVectorOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: true, isLong: true); + } + + public static void EmitVectorLongAcrossVectorOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: false, isLong: true); + } + + private static void EmitVectorAcrossVectorOp( + ArmEmitterContext context, + Func2I emit, + bool signed, + bool isLong) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int elems = op.GetBytesCount() >> op.Size; + + Operand res = EmitVectorExtract(context, op.Rn, 0, op.Size, signed); + + for (int index = 1; index < elems; index++) + { + Operand n = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + res = emit(res, n); + } + + int size = isLong ? op.Size + 1 : op.Size; + + Operand d = EmitVectorInsert(context, context.VectorZero(), res, 0, size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitVectorAcrossVectorOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Debug.Assert((op.Size & 1) == 0 && op.RegisterSize == RegisterSize.Simd128); + + Operand res = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + for (int index = 1; index < 4; index++) + { + Operand n = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), index); + + res = emit(res, n); + } + + Operand d = context.VectorInsert(context.VectorZero(), res, 0); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitSse2VectorAcrossVectorOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Debug.Assert((op.Size & 1) == 0 && op.RegisterSize == RegisterSize.Simd128); + + const int SM0 = 0 << 6 | 0 << 4 | 0 << 2 | 0 << 0; + const int SM1 = 1 << 6 | 1 << 4 | 1 << 2 | 1 << 0; + const int SM2 = 2 << 6 | 2 << 4 | 2 << 2 | 2 << 0; + const int SM3 = 3 << 6 | 3 << 4 | 3 << 2 | 3 << 0; + + Operand nCopy = context.Copy(GetVec(op.Rn)); + + Operand part0 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(SM0)); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(SM1)); + Operand part2 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(SM2)); + Operand part3 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(SM3)); + + Operand res = emit(emit(part0, part1), emit(part2, part3)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + + public static void EmitScalarPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand ne0 = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand ne1 = context.VectorExtract(type, GetVec(op.Rn), 1); + + Operand res = context.VectorInsert(context.VectorZero(), emit(ne0, ne1), 0); + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitSse2ScalarPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand op0, op1; + + if ((op.Size & 1) == 0) + { + const int SM0 = 2 << 6 | 2 << 4 | 2 << 2 | 0 << 0; + const int SM1 = 2 << 6 | 2 << 4 | 2 << 2 | 1 << 0; + + Operand zeroN = context.VectorZeroUpper64(n); + + op0 = context.AddIntrinsic(Intrinsic.X86Pshufd, zeroN, Const(SM0)); + op1 = context.AddIntrinsic(Intrinsic.X86Pshufd, zeroN, Const(SM1)); + } + else /* if ((op.Size & 1) == 1) */ + { + Operand zero = context.VectorZero(); + + op0 = context.AddIntrinsic(Intrinsic.X86Movlhps, n, zero); + op1 = context.AddIntrinsic(Intrinsic.X86Movhlps, zero, n); + } + + context.Copy(GetVec(op.Rd), emit(op0, op1)); + } + + public static void EmitVectorPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int pairs = op.GetPairsCount() >> sizeF + 2; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand n0 = context.VectorExtract(type, GetVec(op.Rn), pairIndex); + Operand n1 = context.VectorExtract(type, GetVec(op.Rn), pairIndex + 1); + + Operand m0 = context.VectorExtract(type, GetVec(op.Rm), pairIndex); + Operand m1 = context.VectorExtract(type, GetVec(op.Rm), pairIndex + 1); + + res = context.VectorInsert(res, emit(n0, n1), index); + res = context.VectorInsert(res, emit(m0, m1), pairs + index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitSse2VectorPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand nCopy = context.Copy(GetVec(op.Rn)); + Operand mCopy = context.Copy(GetVec(op.Rm)); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand unpck = context.AddIntrinsic(Intrinsic.X86Unpcklps, nCopy, mCopy); + + Operand zero = context.VectorZero(); + + Operand part0 = context.AddIntrinsic(Intrinsic.X86Movlhps, unpck, zero); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Movhlps, zero, unpck); + + context.Copy(GetVec(op.Rd), emit(part0, part1)); + } + else /* if (op.RegisterSize == RegisterSize.Simd128) */ + { + const int SM0 = 2 << 6 | 0 << 4 | 2 << 2 | 0 << 0; + const int SM1 = 3 << 6 | 1 << 4 | 3 << 2 | 1 << 0; + + Operand part0 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, mCopy, Const(SM0)); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, mCopy, Const(SM1)); + + context.Copy(GetVec(op.Rd), emit(part0, part1)); + } + } + else /* if (sizeF == 1) */ + { + Operand part0 = context.AddIntrinsic(Intrinsic.X86Unpcklpd, nCopy, mCopy); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Unpckhpd, nCopy, mCopy); + + context.Copy(GetVec(op.Rd), emit(part0, part1)); + } + } + + public enum CmpCondition + { + // Legacy Sse. + Equal = 0, // Ordered, non-signaling. + LessThan = 1, // Ordered, signaling. + LessThanOrEqual = 2, // Ordered, signaling. + UnorderedQ = 3, // Non-signaling. + NotLessThan = 5, // Unordered, signaling. + NotLessThanOrEqual = 6, // Unordered, signaling. + OrderedQ = 7, // Non-signaling. + + // Vex. + GreaterThanOrEqual = 13, // Ordered, signaling. + GreaterThan = 14, // Ordered, signaling. + OrderedS = 23, // Signaling. + } + + [Flags] + public enum SaturatingFlags + { + None = 0, + + ByElem = 1 << 0, + Scalar = 1 << 1, + Signed = 1 << 2, + + Add = 1 << 3, + Sub = 1 << 4, + + Accumulate = 1 << 5, + } + + public static void EmitScalarSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + EmitSaturatingUnaryOpSx(context, emit, SaturatingFlags.Scalar | SaturatingFlags.Signed); + } + + public static void EmitVectorSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + EmitSaturatingUnaryOpSx(context, emit, SaturatingFlags.Signed); + } + + public static void EmitSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit, SaturatingFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + bool scalar = (flags & SaturatingFlags.Scalar) != 0; + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand de; + + if (op.Size <= 2) + { + de = EmitSignedSrcSatQ(context, emit(ne), op.Size, signedDst: true); + } + else /* if (op.Size == 3) */ + { + de = EmitUnarySignedSatQAbsOrNeg(context, emit(ne)); + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitScalarSaturatingBinaryOpSx(ArmEmitterContext context, Func2I emit = null, SaturatingFlags flags = SaturatingFlags.None) + { + EmitSaturatingBinaryOp(context, emit, SaturatingFlags.Scalar | SaturatingFlags.Signed | flags); + } + + public static void EmitScalarSaturatingBinaryOpZx(ArmEmitterContext context, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, SaturatingFlags.Scalar | flags); + } + + public static void EmitVectorSaturatingBinaryOpSx(ArmEmitterContext context, Func2I emit = null, SaturatingFlags flags = SaturatingFlags.None) + { + EmitSaturatingBinaryOp(context, emit, SaturatingFlags.Signed | flags); + } + + public static void EmitVectorSaturatingBinaryOpZx(ArmEmitterContext context, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, flags); + } + + public static void EmitVectorSaturatingBinaryOpByElemSx(ArmEmitterContext context, Func2I emit) + { + EmitSaturatingBinaryOp(context, emit, SaturatingFlags.ByElem | SaturatingFlags.Signed); + } + + public static void EmitSaturatingBinaryOp(ArmEmitterContext context, Func2I emit, SaturatingFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + bool byElem = (flags & SaturatingFlags.ByElem) != 0; + bool scalar = (flags & SaturatingFlags.Scalar) != 0; + bool signed = (flags & SaturatingFlags.Signed) != 0; + + bool add = (flags & SaturatingFlags.Add) != 0; + bool sub = (flags & SaturatingFlags.Sub) != 0; + + bool accumulate = (flags & SaturatingFlags.Accumulate) != 0; + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + if (add || sub) + { + for (int index = 0; index < elems; index++) + { + Operand de; + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + Operand me = EmitVectorExtract(context, ((OpCodeSimdReg)op).Rm, index, op.Size, signed); + + if (op.Size <= 2) + { + Operand temp = add ? context.Add(ne, me) : context.Subtract(ne, me); + + de = EmitSignedSrcSatQ(context, temp, op.Size, signedDst: signed); + } + else /* if (op.Size == 3) */ + { + if (add) + { + de = signed ? EmitBinarySignedSatQAdd(context, ne, me) : EmitBinaryUnsignedSatQAdd(context, ne, me); + } + else /* if (sub) */ + { + de = signed ? EmitBinarySignedSatQSub(context, ne, me) : EmitBinaryUnsignedSatQSub(context, ne, me); + } + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + else if (accumulate) + { + for (int index = 0; index < elems; index++) + { + Operand de; + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, !signed); + Operand me = EmitVectorExtract(context, op.Rd, index, op.Size, signed); + + if (op.Size <= 2) + { + Operand temp = context.Add(ne, me); + + de = EmitSignedSrcSatQ(context, temp, op.Size, signedDst: signed); + } + else /* if (op.Size == 3) */ + { + de = signed ? EmitBinarySignedSatQAcc(context, ne, me) : EmitBinaryUnsignedSatQAcc(context, ne, me); + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + else + { + Operand me = default; + + if (byElem) + { + OpCodeSimdRegElem opRegElem = (OpCodeSimdRegElem)op; + + me = EmitVectorExtract(context, opRegElem.Rm, opRegElem.Index, op.Size, signed); + } + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + if (!byElem) + { + me = EmitVectorExtract(context, ((OpCodeSimdReg)op).Rm, index, op.Size, signed); + } + + Operand de = EmitSignedSrcSatQ(context, emit(ne, me), op.Size, signedDst: signed); + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + + context.Copy(GetVec(op.Rd), res); + } + + [Flags] + public enum SaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0, + } + + public static void EmitSaturatingNarrowOp(ArmEmitterContext context, SaturatingNarrowFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + bool scalar = (flags & SaturatingNarrowFlags.Scalar) != 0; + bool signedSrc = (flags & SaturatingNarrowFlags.SignedSrc) != 0; + bool signedDst = (flags & SaturatingNarrowFlags.SignedDst) != 0; + + int elems = !scalar ? 8 >> op.Size : 1; + + int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size + 1, signedSrc); + + Operand temp = signedSrc + ? EmitSignedSrcSatQ(context, ne, op.Size, signedDst) + : EmitUnsignedSrcSatQ(context, ne, op.Size, signedDst); + + res = EmitVectorInsert(context, res, temp, part + index, op.Size); + } + + context.Copy(d, res); + } + + // long SignedSignSatQ(long op, int size); + public static Operand EmitSignedSignSatQ(ArmEmitterContext context, Operand op, int size) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand zeroL = Const(0L); + Operand maxT = Const((1L << (eSize - 1)) - 1L); + Operand minT = Const(-(1L << (eSize - 1))); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroL); + + context.BranchIf(lbl1, op, zeroL, Comparison.LessOrEqual); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, op, zeroL, Comparison.GreaterOrEqual); + context.Copy(res, minT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // private static ulong UnsignedSignSatQ(ulong op, int size); + public static Operand EmitUnsignedSignSatQ(ArmEmitterContext context, Operand op, int size) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lblEnd = Label(); + + Operand zeroUL = Const(0UL); + Operand maxT = Const(ulong.MaxValue >> (64 - eSize)); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroUL); + + context.BranchIf(lblEnd, op, zeroUL, Comparison.LessOrEqualUI); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // TSrc (16bit, 32bit, 64bit; signed) > TDst (8bit, 16bit, 32bit; signed, unsigned). + // long SignedSrcSignedDstSatQ(long op, int size); ulong SignedSrcUnsignedDstSatQ(long op, int size); + public static Operand EmitSignedSrcSatQ(ArmEmitterContext context, Operand op, int sizeDst, bool signedDst) + { + int eSizeDst = 8 << sizeDst; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSizeDst == 8 || eSizeDst == 16 || eSizeDst == 32); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand maxT = signedDst ? Const((1L << (eSizeDst - 1)) - 1L) : Const((1UL << eSizeDst) - 1UL); + Operand minT = signedDst ? Const(-(1L << (eSizeDst - 1))) : Const(0UL); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lbl1, op, maxT, Comparison.LessOrEqual); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, op, minT, Comparison.GreaterOrEqual); + context.Copy(res, minT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // TSrc (16bit, 32bit, 64bit; unsigned) > TDst (8bit, 16bit, 32bit; signed, unsigned). + // long UnsignedSrcSignedDstSatQ(ulong op, int size); ulong UnsignedSrcUnsignedDstSatQ(ulong op, int size); + public static Operand EmitUnsignedSrcSatQ(ArmEmitterContext context, Operand op, int sizeDst, bool signedDst) + { + int eSizeDst = 8 << sizeDst; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSizeDst == 8 || eSizeDst == 16 || eSizeDst == 32); + + Operand lblEnd = Label(); + + Operand maxT = signedDst ? Const((1L << (eSizeDst - 1)) - 1L) : Const((1UL << eSizeDst) - 1UL); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lblEnd, op, maxT, Comparison.LessOrEqualUI); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long UnarySignedSatQAbsOrNeg(long op); + private static Operand EmitUnarySignedSatQAbsOrNeg(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand minL = Const(long.MinValue); + Operand maxL = Const(long.MaxValue); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lblEnd, op, minL, Comparison.NotEqual); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long BinarySignedSatQAdd(long op1, long op2); + public static Operand EmitBinarySignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand minL = Const(long.MinValue); + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + Operand left = context.BitwiseNot(context.BitwiseExclusiveOr(op1, op2)); + Operand right = context.BitwiseExclusiveOr(op1, add); + context.BranchIf(lblEnd, context.BitwiseAnd(left, right), zeroL, Comparison.GreaterOrEqual); + + Operand isPositive = context.ICompareGreaterOrEqual(op1, zeroL); + context.Copy(res, context.ConditionalSelect(isPositive, maxL, minL)); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // ulong BinaryUnsignedSatQAdd(ulong op1, ulong op2); + public static Operand EmitBinaryUnsignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand maxUL = Const(ulong.MaxValue); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + context.BranchIf(lblEnd, add, op1, Comparison.GreaterOrEqualUI); + context.Copy(res, maxUL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long BinarySignedSatQSub(long op1, long op2); + public static Operand EmitBinarySignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand minL = Const(long.MinValue); + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand sub = context.Subtract(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), sub); + + Operand left = context.BitwiseExclusiveOr(op1, op2); + Operand right = context.BitwiseExclusiveOr(op1, sub); + context.BranchIf(lblEnd, context.BitwiseAnd(left, right), zeroL, Comparison.GreaterOrEqual); + + Operand isPositive = context.ICompareGreaterOrEqual(op1, zeroL); + context.Copy(res, context.ConditionalSelect(isPositive, maxL, minL)); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // ulong BinaryUnsignedSatQSub(ulong op1, ulong op2); + public static Operand EmitBinaryUnsignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand zeroL = Const(0L); + + Operand sub = context.Subtract(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), sub); + + context.BranchIf(lblEnd, op1, op2, Comparison.GreaterOrEqualUI); + context.Copy(res, zeroL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long BinarySignedSatQAcc(ulong op1, long op2); + private static Operand EmitBinarySignedSatQAcc(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lbl1 = Label(); + Operand lbl2 = Label(); + Operand lblEnd = Label(); + + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + context.BranchIf(lbl1, op1, maxL, Comparison.GreaterUI); + Operand notOp2AndRes = context.BitwiseAnd(context.BitwiseNot(op2), add); + context.BranchIf(lblEnd, notOp2AndRes, zeroL, Comparison.GreaterOrEqual); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lbl2, op2, zeroL, Comparison.Less); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl2); + context.BranchIf(lblEnd, add, maxL, Comparison.LessOrEqualUI); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // ulong BinaryUnsignedSatQAcc(long op1, ulong op2); + private static Operand EmitBinaryUnsignedSatQAcc(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand maxUL = Const(ulong.MaxValue); + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + context.BranchIf(lbl1, op1, zeroL, Comparison.Less); + context.BranchIf(lblEnd, add, op1, Comparison.GreaterOrEqualUI); + context.Copy(res, maxUL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, op2, maxL, Comparison.GreaterUI); + context.BranchIf(lblEnd, add, zeroL, Comparison.GreaterOrEqual); + context.Copy(res, zeroL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + public static Operand EmitFloatAbs(ArmEmitterContext context, Operand value, bool single, bool vector) + { + Operand mask; + if (single) + { + mask = vector ? X86GetAllElements(context, -0f) : X86GetScalar(context, -0f); + } + else + { + mask = vector ? X86GetAllElements(context, -0d) : X86GetScalar(context, -0d); + } + + return context.AddIntrinsic(single ? Intrinsic.X86Andnps : Intrinsic.X86Andnpd, mask, value); + } + + public static Operand EmitVectorExtractSx(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract(context, reg, index, size, true); + } + + public static Operand EmitVectorExtractZx(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract(context, reg, index, size, false); + } + + public static Operand EmitVectorExtract(ArmEmitterContext context, int reg, int index, int size, bool signed) + { + ThrowIfInvalid(index, size); + + Operand res = default; + + switch (size) + { + case 0: + res = context.VectorExtract8(GetVec(reg), index); + break; + + case 1: + res = context.VectorExtract16(GetVec(reg), index); + break; + + case 2: + res = context.VectorExtract(OperandType.I32, GetVec(reg), index); + break; + + case 3: + res = context.VectorExtract(OperandType.I64, GetVec(reg), index); + break; + } + + if (signed) + { + switch (size) + { + case 0: + res = context.SignExtend8(OperandType.I64, res); + break; + case 1: + res = context.SignExtend16(OperandType.I64, res); + break; + case 2: + res = context.SignExtend32(OperandType.I64, res); + break; + } + } + else + { + switch (size) + { + case 0: + res = context.ZeroExtend8(OperandType.I64, res); + break; + case 1: + res = context.ZeroExtend16(OperandType.I64, res); + break; + case 2: + res = context.ZeroExtend32(OperandType.I64, res); + break; + } + } + + return res; + } + + public static Operand EmitVectorInsert(ArmEmitterContext context, Operand vector, Operand value, int index, int size) + { + ThrowIfInvalid(index, size); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + switch (size) + { + case 0: + vector = context.VectorInsert8(vector, value, index); + break; + case 1: + vector = context.VectorInsert16(vector, value, index); + break; + case 2: + vector = context.VectorInsert(vector, value, index); + break; + case 3: + vector = context.VectorInsert(vector, value, index); + break; + } + + return vector; + } + + public static void ThrowIfInvalid(int index, int size) + { + if ((uint)size > 3u) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if ((uint)index >= 16u >> size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs new file mode 100644 index 000000000..c1c59b87b --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs @@ -0,0 +1,1291 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + using Func2I = Func; + using Func3I = Func; + + static class InstEmitSimdHelper32 + { + public static (int, int) GetQuadwordAndSubindex(int index, RegisterSize size) + { + return size switch + { + RegisterSize.Simd128 => (index >> 1, 0), + RegisterSize.Simd64 or RegisterSize.Int64 => (index >> 1, index & 1), + RegisterSize.Int32 => (index >> 2, index & 3), + _ => throw new ArgumentException("Unrecognized Vector Register Size."), + }; + } + + public static Operand ExtractScalar(ArmEmitterContext context, OperandType type, int reg) + { + Debug.Assert(type != OperandType.V128); + + if (type == OperandType.FP64 || type == OperandType.I64) + { + // From dreg. + return context.VectorExtract(type, GetVecA32(reg >> 1), reg & 1); + } + else + { + // From sreg. + return context.VectorExtract(type, GetVecA32(reg >> 2), reg & 3); + } + } + + public static void InsertScalar(ArmEmitterContext context, int reg, Operand value) + { + Debug.Assert(value.Type != OperandType.V128); + + Operand vec, insert; + if (value.Type == OperandType.FP64 || value.Type == OperandType.I64) + { + // From dreg. + vec = GetVecA32(reg >> 1); + insert = context.VectorInsert(vec, value, reg & 1); + } + else + { + // From sreg. + vec = GetVecA32(reg >> 2); + insert = context.VectorInsert(vec, value, reg & 3); + } + + context.Copy(vec, insert); + } + + public static Operand ExtractScalar16(ArmEmitterContext context, int reg, bool top) + { + return context.VectorExtract16(GetVecA32(reg >> 2), ((reg & 3) << 1) | (top ? 1 : 0)); + } + + public static void InsertScalar16(ArmEmitterContext context, int reg, bool top, Operand value) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.I32); + + Operand vec, insert; + vec = GetVecA32(reg >> 2); + insert = context.VectorInsert16(vec, value, ((reg & 3) << 1) | (top ? 1 : 0)); + + context.Copy(vec, insert); + } + + public static Operand ExtractElement(ArmEmitterContext context, int reg, int size, bool signed) + { + return EmitVectorExtract32(context, reg >> (4 - size), reg & ((16 >> size) - 1), size, signed); + } + + public static void EmitVectorImmUnaryOp32(ArmEmitterContext context, Func1I emit) + { + IOpCode32SimdImm op = (IOpCode32SimdImm)context.CurrOp; + + Operand imm = Const(op.Immediate); + + int elems = op.Elems; + (int index, int subIndex) = GetQuadwordAndSubindex(op.Vd, op.RegisterSize); + + Operand vec = GetVecA32(index); + Operand res = vec; + + for (int item = 0; item < elems; item++) + { + res = EmitVectorInsert(context, res, emit(imm), item + subIndex * elems, op.Size); + } + + context.Copy(vec, res); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Func1I emit) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(m)); + } + + public static void EmitScalarBinaryOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = ExtractScalar(context, type, op.Vn); + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(n, m)); + } + + public static void EmitScalarBinaryOpI32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.I64 : OperandType.I32; + + if (op.Size < 2) + { + throw new NotSupportedException("Cannot perform a scalar SIMD operation on integers smaller than 32 bits."); + } + + Operand n = ExtractScalar(context, type, op.Vn); + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(n, m)); + } + + public static void EmitScalarTernaryOpF32(ArmEmitterContext context, Func3I emit) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand a = ExtractScalar(context, type, op.Vd); + Operand n = ExtractScalar(context, type, op.Vn); + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(a, n, m)); + } + + public static void EmitVectorUnaryOpF32(ArmEmitterContext context, Func1I emit) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand me = context.VectorExtract(type, GetVecA32(op.Qm), op.Fm + index); + + res = context.VectorInsert(res, emit(me), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> (sizeF + 2); + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + Operand me = context.VectorExtract(type, GetVecA32(op.Qm), op.Fm + index); + + res = context.VectorInsert(res, emit(ne, me), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Func3I emit) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVecA32(op.Qd), op.Fd + index); + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + Operand me = context.VectorExtract(type, GetVecA32(op.Qm), op.Fm + index); + + res = context.VectorInsert(res, emit(de, ne, me), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Integer + + public static void EmitVectorUnaryAccumulateOpI32(ArmEmitterContext context, Func1I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, context.Add(de, emit(me)), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorUnaryOpI32(ArmEmitterContext context, Func1I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryLongOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryWideOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size + 1, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + if (op.Size == 2) + { + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorImmBinaryQdQmOpZx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorImmBinaryQdQmOpI32(context, emit, false); + } + + public static void EmitVectorImmBinaryQdQmOpSx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorImmBinaryQdQmOpI32(context, emit, true); + } + + public static void EmitVectorImmBinaryQdQmOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed); + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorTernaryOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorUnaryOpSx32(ArmEmitterContext context, Func1I emit) + { + EmitVectorUnaryOpI32(context, emit, true); + } + + public static void EmitVectorUnaryOpSx32(ArmEmitterContext context, Func1I emit, bool accumulate) + { + if (accumulate) + { + EmitVectorUnaryAccumulateOpI32(context, emit, true); + } + else + { + EmitVectorUnaryOpI32(context, emit, true); + } + } + + public static void EmitVectorBinaryOpSx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorBinaryOpI32(context, emit, true); + } + + public static void EmitVectorTernaryOpSx32(ArmEmitterContext context, Func3I emit) + { + EmitVectorTernaryOpI32(context, emit, true); + } + + public static void EmitVectorUnaryOpZx32(ArmEmitterContext context, Func1I emit) + { + EmitVectorUnaryOpI32(context, emit, false); + } + + public static void EmitVectorUnaryOpZx32(ArmEmitterContext context, Func1I emit, bool accumulate) + { + if (accumulate) + { + EmitVectorUnaryAccumulateOpI32(context, emit, false); + } + else + { + EmitVectorUnaryOpI32(context, emit, false); + } + } + + public static void EmitVectorBinaryOpZx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorBinaryOpI32(context, emit, false); + } + + public static void EmitVectorTernaryOpZx32(ArmEmitterContext context, Func3I emit) + { + EmitVectorTernaryOpI32(context, emit, false); + } + + // Vector by scalar + + public static void EmitVectorByScalarOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand m = ExtractScalar(context, type, op.Vm); + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + + res = context.VectorInsert(res, emit(ne, m), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorByScalarOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand m = ExtractElement(context, op.Vm, op.Size, signed); + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, m), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorByScalarLongOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand m = ExtractElement(context, op.Vm, op.Size, signed); + + if (op.Size == 2) + { + m = signed ? context.SignExtend32(OperandType.I64, m) : context.ZeroExtend32(OperandType.I64, m); + } + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + } + + res = EmitVectorInsert(context, res, emit(ne, m), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorsByScalarOpF32(ArmEmitterContext context, Func3I emit) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand m = ExtractScalar(context, type, op.Vm); + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVecA32(op.Qd), op.Fd + index); + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + + res = context.VectorInsert(res, emit(de, ne, m), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorsByScalarOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand m = EmitVectorExtract32(context, op.Vm >> (4 - op.Size), op.Vm & ((1 << (4 - op.Size)) - 1), op.Size, signed); + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, m), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Pairwise + + public static void EmitVectorPairwiseOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> (sizeF + 2); + int pairs = elems >> 1; + + Operand res = GetVecA32(op.Qd); + Operand mvec = GetVecA32(op.Qm); + Operand nvec = GetVecA32(op.Qn); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand n1 = context.VectorExtract(type, nvec, op.Fn + pairIndex); + Operand n2 = context.VectorExtract(type, nvec, op.Fn + pairIndex + 1); + + res = context.VectorInsert(res, emit(n1, n2), op.Fd + index); + + Operand m1 = context.VectorExtract(type, mvec, op.Fm + pairIndex); + Operand m2 = context.VectorExtract(type, mvec, op.Fm + pairIndex + 1); + + res = context.VectorInsert(res, emit(m1, m2), op.Fd + index + pairs); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorPairwiseOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand n1 = EmitVectorExtract32(context, op.Qn, op.In + pairIndex, op.Size, signed); + Operand n2 = EmitVectorExtract32(context, op.Qn, op.In + pairIndex + 1, op.Size, signed); + + Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed); + Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(n1, n2), op.Id + index, op.Size); + res = EmitVectorInsert(context, res, emit(m1, m2), op.Id + index + pairs, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorPairwiseLongOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int elems = (op.Q ? 16 : 8) >> op.Size; + int pairs = elems >> 1; + int id = (op.Vd & 1) * pairs; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed); + Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed); + + if (op.Size == 2) + { + m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1); + m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2); + } + + res = EmitVectorInsert(context, res, emit(m1, m2), id + index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Narrow + + public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int elems = 8 >> op.Size; // Size contains the target element size. (for when it becomes a doubleword) + + Operand res = GetVecA32(op.Qd); + int id = (op.Vd & 1) << (3 - op.Size); // Target doubleword base. + + for (int index = 0; index < elems; index++) + { + Operand m = EmitVectorExtract32(context, op.Qm, index, op.Size + 1, signed); + + res = EmitVectorInsert(context, res, emit(m), id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Intrinsic Helpers + + public static Operand EmitMoveDoubleWordToSide(ArmEmitterContext context, Operand input, int originalV, int targetV) + { + Debug.Assert(input.Type == OperandType.V128); + + int originalSide = originalV & 1; + int targetSide = targetV & 1; + + if (originalSide == targetSide) + { + return input; + } + + if (targetSide == 1) + { + return context.AddIntrinsic(Intrinsic.X86Movlhps, input, input); // Low to high. + } + else + { + return context.AddIntrinsic(Intrinsic.X86Movhlps, input, input); // High to low. + } + } + + public static Operand EmitDoubleWordInsert(ArmEmitterContext context, Operand target, Operand value, int targetV) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + int targetSide = targetV & 1; + int shuffleMask = 2; + + if (targetSide == 1) + { + return context.AddIntrinsic(Intrinsic.X86Shufpd, target, value, Const(shuffleMask)); + } + else + { + return context.AddIntrinsic(Intrinsic.X86Shufpd, value, target, Const(shuffleMask)); + } + } + + public static Operand EmitScalarInsert(ArmEmitterContext context, Operand target, Operand value, int reg, bool doubleWidth) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + // Insert from index 0 in value to index in target. + int index = reg & (doubleWidth ? 1 : 3); + + if (doubleWidth) + { + if (index == 1) + { + return context.AddIntrinsic(Intrinsic.X86Movlhps, target, value); // Low to high. + } + else + { + return context.AddIntrinsic(Intrinsic.X86Shufpd, value, target, Const(2)); // Low to low, keep high from original. + } + } + else + { + if (Optimizations.UseSse41) + { + return context.AddIntrinsic(Intrinsic.X86Insertps, target, value, Const(index << 4)); + } + else + { + target = EmitSwapScalar(context, target, index, doubleWidth); // Swap value to replace into element 0. + target = context.AddIntrinsic(Intrinsic.X86Movss, target, value); // Move the value into element 0 of the vector. + return EmitSwapScalar(context, target, index, doubleWidth); // Swap new value back to the correct index. + } + } + } + + public static Operand EmitSwapScalar(ArmEmitterContext context, Operand target, int reg, bool doubleWidth) + { + // Index into 0, 0 into index. This swap happens at the start of an A32 scalar op if required. + int index = reg & (doubleWidth ? 1 : 3); + if (index == 0) + { + return target; + } + + if (doubleWidth) + { + int shuffleMask = 1; // Swap top and bottom. (b0 = 1, b1 = 0) + return context.AddIntrinsic(Intrinsic.X86Shufpd, target, target, Const(shuffleMask)); + } + else + { + int shuffleMask = (3 << 6) | (2 << 4) | (1 << 2) | index; // Swap index and 0. (others remain) + shuffleMask &= ~(3 << (index * 2)); + + return context.AddIntrinsic(Intrinsic.X86Shufps, target, target, Const(shuffleMask)); + } + } + + // Vector Operand Templates + + public static void EmitVectorUnaryOpSimd32(ArmEmitterContext context, Func1I vectorFunc) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorUnaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + EmitVectorUnaryOpSimd32(context, (m) => context.AddIntrinsic(inst, m)); + } + + public static void EmitVectorBinaryOpSimd32(ArmEmitterContext context, Func2I vectorFunc, int side = -1) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (side == -1) + { + side = op.Vd; + } + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, side); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, side); + } + + Operand res = vectorFunc(n, m); + + if (!op.Q) // Register insert. + { + if (side != op.Vd) + { + res = EmitMoveDoubleWordToSide(context, res, side, op.Vd); + } + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorBinaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorTernaryOpSimd32(ArmEmitterContext context, Func3I vectorFunc) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + Operand initialD = d; + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(d, n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, initialD, res, op.Vd); + } + + context.Copy(initialD, res); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Intrinsic inst32pt1, Intrinsic inst64pt1, Intrinsic inst32pt2, Intrinsic inst64pt2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Intrinsic inst1 = (op.Size & 1) != 0 ? inst64pt1 : inst32pt1; + Intrinsic inst2 = (op.Size & 1) != 0 ? inst64pt2 : inst32pt2; + + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(inst1, n, m); + return res = context.AddIntrinsic(inst2, d, res); + }); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Intrinsic inst32) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Debug.Assert((op.Size & 1) == 0); + + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + return context.AddIntrinsic(inst32, d, n, m); + }); + } + + public static void EmitScalarUnaryOpSimd32(ArmEmitterContext context, Func1I scalarFunc) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + m = EmitSwapScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + EmitScalarUnaryOpSimd32(context, (m) => (inst == 0) ? m : context.AddIntrinsic(inst, m)); + } + + public static void EmitScalarBinaryOpSimd32(ArmEmitterContext context, Func2I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + n = EmitSwapScalar(context, n, op.Vn, doubleSize); + m = EmitSwapScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarBinaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + EmitScalarBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarTernaryOpSimd32(ArmEmitterContext context, Func3I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + Operand initialD = d; + + n = EmitSwapScalar(context, n, op.Vn, doubleSize); + m = EmitSwapScalar(context, m, op.Vm, doubleSize); + d = EmitSwapScalar(context, d, op.Vd, doubleSize); + + Operand res = scalarFunc(d, n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, initialD, res, op.Vd, doubleSize); + + context.Copy(initialD, res); + } + + public static void EmitScalarTernaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + + Intrinsic inst = doubleSize ? inst64 : inst32; + + EmitScalarTernaryOpSimd32(context, (d, n, m) => + { + return context.AddIntrinsic(inst, d, n, m); + }); + } + + public static void EmitScalarTernaryOpF32( + ArmEmitterContext context, + Intrinsic inst32pt1, + Intrinsic inst64pt1, + Intrinsic inst32pt2, + Intrinsic inst64pt2, + bool isNegD = false) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + + Intrinsic inst1 = doubleSize ? inst64pt1 : inst32pt1; + Intrinsic inst2 = doubleSize ? inst64pt2 : inst32pt2; + + EmitScalarTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(inst1, n, m); + + if (isNegD) + { + Operand mask = doubleSize + ? X86GetScalar(context, -0d) + : X86GetScalar(context, -0f); + + d = doubleSize + ? context.AddIntrinsic(Intrinsic.X86Xorpd, mask, d) + : context.AddIntrinsic(Intrinsic.X86Xorps, mask, d); + } + + return context.AddIntrinsic(inst2, d, res); + }); + } + + // By Scalar + + public static void EmitVectorByScalarOpSimd32(ArmEmitterContext context, Func2I vectorFunc) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand d = GetVecA32(op.Qd); + + int index = op.Vm & 3; + int dupeMask = (index << 6) | (index << 4) | (index << 2) | index; + Operand m = GetVecA32(op.Vm >> 2); + m = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(dupeMask)); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + } + + Operand res = vectorFunc(n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorByScalarOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + EmitVectorByScalarOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorsByScalarOpSimd32(ArmEmitterContext context, Func3I vectorFunc) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand d = GetVecA32(op.Qd); + Operand initialD = d; + + int index = op.Vm & 3; + int dupeMask = (index << 6) | (index << 4) | (index << 2) | index; + Operand m = GetVecA32(op.Vm >> 2); + m = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(dupeMask)); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + } + + Operand res = vectorFunc(d, n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, initialD, res, op.Vd); + } + + context.Copy(initialD, res); + } + + public static void EmitVectorsByScalarOpF32(ArmEmitterContext context, Intrinsic inst32pt1, Intrinsic inst64pt1, Intrinsic inst32pt2, Intrinsic inst64pt2) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Intrinsic inst1 = (op.Size & 1) != 0 ? inst64pt1 : inst32pt1; + Intrinsic inst2 = (op.Size & 1) != 0 ? inst64pt2 : inst32pt2; + + EmitVectorsByScalarOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(inst1, n, m); + return res = context.AddIntrinsic(inst2, d, res); + }); + } + + // Pairwise + + public static void EmitSse2VectorPairwiseOpF32(ArmEmitterContext context, Intrinsic inst32) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpSimd32(context, (n, m) => + { + Operand unpck = context.AddIntrinsic(Intrinsic.X86Unpcklps, n, m); + + Operand part0 = unpck; + Operand part1 = context.AddIntrinsic(Intrinsic.X86Movhlps, unpck, unpck); + + return context.AddIntrinsic(inst32, part0, part1); + }, 0); + } + + public static void EmitSsse3VectorPairwiseOp32(ArmEmitterContext context, Intrinsic[] inst) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpSimd32(context, (n, m) => + { + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand zeroEvenMask = X86GetElements(context, ZeroMask, EvenMasks[op.Size]); + Operand zeroOddMask = X86GetElements(context, ZeroMask, OddMasks[op.Size]); + + Operand mN = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); // m:n + + Operand left = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroEvenMask); // 0:even from m:n + Operand right = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroOddMask); // 0:odd from m:n + + return context.AddIntrinsic(inst[op.Size], left, right); + } + else if (op.Size < 3) + { + Operand oddEvenMask = X86GetElements(context, OddMasks[op.Size], EvenMasks[op.Size]); + + Operand oddEvenN = context.AddIntrinsic(Intrinsic.X86Pshufb, n, oddEvenMask); // odd:even from n + Operand oddEvenM = context.AddIntrinsic(Intrinsic.X86Pshufb, m, oddEvenMask); // odd:even from m + + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, oddEvenN, oddEvenM); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, oddEvenN, oddEvenM); + + return context.AddIntrinsic(inst[op.Size], left, right); + } + else + { + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, n, m); + + return context.AddIntrinsic(inst[3], left, right); + } + }, 0); + } + + // Generic Functions + + public static Operand EmitSoftFloatCallDefaultFpscr(ArmEmitterContext context, string name, params Operand[] callArgs) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(SoftFloat32).GetMethod(name) + : typeof(SoftFloat64).GetMethod(name); + + Array.Resize(ref callArgs, callArgs.Length + 1); + callArgs[^1] = Const(1); + + context.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(info, callArgs); + context.LoadFromContext(); + context.EnterArmFpMode(); + + return res; + } + + public static Operand EmitVectorExtractSx32(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract32(context, reg, index, size, true); + } + + public static Operand EmitVectorExtractZx32(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract32(context, reg, index, size, false); + } + + public static Operand EmitVectorExtract32(ArmEmitterContext context, int reg, int index, int size, bool signed) + { + ThrowIfInvalid(index, size); + + Operand res = default; + + switch (size) + { + case 0: + res = context.VectorExtract8(GetVec(reg), index); + break; + + case 1: + res = context.VectorExtract16(GetVec(reg), index); + break; + + case 2: + res = context.VectorExtract(OperandType.I32, GetVec(reg), index); + break; + + case 3: + res = context.VectorExtract(OperandType.I64, GetVec(reg), index); + break; + } + + if (signed) + { + switch (size) + { + case 0: + res = context.SignExtend8(OperandType.I32, res); + break; + case 1: + res = context.SignExtend16(OperandType.I32, res); + break; + } + } + else + { + switch (size) + { + case 0: + res = context.ZeroExtend8(OperandType.I32, res); + break; + case 1: + res = context.ZeroExtend16(OperandType.I32, res); + break; + } + } + + return res; + } + + public static Operand EmitPolynomialMultiply(ArmEmitterContext context, Operand op1, Operand op2, int eSize) + { + Debug.Assert(eSize <= 32); + + Operand result = eSize == 32 ? Const(0L) : Const(0); + + if (eSize == 32) + { + op1 = context.ZeroExtend32(OperandType.I64, op1); + op2 = context.ZeroExtend32(OperandType.I64, op2); + } + + for (int i = 0; i < eSize; i++) + { + Operand mask = context.BitwiseAnd(op1, Const(op1.Type, 1L << i)); + + result = context.BitwiseExclusiveOr(result, context.Multiply(op2, mask)); + } + + return result; + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs new file mode 100644 index 000000000..568c07122 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs @@ -0,0 +1,372 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +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 +{ + using Func1I = Func; + using Func2I = Func; + using Func3I = Func; + + static class InstEmitSimdHelper32Arm64 + { + // Intrinsic Helpers + + public static Operand EmitMoveDoubleWordToSide(ArmEmitterContext context, Operand input, int originalV, int targetV) + { + Debug.Assert(input.Type == OperandType.V128); + + int originalSide = originalV & 1; + int targetSide = targetV & 1; + + if (originalSide == targetSide) + { + return input; + } + + Intrinsic vType = Intrinsic.Arm64VDWord | Intrinsic.Arm64V128; + + if (targetSide == 1) + { + return context.AddIntrinsic(Intrinsic.Arm64DupVe | vType, input, Const(OperandType.I32, 0)); // Low to high. + } + else + { + return context.AddIntrinsic(Intrinsic.Arm64DupVe | vType, input, Const(OperandType.I32, 1)); // High to low. + } + } + + public static Operand EmitDoubleWordInsert(ArmEmitterContext context, Operand target, Operand value, int targetV) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + int targetSide = targetV & 1; + Operand idx = Const(targetSide); + + return context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, target, idx, value, idx); + } + + public static Operand EmitScalarInsert(ArmEmitterContext context, Operand target, Operand value, int reg, bool doubleWidth) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + // Insert from index 0 in value to index in target. + int index = reg & (doubleWidth ? 1 : 3); + + if (doubleWidth) + { + return context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, target, Const(index), value, Const(0)); + } + else + { + return context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VWord, target, Const(index), value, Const(0)); + } + } + + public static Operand EmitExtractScalar(ArmEmitterContext context, Operand target, int reg, bool doubleWidth) + { + int index = reg & (doubleWidth ? 1 : 3); + if (index == 0) + { + return target; // Element is already at index 0, so just return the vector directly. + } + + if (doubleWidth) + { + return context.AddIntrinsic(Intrinsic.Arm64DupSe | Intrinsic.Arm64VDWord, target, Const(1)); // Extract high (index 1). + } + else + { + return context.AddIntrinsic(Intrinsic.Arm64DupSe | Intrinsic.Arm64VWord, target, Const(index)); // Extract element at index. + } + } + + // Vector Operand Templates + + public static void EmitVectorUnaryOpSimd32(ArmEmitterContext context, Func1I vectorFunc) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorUnaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitVectorUnaryOpSimd32(context, (m) => context.AddIntrinsic(inst, m)); + } + + public static void EmitVectorBinaryOpSimd32(ArmEmitterContext context, Func2I vectorFunc, int side = -1) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (side == -1) + { + side = op.Vd; + } + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, side); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, side); + } + + Operand res = vectorFunc(n, m); + + if (!op.Q) // Register insert. + { + if (side != op.Vd) + { + res = EmitMoveDoubleWordToSide(context, res, side, op.Vd); + } + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorBinaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorTernaryOpSimd32(ArmEmitterContext context, Func3I vectorFunc) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + Operand initialD = d; + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(d, n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, initialD, res, op.Vd); + } + + context.Copy(initialD, res); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitScalarUnaryOpSimd32(ArmEmitterContext context, Func1I scalarFunc, bool doubleSize) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + int shift = doubleSize ? 1 : 2; + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + m = EmitExtractScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + EmitScalarUnaryOpF32(context, inst, (op.Size & 1) != 0); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Intrinsic inst, bool doubleSize) + { + inst |= (doubleSize ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitScalarUnaryOpSimd32(context, (m) => (inst == 0) ? m : context.AddIntrinsic(inst, m), doubleSize); + } + + public static void EmitScalarBinaryOpSimd32(ArmEmitterContext context, Func2I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + n = EmitExtractScalar(context, n, op.Vn, doubleSize); + m = EmitExtractScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarBinaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitScalarBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarTernaryOpSimd32(ArmEmitterContext context, Func3I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + Operand initialD = d; + + n = EmitExtractScalar(context, n, op.Vn, doubleSize); + m = EmitExtractScalar(context, m, op.Vm, doubleSize); + d = EmitExtractScalar(context, d, op.Vd, doubleSize); + + Operand res = scalarFunc(d, n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, initialD, res, op.Vd, doubleSize); + + context.Copy(initialD, res); + } + + public static void EmitScalarTernaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitScalarTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(inst, d, n, m)); + } + + // Pairwise + + public static void EmitVectorPairwiseOpF32(ArmEmitterContext context, Intrinsic inst32) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + inst32 |= Intrinsic.Arm64V64 | Intrinsic.Arm64VFloat; + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst32, n, m), 0); + } + + public static void EmitVcmpOrVcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + bool cmpWithZero = (op.Opc & 2) != 0; + + Intrinsic inst = signalNaNs ? Intrinsic.Arm64FcmpeS : Intrinsic.Arm64FcmpS; + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vd >> shift); + Operand m = GetVecA32(op.Vm >> shift); + + n = EmitExtractScalar(context, n, op.Vd, doubleSize); + m = cmpWithZero ? Const(0) : EmitExtractScalar(context, m, op.Vm, doubleSize); + + Operand nzcv = context.AddIntrinsicInt(inst, n, m); + + Operand one = Const(1); + + SetFpFlag(context, FPState.VFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(28)), one)); + SetFpFlag(context, FPState.CFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(29)), one)); + SetFpFlag(context, FPState.ZFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(30)), one)); + SetFpFlag(context, FPState.NFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(31)), one)); + } + + public static void EmitCmpOpF32(ArmEmitterContext context, CmpCondition cond, bool zero) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int sizeF = op.Size & 1; + + Intrinsic inst; + if (zero) + { + inst = cond switch + { + CmpCondition.Equal => Intrinsic.Arm64FcmeqVz, + CmpCondition.GreaterThan => Intrinsic.Arm64FcmgtVz, + CmpCondition.GreaterThanOrEqual => Intrinsic.Arm64FcmgeVz, + CmpCondition.LessThan => Intrinsic.Arm64FcmltVz, + CmpCondition.LessThanOrEqual => Intrinsic.Arm64FcmleVz, + _ => throw new InvalidOperationException(), + }; + } + else + { + inst = cond switch + { + CmpCondition.Equal => Intrinsic.Arm64FcmeqV, + CmpCondition.GreaterThan => Intrinsic.Arm64FcmgtV, + CmpCondition.GreaterThanOrEqual => Intrinsic.Arm64FcmgeV, + _ => throw new InvalidOperationException(), + }; + } + + inst |= (sizeF != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + + if (zero) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(inst, m); + }); + } + else + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + return context.AddIntrinsic(inst, n, m); + }); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs new file mode 100644 index 000000000..70dfc0fbd --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs @@ -0,0 +1,720 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitSimdHelperArm64 + { + public static void EmitScalarUnaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitScalarUnaryOpFFromGp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitScalarUnaryOpFToGp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + SetIntOrZR(context, op.Rd, op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(inst, n) + : context.AddIntrinsicLong(inst, n)); + } + + public static void EmitScalarBinaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarBinaryOpFByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m, Const(op.Index))); + } + + public static void EmitScalarTernaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + Operand a = GetVec(op.Ra); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, a, n, m)); + } + + public static void EmitScalarTernaryOpFRdByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m, Const(op.Index))); + } + + public static void EmitScalarUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitScalarBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n)); + } + + public static void EmitScalarTernaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(d, context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitScalarShiftBinaryOp(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(shift))); + } + + public static void EmitScalarShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + } + + public static void EmitScalarSaturatingShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarSaturatingUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand result = context.AddIntrinsic(inst, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarSaturatingBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand result = context.AddIntrinsic(inst, n, m); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarSaturatingBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand result = context.AddIntrinsic(inst, d, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarConvertBinaryOpF(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(fBits))); + } + + public static void EmitScalarConvertBinaryOpFFromGp(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(fBits))); + } + + public static void EmitScalarConvertBinaryOpFToGp(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + SetIntOrZR(context, op.Rd, op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(inst, n, Const(fBits)) + : context.AddIntrinsicLong(inst, n, Const(fBits))); + } + + public static void EmitVectorUnaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitVectorBinaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorBinaryOpFRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n)); + } + + public static void EmitVectorBinaryOpFByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m, Const(op.Index))); + } + + public static void EmitVectorTernaryOpFRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitVectorTernaryOpFRdByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m, Const(op.Index))); + } + + public static void EmitVectorUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitVectorBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n)); + } + + public static void EmitVectorBinaryOpByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m, Const(op.Index))); + } + + public static void EmitVectorTernaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitVectorTernaryOpRdByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m, Const(op.Index))); + } + + public static void EmitVectorShiftBinaryOp(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(shift))); + } + + public static void EmitVectorShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + } + + public static void EmitVectorSaturatingShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, n, m); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, d, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingBinaryOpByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, n, m, Const(op.Index)); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorConvertBinaryOpF(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(fBits))); + } + + public static void EmitVectorLookupTable(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdTbl op = (OpCodeSimdTbl)context.CurrOp; + + Operand[] operands = new Operand[op.Size + 1]; + + operands[op.Size] = GetVec(op.Rm); + + for (int index = 0; index < op.Size; index++) + { + operands[index] = GetVec((op.Rn + index) & 0x1F); + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, operands)); + } + + public static void EmitFcmpOrFcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + bool cmpWithZero = op is not OpCodeSimdFcond && op.Bit3; + + Intrinsic inst = signalNaNs ? Intrinsic.Arm64FcmpeS : Intrinsic.Arm64FcmpS; + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + Operand n = GetVec(op.Rn); + Operand m = cmpWithZero ? Const(0) : GetVec(op.Rm); + + Operand nzcv = context.AddIntrinsicInt(inst, n, m); + + Operand one = Const(1); + + SetFlag(context, PState.VFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(28)), one)); + SetFlag(context, PState.CFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(29)), one)); + SetFlag(context, PState.ZFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(30)), one)); + SetFlag(context, PState.NFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(31)), one)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdLogical.cs b/src/ARMeilleure/Instructions/InstEmitSimdLogical.cs new file mode 100644 index 000000000..ace8e4c54 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdLogical.cs @@ -0,0 +1,613 @@ +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.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64AndV); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64BicV); + } + else 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) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64BifV); + } + else + { + EmitBifBit(context, notRm: true); + } + } + + public static void Bit_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64BitV); + } + else + { + 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.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64BslV); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64EorV); + } + else 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.UseAvx512Ortho) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, n, Const(~0b10101010)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV); + } + else if (Optimizations.UseAvx512Ortho) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrrV); + } + else 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); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs b/src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs new file mode 100644 index 000000000..26d093447 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs @@ -0,0 +1,278 @@ +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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64AndV | Intrinsic.Arm64V128, n, m)); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64BicV | Intrinsic.Arm64V128, n, m)); + } + else 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) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(Intrinsic.Arm64BifV | Intrinsic.Arm64V128, d, n, m)); + } + else + { + EmitBifBit(context, true); + } + } + + public static void Vbit(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(Intrinsic.Arm64BitV | Intrinsic.Arm64V128, d, n, m)); + } + else + { + EmitBifBit(context, false); + } + } + + public static void Vbsl(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(Intrinsic.Arm64BslV | Intrinsic.Arm64V128, d, n, m)); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64EorV | Intrinsic.Arm64V128, n, m)); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseAvx512Ortho) + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + return context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010)); + }); + } + else 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.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrrV | Intrinsic.Arm64V128, n, m)); + } + else 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); + }); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMemory.cs b/src/ARMeilleure/Instructions/InstEmitSimdMemory.cs new file mode 100644 index 000000000..dedf0fa05 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMemory.cs @@ -0,0 +1,162 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Ld__Vms(ArmEmitterContext context) + { + EmitSimdMemMs(context, isLoad: true); + } + + public static void Ld__Vss(ArmEmitterContext context) + { + EmitSimdMemSs(context, isLoad: true); + } + + public static void St__Vms(ArmEmitterContext context) + { + EmitSimdMemMs(context, isLoad: false); + } + + public static void St__Vss(ArmEmitterContext context) + { + EmitSimdMemSs(context, isLoad: false); + } + + private static void EmitSimdMemMs(ArmEmitterContext context, bool isLoad) + { + OpCodeSimdMemMs op = (OpCodeSimdMemMs)context.CurrOp; + + Operand n = GetIntOrSP(context, op.Rn); + + long offset = 0; + +#pragma warning disable IDE0055 // Disable formatting + for (int rep = 0; rep < op.Reps; rep++) + for (int elem = 0; elem < op.Elems; elem++) + for (int sElem = 0; sElem < op.SElems; sElem++) + { + int rtt = (op.Rt + rep + sElem) & 0x1f; + + Operand tt = GetVec(rtt); + + Operand address = context.Add(n, Const(offset)); + + if (isLoad) + { + EmitLoadSimd(context, address, tt, rtt, elem, op.Size); + + if (op.RegisterSize == RegisterSize.Simd64 && elem == op.Elems - 1) + { + context.Copy(tt, context.VectorZeroUpper64(tt)); + } + } + else + { + EmitStoreSimd(context, address, rtt, elem, op.Size); + } + + offset += 1 << op.Size; + } +#pragma warning restore IDE0055 + + if (op.WBack) + { + EmitSimdMemWBack(context, offset); + } + } + + private static void EmitSimdMemSs(ArmEmitterContext context, bool isLoad) + { + OpCodeSimdMemSs op = (OpCodeSimdMemSs)context.CurrOp; + + Operand n = GetIntOrSP(context, op.Rn); + + long offset = 0; + + if (op.Replicate) + { + // Only loads uses the replicate mode. + Debug.Assert(isLoad, "Replicate mode is not valid for stores."); + + int elems = op.GetBytesCount() >> op.Size; + + for (int sElem = 0; sElem < op.SElems; sElem++) + { + int rt = (op.Rt + sElem) & 0x1f; + + Operand t = GetVec(rt); + + Operand address = context.Add(n, Const(offset)); + + for (int index = 0; index < elems; index++) + { + EmitLoadSimd(context, address, t, rt, index, op.Size); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + context.Copy(t, context.VectorZeroUpper64(t)); + } + + offset += 1 << op.Size; + } + } + else + { + for (int sElem = 0; sElem < op.SElems; sElem++) + { + int rt = (op.Rt + sElem) & 0x1f; + + Operand t = GetVec(rt); + + Operand address = context.Add(n, Const(offset)); + + if (isLoad) + { + EmitLoadSimd(context, address, t, rt, op.Index, op.Size); + } + else + { + EmitStoreSimd(context, address, rt, op.Index, op.Size); + } + + offset += 1 << op.Size; + } + } + + if (op.WBack) + { + EmitSimdMemWBack(context, offset); + } + } + + private static void EmitSimdMemWBack(ArmEmitterContext context, long offset) + { + OpCodeMemReg op = (OpCodeMemReg)context.CurrOp; + + Operand n = GetIntOrSP(context, op.Rn); + Operand m; + + if (op.Rm != RegisterAlias.Zr) + { + m = GetIntOrZR(context, op.Rm); + } + else + { + m = Const(offset); + } + + context.Copy(n, context.Add(n, m)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs b/src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs new file mode 100644 index 000000000..35c6dd328 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs @@ -0,0 +1,352 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Vld1(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 1, true); + } + + public static void Vld2(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 2, true); + } + + public static void Vld3(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 3, true); + } + + public static void Vld4(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 4, true); + } + + public static void Vst1(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 1, false); + } + + public static void Vst2(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 2, false); + } + + public static void Vst3(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 3, false); + } + + public static void Vst4(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 4, false); + } + + public static void EmitVStoreOrLoadN(ArmEmitterContext context, int count, bool load) + { + if (context.CurrOp is OpCode32SimdMemSingle) + { + OpCode32SimdMemSingle op = (OpCode32SimdMemSingle)context.CurrOp; + + int eBytes = 1 << op.Size; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + // TODO: Check alignment. + int offset = 0; + int d = op.Vd; + + for (int i = 0; i < count; i++) + { + // Accesses an element from a double simd register. + Operand address = context.Add(n, Const(offset)); + if (eBytes == 8) + { + if (load) + { + EmitDVectorLoad(context, address, d); + } + else + { + EmitDVectorStore(context, address, d); + } + } + else + { + int index = ((d & 1) << (3 - op.Size)) + op.Index; + if (load) + { + if (op.Replicate) + { + var regs = (count > 1) ? 1 : op.Increment; + for (int reg = 0; reg < regs; reg++) + { + int dreg = reg + d; + int rIndex = ((dreg & 1) << (3 - op.Size)); + int limit = rIndex + (1 << (3 - op.Size)); + + while (rIndex < limit) + { + EmitLoadSimd(context, address, GetVecA32(dreg >> 1), dreg >> 1, rIndex++, op.Size); + } + } + } + else + { + EmitLoadSimd(context, address, GetVecA32(d >> 1), d >> 1, index, op.Size); + } + } + else + { + EmitStoreSimd(context, address, d >> 1, index, op.Size); + } + } + offset += eBytes; + d += op.Increment; + } + + if (op.WBack) + { + if (op.RegisterIndex) + { + Operand m = GetIntA32(context, op.Rm); + SetIntA32(context, op.Rn, context.Add(n, m)); + } + else + { + SetIntA32(context, op.Rn, context.Add(n, Const(count * eBytes))); + } + } + } + else + { + OpCode32SimdMemPair op = (OpCode32SimdMemPair)context.CurrOp; + + int increment = count > 1 ? op.Increment : 1; + int eBytes = 1 << op.Size; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + int offset = 0; + int d = op.Vd; + + for (int reg = 0; reg < op.Regs; reg++) + { + for (int elem = 0; elem < op.Elems; elem++) + { + int elemD = d + reg; + for (int i = 0; i < count; i++) + { + // Accesses an element from a double simd register, + // add ebytes for each element. + Operand address = context.Add(n, Const(offset)); + int index = ((elemD & 1) << (3 - op.Size)) + elem; + if (eBytes == 8) + { + if (load) + { + EmitDVectorLoad(context, address, elemD); + } + else + { + EmitDVectorStore(context, address, elemD); + } + } + else + { + if (load) + { + EmitLoadSimd(context, address, GetVecA32(elemD >> 1), elemD >> 1, index, op.Size); + } + else + { + EmitStoreSimd(context, address, elemD >> 1, index, op.Size); + } + } + + offset += eBytes; + elemD += increment; + } + } + } + + if (op.WBack) + { + if (op.RegisterIndex) + { + Operand m = GetIntA32(context, op.Rm); + SetIntA32(context, op.Rn, context.Add(n, m)); + } + else + { + SetIntA32(context, op.Rn, context.Add(n, Const(count * 8 * op.Regs))); + } + } + } + } + + public static void Vldm(ArmEmitterContext context) + { + OpCode32SimdMemMult op = (OpCode32SimdMemMult)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + bool writeBack = op.PostOffset != 0; + + if (writeBack) + { + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + int range = op.RegisterRange; + + int sReg = (op.DoubleWidth) ? (op.Vd << 1) : op.Vd; + int offset = 0; + int byteSize = 4; + + for (int num = 0; num < range; num++, sReg++) + { + Operand address = context.Add(baseAddress, Const(offset)); + Operand vec = GetVecA32(sReg >> 2); + + EmitLoadSimd(context, address, vec, sReg >> 2, sReg & 3, WordSizeLog2); + offset += byteSize; + } + } + + public static void Vstm(ArmEmitterContext context) + { + OpCode32SimdMemMult op = (OpCode32SimdMemMult)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + bool writeBack = op.PostOffset != 0; + + if (writeBack) + { + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + int offset = 0; + + int range = op.RegisterRange; + int sReg = (op.DoubleWidth) ? (op.Vd << 1) : op.Vd; + int byteSize = 4; + + for (int num = 0; num < range; num++, sReg++) + { + Operand address = context.Add(baseAddress, Const(offset)); + + EmitStoreSimd(context, address, sReg >> 2, sReg & 3, WordSizeLog2); + + offset += byteSize; + } + } + + public static void Vldr(ArmEmitterContext context) + { + EmitVLoadOrStore(context, AccessType.Load); + } + + public static void Vstr(ArmEmitterContext context) + { + EmitVLoadOrStore(context, AccessType.Store); + } + + private static void EmitDVectorStore(ArmEmitterContext context, Operand address, int vecD) + { + int vecQ = vecD >> 1; + int vecSElem = (vecD & 1) << 1; + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + EmitStoreSimd(context, address, vecQ, vecSElem, WordSizeLog2); + EmitStoreSimd(context, context.Add(address, Const(4)), vecQ, vecSElem | 1, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + EmitStoreSimd(context, address, vecQ, vecSElem | 1, WordSizeLog2); + EmitStoreSimd(context, context.Add(address, Const(4)), vecQ, vecSElem, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + + private static void EmitDVectorLoad(ArmEmitterContext context, Operand address, int vecD) + { + int vecQ = vecD >> 1; + int vecSElem = (vecD & 1) << 1; + Operand vec = GetVecA32(vecQ); + + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + EmitLoadSimd(context, address, vec, vecQ, vecSElem, WordSizeLog2); + EmitLoadSimd(context, context.Add(address, Const(4)), vec, vecQ, vecSElem | 1, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + EmitLoadSimd(context, address, vec, vecQ, vecSElem | 1, WordSizeLog2); + EmitLoadSimd(context, context.Add(address, Const(4)), vec, vecQ, vecSElem, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + + private static void EmitVLoadOrStore(ArmEmitterContext context, AccessType accType) + { + OpCode32SimdMemImm op = (OpCode32SimdMemImm)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + Operand m = GetMemM(context, setCarry: false); + + Operand address = op.Add + ? context.Add(n, m) + : context.Subtract(n, m); + + int size = op.Size; + + if ((accType & AccessType.Load) != 0) + { + if (size == DWordSizeLog2) + { + EmitDVectorLoad(context, address, op.Vd); + } + else + { + Operand vec = GetVecA32(op.Vd >> 2); + EmitLoadSimd(context, address, vec, op.Vd >> 2, (op.Vd & 3) << (2 - size), size); + } + } + else + { + if (size == DWordSizeLog2) + { + EmitDVectorStore(context, address, op.Vd); + } + else + { + EmitStoreSimd(context, address, op.Vd >> 2, (op.Vd & 3) << (2 - size), size); + } + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMove.cs b/src/ARMeilleure/Instructions/InstEmitSimdMove.cs new file mode 100644 index 000000000..85c98fe3a --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMove.cs @@ -0,0 +1,877 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + #region "Masks" + 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 Dup_Gp(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + if (Optimizations.UseSse2) + { + switch (op.Size) + { + case 0: + n = context.ZeroExtend8(n.Type, n); + n = context.Multiply(n, Const(n.Type, 0x01010101)); + break; + case 1: + n = context.ZeroExtend16(n.Type, n); + n = context.Multiply(n, Const(n.Type, 0x00010001)); + break; + case 2: + n = context.ZeroExtend32(n.Type, n); + break; + } + + Operand res = context.VectorInsert(context.VectorZero(), n, 0); + + if (op.Size < 3) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0xf0)); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0)); + } + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, n, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Dup_S(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, op.DstIndex, op.Size); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), ne, 0, op.Size)); + } + + public static void Dup_V(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand res = GetVec(op.Rn); + + if (op.Size == 0) + { + if (op.DstIndex != 0) + { + res = context.AddIntrinsic(Intrinsic.X86Psrldq, res, Const(op.DstIndex)); + } + + res = context.AddIntrinsic(Intrinsic.X86Punpcklbw, res, res); + res = context.AddIntrinsic(Intrinsic.X86Punpcklwd, res, res); + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0)); + } + else if (op.Size == 1) + { + if (op.DstIndex != 0) + { + res = context.AddIntrinsic(Intrinsic.X86Psrldq, res, Const(op.DstIndex * 2)); + } + + res = context.AddIntrinsic(Intrinsic.X86Punpcklwd, res, res); + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0)); + } + else if (op.Size == 2) + { + int mask = op.DstIndex * 0b01010101; + + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(mask)); + } + else if (op.DstIndex == 0 && op.RegisterSize != RegisterSize.Simd64) + { + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, res); + } + else if (op.DstIndex == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Movhlps, res, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = EmitVectorExtractZx(context, op.Rn, op.DstIndex, op.Size); + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, ne, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Ext_V(ArmEmitterContext context) + { + OpCodeSimdExt op = (OpCodeSimdExt)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand nShifted = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd64) + { + nShifted = context.VectorZeroUpper64(nShifted); + } + + nShifted = context.AddIntrinsic(Intrinsic.X86Psrldq, nShifted, Const(op.Imm4)); + + Operand mShifted = GetVec(op.Rm); + + mShifted = context.AddIntrinsic(Intrinsic.X86Pslldq, mShifted, Const(op.GetBytesCount() - op.Imm4)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + mShifted = context.VectorZeroUpper64(mShifted); + } + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, mShifted); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int bytes = op.GetBytesCount(); + + int position = op.Imm4 & (bytes - 1); + + for (int index = 0; index < bytes; index++) + { + int reg = op.Imm4 + index < bytes ? op.Rn : op.Rm; + + Operand e = EmitVectorExtractZx(context, reg, position, 0); + + position = (position + 1) & (bytes - 1); + + res = EmitVectorInsert(context, res, e, index, 0); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fcsel_S(ArmEmitterContext context) + { + OpCodeSimdFcond op = (OpCodeSimdFcond)context.CurrOp; + + Operand lblTrue = Label(); + Operand lblEnd = Label(); + + Operand isTrue = InstEmitFlowHelper.GetCondTrue(context, op.Cond); + + context.BranchIfTrue(lblTrue, isTrue); + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand me = context.VectorExtract(type, GetVec(op.Rm), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), me, 0)); + + context.Branch(lblEnd); + + context.MarkLabel(lblTrue); + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), ne, 0)); + + context.MarkLabel(lblEnd); + } + + public static void Fmov_Ftoi(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, op.Size + 2); + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Fmov_Ftoi1(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, 1, 3); + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Fmov_Itof(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), n, 0, op.Size + 2)); + } + + public static void Fmov_Itof1(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetIntOrZR(context, op.Rn); + + context.Copy(d, EmitVectorInsert(context, d, n, 1, 3)); + } + + public static void Fmov_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), ne, 0)); + } + + public static void Fmov_Si(ArmEmitterContext context) + { + OpCodeSimdFmov op = (OpCodeSimdFmov)context.CurrOp; + + if (Optimizations.UseSse2) + { + if (op.Size == 0) + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, (int)op.Immediate)); + } + else + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, op.Immediate)); + } + } + else + { + Operand e = Const(op.Immediate); + + Operand res = context.VectorZero(); + + res = EmitVectorInsert(context, res, e, 0, op.Size + 2); + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fmov_Vi(ArmEmitterContext context) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + if (Optimizations.UseSse2) + { + if (op.RegisterSize == RegisterSize.Simd128) + { + context.Copy(GetVec(op.Rd), X86GetAllElements(context, op.Immediate)); + } + else + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, op.Immediate)); + } + } + else + { + Operand e = Const(op.Immediate); + + Operand res = context.VectorZero(); + + int elems = op.RegisterSize == RegisterSize.Simd128 ? 2 : 1; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, e, index, 3); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Ins_Gp(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetIntOrZR(context, op.Rn); + + context.Copy(d, EmitVectorInsert(context, d, n, op.DstIndex, op.Size)); + } + + public static void Ins_V(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand ne = EmitVectorExtractZx(context, op.Rn, op.SrcIndex, op.Size); + + context.Copy(d, EmitVectorInsert(context, d, ne, op.DstIndex, op.Size)); + } + + public static void Movi_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2VectorMoviMvniOp(context, not: false); + } + else + { + EmitVectorImmUnaryOp(context, (op1) => op1); + } + } + + public static void Mvni_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2VectorMoviMvniOp(context, not: true); + } + else + { + EmitVectorImmUnaryOp(context, (op1) => context.BitwiseNot(op1)); + } + } + + public static void Smov_S(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand ne = EmitVectorExtractSx(context, op.Rn, op.DstIndex, op.Size); + + if (op.RegisterSize == RegisterSize.Simd64) + { + ne = context.ZeroExtend32(OperandType.I64, ne); + } + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Tbl_V(ArmEmitterContext context) + { + EmitTableVectorLookup(context, isTbl: true); + } + + public static void Tbx_V(ArmEmitterContext context) + { + EmitTableVectorLookup(context, isTbl: false); + } + + public static void Trn1_V(ArmEmitterContext context) + { + EmitVectorTranspose(context, part: 0); + } + + public static void Trn2_V(ArmEmitterContext context) + { + EmitVectorTranspose(context, part: 1); + } + + public static void Umov_S(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, op.DstIndex, op.Size); + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Uzp1_V(ArmEmitterContext context) + { + EmitVectorUnzip(context, part: 0); + } + + public static void Uzp2_V(ArmEmitterContext context) + { + EmitVectorUnzip(context, part: 1); + } + + public static void Xtn_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (Optimizations.UseSsse3) + { + Operand d = GetVec(op.Rd); + + Operand res = context.VectorZeroUpper64(d); + + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, GetVec(op.Rn), mask); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + res = context.AddIntrinsic(movInst, res, res2); + + context.Copy(d, res); + } + else + { + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size + 1); + + res = EmitVectorInsert(context, res, ne, part + index, op.Size); + } + + context.Copy(d, res); + } + } + + public static void Zip1_V(ArmEmitterContext context) + { + EmitVectorZip(context, part: 0); + } + + public static void Zip2_V(ArmEmitterContext context) + { + EmitVectorZip(context, part: 1); + } + + private static void EmitSse2VectorMoviMvniOp(ArmEmitterContext context, bool not) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + long imm = op.Immediate; + + switch (op.Size) + { + case 0: + imm *= 0x01010101; + break; + case 1: + imm *= 0x00010001; + break; + } + + if (not) + { + imm = ~imm; + } + + Operand mask; + + if (op.Size < 3) + { + mask = X86GetAllElements(context, (int)imm); + } + else + { + mask = X86GetAllElements(context, imm); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + mask = context.VectorZeroUpper64(mask); + } + + context.Copy(GetVec(op.Rd), mask); + } + + private static void EmitTableVectorLookup(ArmEmitterContext context, bool isTbl) + { + OpCodeSimdTbl op = (OpCodeSimdTbl)context.CurrOp; + + if (Optimizations.UseSsse3) + { + Operand d = GetVec(op.Rd); + Operand m = GetVec(op.Rm); + + Operand res; + + Operand mask = X86GetAllElements(context, 0x0F0F0F0F0F0F0F0FL); + + // Fast path for single register table. + { + Operand n = GetVec(op.Rn); + + 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 < op.Size; index++) + { + Operand ni = GetVec((op.Rn + index) & 0x1F); + + Operand idxMask = X86GetAllElements(context, 0x1010101010101010L * 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 (!isTbl) + { + Operand idxMask = X86GetAllElements(context, (0x1010101010101010L * op.Size) - 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, d, mMask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, dMask); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand d = GetVec(op.Rd); + + List args = new(); + + if (!isTbl) + { + args.Add(d); + } + + args.Add(GetVec(op.Rm)); + + args.Add(Const(op.RegisterSize == RegisterSize.Simd64 ? 8 : 16)); + + for (int index = 0; index < op.Size; index++) + { + args.Add(GetVec((op.Rn + index) & 0x1F)); + } + + MethodInfo info = null; + + if (isTbl) + { + switch (op.Size) + { + case 1: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl1)); + break; + case 2: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl2)); + break; + case 3: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl3)); + break; + case 4: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl4)); + break; + } + } + else + { + switch (op.Size) + { + case 1: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx1)); + break; + case 2: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx2)); + break; + case 3: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx3)); + break; + case 4: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx4)); + break; + } + } + + context.Copy(d, context.Call(info, args.ToArray())); + } + } + + private static void EmitVectorTranspose(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + 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); + } + + Operand n = GetVec(op.Rn); + + if (op.Size < 3) + { + n = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + } + + Operand m = GetVec(op.Rm); + + if (op.Size < 3) + { + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Intrinsic punpckInst = part == 0 + ? X86PunpcklInstruction[op.Size] + : X86PunpckhInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpckInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand ne = EmitVectorExtractZx(context, op.Rn, pairIndex + part, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, pairIndex + part, op.Size); + + res = EmitVectorInsert(context, res, ne, pairIndex, op.Size); + res = EmitVectorInsert(context, res, me, pairIndex + 1, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitVectorUnzip(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + 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); + } + + Operand n = GetVec(op.Rn); + + if (op.Size < 3) + { + n = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + } + + Operand m = GetVec(op.Rm); + + if (op.Size < 3) + { + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Intrinsic punpckInst = part == 0 + ? Intrinsic.X86Punpcklqdq + : Intrinsic.X86Punpckhqdq; + + Operand res = context.AddIntrinsic(punpckInst, n, m); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic punpcklInst = X86PunpcklInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpcklInst, n, 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); + } + + Intrinsic punpckInst = part == 0 + ? Intrinsic.X86Punpcklqdq + : Intrinsic.X86Punpckhqdq; + + res = context.AddIntrinsic(punpckInst, res, context.VectorZero()); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int idx = index << 1; + + Operand ne = EmitVectorExtractZx(context, op.Rn, idx + part, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, idx + part, op.Size); + + res = EmitVectorInsert(context, res, ne, index, op.Size); + res = EmitVectorInsert(context, res, me, pairs + index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitVectorZip(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + Intrinsic punpckInst = part == 0 + ? X86PunpcklInstruction[op.Size] + : X86PunpckhInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpckInst, n, m); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.AddIntrinsic(X86PunpcklInstruction[op.Size], n, m); + + Intrinsic punpckInst = part == 0 + ? Intrinsic.X86Punpcklqdq + : Intrinsic.X86Punpckhqdq; + + res = context.AddIntrinsic(punpckInst, res, context.VectorZero()); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + int baseIndex = part != 0 ? pairs : 0; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand ne = EmitVectorExtractZx(context, op.Rn, baseIndex + index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, baseIndex + index, op.Size); + + res = EmitVectorInsert(context, res, ne, pairIndex, op.Size); + res = EmitVectorInsert(context, res, me, pairIndex + 1, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs new file mode 100644 index 000000000..9fa740997 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs @@ -0,0 +1,655 @@ +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.UseAvx512Ortho) + { + EmitVectorUnaryOpSimd32(context, (op1) => + { + return context.AddIntrinsic(Intrinsic.X86Vpternlogd, op1, op1, Const(0b01010101)); + }); + } + else 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, _) = 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.UseAdvSimd) + { + EmitVectorZipUzpOpSimd32(context, Intrinsic.Arm64Zip1V, Intrinsic.Arm64Zip2V); + } + else 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.UseAdvSimd) + { + EmitVectorZipUzpOpSimd32(context, Intrinsic.Arm64Uzp1V, Intrinsic.Arm64Uzp2V); + } + else 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 EmitVectorZipUzpOpSimd32(ArmEmitterContext context, Intrinsic inst1, Intrinsic inst2) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + bool overlap = op.Qm == op.Qd; + + Operand d = GetVecA32(op.Qd); + Operand m = GetVecA32(op.Qm); + + Operand dPart = d; + Operand mPart = m; + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + dPart = InstEmitSimdHelper32Arm64.EmitMoveDoubleWordToSide(context, d, op.Vd, 0); + mPart = InstEmitSimdHelper32Arm64.EmitMoveDoubleWordToSide(context, m, op.Vm, 0); + } + + Intrinsic vSize = op.Q ? Intrinsic.Arm64V128 : Intrinsic.Arm64V64; + + vSize |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand resD = context.AddIntrinsic(inst1 | vSize, dPart, mPart); + Operand resM = context.AddIntrinsic(inst2 | vSize, dPart, mPart); + + if (!op.Q) // Register insert. + { + resD = context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, d, Const(op.Vd & 1), resD, Const(0)); + + if (overlap) + { + resD = context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, resD, Const(op.Vm & 1), resM, Const(0)); + } + else + { + resM = context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, m, Const(op.Vm & 1), resM, Const(0)); + } + } + + context.Copy(d, resD); + if (!overlap) + { + context.Copy(m, resM); + } + } + + private static void EmitVectorShuffleOpSimd32(ArmEmitterContext context, Func 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); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdShift.cs b/src/ARMeilleure/Instructions/InstEmitSimdShift.cs new file mode 100644 index 000000000..be0670645 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdShift.cs @@ -0,0 +1,1832 @@ +// https://github.com/intel/ARM_NEON_2_x86_SSE/blob/master/NEON_2_SSE.h + +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit + { + #region "Masks" + private static readonly long[] _masks_SliSri = new long[] // Replication masks. + { + 0x0101010101010101L, 0x0001000100010001L, 0x0000000100000001L, 0x0000000000000001L, + }; + #endregion + + public static void Rshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64RshrnV, shift); + } + else if (Optimizations.UseSsse3) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand dLow = context.VectorZeroUpper64(d); + + Operand mask = default; + + switch (op.Size + 1) + { + case 1: + mask = X86GetAllElements(context, (int)roundConst * 0x00010001); + break; + case 2: + mask = X86GetAllElements(context, (int)roundConst); + break; + case 3: + mask = X86GetAllElements(context, roundConst); + break; + } + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + Operand res = context.AddIntrinsic(addInst, n, mask); + + Intrinsic srlInst = X86PsrlInstruction[op.Size + 1]; + + res = context.AddIntrinsic(srlInst, res, Const(shift)); + + Operand mask2 = X86GetAllElements(context, EvenMasks[op.Size]); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask2); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + res = context.AddIntrinsic(movInst, dLow, res); + + context.Copy(d, res); + } + else + { + EmitVectorShrImmNarrowOpZx(context, round: true); + } + } + + public static void Shl_S(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64ShlS, shift); + } + else + { + EmitScalarUnaryOpZx(context, (op1) => context.ShiftLeft(op1, Const(shift))); + } + } + + public static void Shl_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + int eSize = 8 << op.Size; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64ShlV, shift); + } + else if (shift >= eSize) + { + if ((op.RegisterSize == RegisterSize.Simd64)) + { + Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand n = GetVec(op.Rn); + + ulong bitMatrix = X86GetGf2p8LogicalShiftLeft(shift); + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand res = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(shift)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpZx(context, (op1) => context.ShiftLeft(op1, Const(shift))); + } + } + + public static void Shll_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int shift = 8 << op.Size; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64ShllV); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + } + + Intrinsic movsxInst = X86PmovsxInstruction[op.Size]; + + Operand res = context.AddIntrinsic(movsxInst, n); + + Intrinsic sllInst = X86PsllInstruction[op.Size + 1]; + + res = context.AddIntrinsic(sllInst, res, Const(shift)); + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShImmWidenBinaryZx(context, (op1, op2) => context.ShiftLeft(op1, op2), shift); + } + } + + public static void Shrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64ShrnV, shift); + } + else if (Optimizations.UseSsse3) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand dLow = context.VectorZeroUpper64(d); + + Intrinsic srlInst = X86PsrlInstruction[op.Size + 1]; + + Operand nShifted = context.AddIntrinsic(srlInst, n, Const(shift)); + + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, nShifted, mask); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + res = context.AddIntrinsic(movInst, dLow, res); + + context.Copy(d, res); + } + else + { + EmitVectorShrImmNarrowOpZx(context, round: false); + } + } + + public static void Sli_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SliS, shift); + } + else + { + EmitSli(context, scalar: true); + } + } + + public static void Sli_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SliV, shift); + } + else + { + EmitSli(context, scalar: false); + } + } + + public static void Sqrshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed | ShlRegFlags.Round | ShlRegFlags.Saturating); + } + } + + public static void Sqrshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrnS, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } + } + + public static void Sqrshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrnV, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } + } + + public static void Sqrshrun_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrunS, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } + } + + public static void Sqrshrun_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrunV, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + } + + public static void Sqshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed | ShlRegFlags.Saturating); + } + } + + public static void Sqshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrnS, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } + } + + public static void Sqshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrnV, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } + } + + public static void Sqshrun_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrunS, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } + } + + public static void Sqshrun_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrunV, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + } + + public static void Sri_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SriS, shift); + } + else + { + EmitSri(context, scalar: true); + } + } + + public static void Sri_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SriV, shift); + } + else + { + EmitSri(context, scalar: false); + } + } + + public static void Srshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed | ShlRegFlags.Round); + } + } + + public static void Srshr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64SrshrS, shift); + } + else + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Round); + } + } + + public static void Srshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64SrshrV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand nSra = context.AddIntrinsic(sraInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSra); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShrImmOpSx(context, ShrImmFlags.Round); + } + } + + public static void Srsra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SrsraS, shift); + } + else + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Srsra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SrsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand nSra = context.AddIntrinsic(sraInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSra); + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpSx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Sshl_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64SshlS); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Scalar | ShlRegFlags.Signed); + } + } + + public static void Sshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed); + } + } + + public static void Sshll_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64SshllV, shift); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + } + + Intrinsic movsxInst = X86PmovsxInstruction[op.Size]; + + Operand res = context.AddIntrinsic(movsxInst, n); + + if (shift != 0) + { + Intrinsic sllInst = X86PsllInstruction[op.Size + 1]; + + res = context.AddIntrinsic(sllInst, res, Const(shift)); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShImmWidenBinarySx(context, (op1, op2) => context.ShiftLeft(op1, op2), shift); + } + } + + public static void Sshr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64SshrS, shift); + } + else + { + EmitShrImmOp(context, ShrImmFlags.ScalarSx); + } + } + + public static void Sshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64SshrV, shift); + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand n = GetVec(op.Rn); + + ulong bitMatrix; + + if (shift < 8) + { + bitMatrix = X86GetGf2p8LogicalShiftLeft(-shift); + + // Extend sign-bit + bitMatrix |= 0x8080808080808080UL >> (64 - shift * 8); + } + else + { + // Replicate sign-bit into all bits + bitMatrix = 0x8080808080808080UL; + } + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand res = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + Operand n = GetVec(op.Rn); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sraInst, n, Const(shift)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitShrImmOp(context, ShrImmFlags.VectorSx); + } + } + + public static void Ssra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SsraS, shift); + } + else + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Accumulate); + } + } + + public static void Ssra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sraInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpSx(context, ShrImmFlags.Accumulate); + } + } + + public static void Uqrshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Round | ShlRegFlags.Saturating); + } + } + + public static void Uqrshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqrshrnS, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + } + + public static void Uqrshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqrshrnV, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + } + + public static void Uqshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Saturating); + } + } + + public static void Uqshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqshrnS, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + } + + public static void Uqshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqshrnV, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + } + + public static void Urshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Round); + } + } + + public static void Urshr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64UrshrS, shift); + } + else + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Round); + } + } + + public static void Urshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64UrshrV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Operand nSrl = context.AddIntrinsic(srlInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSrl); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShrImmOpZx(context, ShrImmFlags.Round); + } + } + + public static void Ursra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64UrsraS, shift); + } + else + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Ursra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64UrsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Operand nSrl = context.AddIntrinsic(srlInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSrl); + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpZx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Ushl_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64UshlS); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Scalar); + } + } + + public static void Ushl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.None); + } + } + + public static void Ushll_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64UshllV, shift); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + } + + Intrinsic movzxInst = X86PmovzxInstruction[op.Size]; + + Operand res = context.AddIntrinsic(movzxInst, n); + + if (shift != 0) + { + Intrinsic sllInst = X86PsllInstruction[op.Size + 1]; + + res = context.AddIntrinsic(sllInst, res, Const(shift)); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShImmWidenBinaryZx(context, (op1, op2) => context.ShiftLeft(op1, op2), shift); + } + } + + public static void Ushr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64UshrS, shift); + } + else + { + EmitShrImmOp(context, ShrImmFlags.ScalarZx); + } + } + + public static void Ushr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64UshrV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + + Operand n = GetVec(op.Rn); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + Operand res = context.AddIntrinsic(srlInst, n, Const(shift)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitShrImmOp(context, ShrImmFlags.VectorZx); + } + } + + public static void Usra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64UsraS, shift); + } + else + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Accumulate); + } + } + + public static void Usra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64UsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + Operand res = context.AddIntrinsic(srlInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpZx(context, ShrImmFlags.Accumulate); + } + } + + [Flags] + private enum ShrImmFlags + { + Scalar = 1 << 0, + Signed = 1 << 1, + + Round = 1 << 2, + Accumulate = 1 << 3, + + ScalarSx = Scalar | Signed, + ScalarZx = Scalar, + + VectorSx = Signed, + VectorZx = 0, + } + + private static void EmitScalarShrImmOpSx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.ScalarSx | flags); + } + + private static void EmitScalarShrImmOpZx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.ScalarZx | flags); + } + + private static void EmitVectorShrImmOpSx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.VectorSx | flags); + } + + private static void EmitVectorShrImmOpZx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.VectorZx | flags); + } + + private static void EmitShrImmOp(ArmEmitterContext context, ShrImmFlags flags) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + Operand res = context.VectorZero(); + + bool scalar = (flags & ShrImmFlags.Scalar) != 0; + bool signed = (flags & ShrImmFlags.Signed) != 0; + bool round = (flags & ShrImmFlags.Round) != 0; + bool accumulate = (flags & ShrImmFlags.Accumulate) != 0; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand e = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + if (op.Size <= 2) + { + if (round) + { + e = context.Add(e, Const(roundConst)); + } + + e = signed ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); + } + else /* if (op.Size == 3) */ + { + e = EmitShrImm64(context, e, signed, round ? roundConst : 0L, shift); + } + + if (accumulate) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size, signed); + + e = context.Add(e, de); + } + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitVectorShrImmNarrowOpZx(ArmEmitterContext context, bool round) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand e = EmitVectorExtractZx(context, op.Rn, index, op.Size + 1); + + if (round) + { + e = context.Add(e, Const(roundConst)); + } + + e = context.ShiftRightUI(e, Const(shift)); + + res = EmitVectorInsert(context, res, e, part + index, op.Size); + } + + context.Copy(d, res); + } + + [Flags] + private enum ShrImmSaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + Round = 1 << 3, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0, + } + + private static void EmitRoundShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.Round | flags); + } + + private static void EmitShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + bool scalar = (flags & ShrImmSaturatingNarrowFlags.Scalar) != 0; + bool signedSrc = (flags & ShrImmSaturatingNarrowFlags.SignedSrc) != 0; + bool signedDst = (flags & ShrImmSaturatingNarrowFlags.SignedDst) != 0; + bool round = (flags & ShrImmSaturatingNarrowFlags.Round) != 0; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + int elems = !scalar ? 8 >> op.Size : 1; + + int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand e = EmitVectorExtract(context, op.Rn, index, op.Size + 1, signedSrc); + + if (op.Size <= 1 || !round) + { + if (round) + { + e = context.Add(e, Const(roundConst)); + } + + e = signedSrc ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); + } + else /* if (op.Size == 2 && round) */ + { + e = EmitShrImm64(context, e, signedSrc, roundConst, shift); // shift <= 32 + } + + e = signedSrc ? EmitSignedSrcSatQ(context, e, op.Size, signedDst) : EmitUnsignedSrcSatQ(context, e, op.Size, signedDst); + + res = EmitVectorInsert(context, res, e, part + index, op.Size); + } + + context.Copy(d, res); + } + + // dst64 = (Int(src64, signed) + roundConst) >> shift; + private static Operand EmitShrImm64( + ArmEmitterContext context, + Operand value, + bool signed, + long roundConst, + int shift) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShrImm64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShrImm64)); + + return context.Call(info, value, Const(roundConst), Const(shift)); + } + + private static void EmitVectorShImmWidenBinarySx(ArmEmitterContext context, Func2I emit, int imm) + { + EmitVectorShImmWidenBinaryOp(context, emit, imm, signed: true); + } + + private static void EmitVectorShImmWidenBinaryZx(ArmEmitterContext context, Func2I emit, int imm) + { + EmitVectorShImmWidenBinaryOp(context, emit, imm, signed: false); + } + + private static void EmitVectorShImmWidenBinaryOp(ArmEmitterContext context, Func2I emit, int imm, bool signed) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, Const(imm)), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSli(ArmEmitterContext context, bool scalar) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + int eSize = 8 << op.Size; + + ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL; + + if (shift >= eSize) + { + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + ulong bitMatrix = X86GetGf2p8LogicalShiftLeft(shift); + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand nShifted = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand nShifted = context.AddIntrinsic(sllInst, n, Const(shift)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand neShifted = context.ShiftLeft(ne, Const(shift)); + + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + + Operand deMasked = context.BitwiseAnd(de, Const(mask)); + + Operand e = context.BitwiseOr(neShifted, deMasked); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSri(ArmEmitterContext context, bool scalar) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + ulong mask = (ulong.MaxValue << (eSize - shift)) & (ulong.MaxValue >> (64 - eSize)); + + if (shift >= eSize) + { + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + ulong bitMatrix = X86GetGf2p8LogicalShiftLeft(-shift); + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand nShifted = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + Operand nShifted = context.AddIntrinsic(srlInst, n, Const(shift)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand neShifted = shift != 64 ? context.ShiftRightUI(ne, Const(shift)) : Const(0UL); + + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + + Operand deMasked = context.BitwiseAnd(de, Const(mask)); + + Operand e = context.BitwiseOr(neShifted, deMasked); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + [Flags] + private enum ShlRegFlags + { + None = 0, + Scalar = 1 << 0, + Signed = 1 << 1, + Round = 1 << 2, + Saturating = 1 << 3, + } + + private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None) + { + bool scalar = flags.HasFlag(ShlRegFlags.Scalar); + bool signed = flags.HasFlag(ShlRegFlags.Signed); + bool round = flags.HasFlag(ShlRegFlags.Round); + bool saturating = flags.HasFlag(ShlRegFlags.Saturating); + + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + Operand me = EmitVectorExtractSx(context, op.Rm, index << op.Size, size: 0); + + Operand e = !saturating + ? EmitShlReg(context, ne, context.ConvertI64ToI32(me), round, op.Size, signed) + : EmitShlRegSatQ(context, ne, context.ConvertI64ToI32(me), round, op.Size, signed); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + // long SignedShlReg(long op, int shiftLsB, bool round, int size); + // ulong UnsignedShlReg(ulong op, int shiftLsB, bool round, int size); + private static Operand EmitShlReg(ArmEmitterContext context, Operand op, Operand shiftLsB, bool round, int size, bool signed) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(shiftLsB.Type == OperandType.I32); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zero = Const(0); + Operand zeroL = Const(0L); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lbl1, shiftLsB, zero, Comparison.GreaterOrEqual); + context.Copy(res, signed + ? EmitSignedShrReg(context, op, context.Negate(shiftLsB), round, eSize) + : EmitUnsignedShrReg(context, op, context.Negate(shiftLsB), round, eSize)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, shiftLsB, zero, Comparison.LessOrEqual); + Operand shl = context.ShiftLeft(op, shiftLsB); + Operand isGreaterOrEqual = context.ICompareGreaterOrEqual(shiftLsB, eSizeOp); + context.Copy(res, context.ConditionalSelect(isGreaterOrEqual, zeroL, shl)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long SignedShlRegSatQ(long op, int shiftLsB, bool round, int size); + // ulong UnsignedShlRegSatQ(ulong op, int shiftLsB, bool round, int size); + private static Operand EmitShlRegSatQ(ArmEmitterContext context, Operand op, Operand shiftLsB, bool round, int size, bool signed) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(shiftLsB.Type == OperandType.I32); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lbl1 = Label(); + Operand lbl2 = Label(); + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zero = Const(0); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lbl1, shiftLsB, zero, Comparison.GreaterOrEqual); + context.Copy(res, signed + ? EmitSignedShrReg(context, op, context.Negate(shiftLsB), round, eSize) + : EmitUnsignedShrReg(context, op, context.Negate(shiftLsB), round, eSize)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, shiftLsB, zero, Comparison.LessOrEqual); + context.BranchIf(lbl2, shiftLsB, eSizeOp, Comparison.Less); + context.Copy(res, signed + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + context.Branch(lblEnd); + + context.MarkLabel(lbl2); + Operand shl = context.ShiftLeft(op, shiftLsB); + if (eSize == 64) + { + Operand sarOrShr = signed + ? context.ShiftRightSI(shl, shiftLsB) + : context.ShiftRightUI(shl, shiftLsB); + context.Copy(res, shl); + context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal); + context.Copy(res, signed + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + } + else + { + context.Copy(res, signed + ? EmitSignedSrcSatQ(context, shl, size, signedDst: true) + : EmitUnsignedSrcSatQ(context, shl, size, signedDst: false)); + } + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // shift := [1, 128]; eSize := {8, 16, 32, 64}. + // long SignedShrReg(long op, int shift, bool round, int eSize); + private static Operand EmitSignedShrReg(ArmEmitterContext context, Operand op, Operand shift, bool round, int eSize) + { + if (round) + { + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zeroL = Const(0L); + Operand one = Const(1); + Operand oneL = Const(1L); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroL); + + context.BranchIf(lblEnd, shift, eSizeOp, Comparison.GreaterOrEqual); + Operand roundConst = context.ShiftLeft(oneL, context.Subtract(shift, one)); + Operand add = context.Add(op, roundConst); + Operand sar = context.ShiftRightSI(add, shift); + if (eSize == 64) + { + Operand shr = context.ShiftRightUI(add, shift); + Operand left = context.BitwiseAnd(context.Negate(op), context.BitwiseExclusiveOr(op, add)); + Operand isLess = context.ICompareLess(left, zeroL); + context.Copy(res, context.ConditionalSelect(isLess, shr, sar)); + } + else + { + context.Copy(res, sar); + } + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + else + { + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zeroL = Const(0L); + Operand negOneL = Const(-1L); + + Operand sar = context.ShiftRightSI(op, shift); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), sar); + + context.BranchIf(lblEnd, shift, eSizeOp, Comparison.Less); + Operand isLess = context.ICompareLess(op, zeroL); + context.Copy(res, context.ConditionalSelect(isLess, negOneL, zeroL)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + } + + // shift := [1, 128]; eSize := {8, 16, 32, 64}. + // ulong UnsignedShrReg(ulong op, int shift, bool round, int eSize); + private static Operand EmitUnsignedShrReg(ArmEmitterContext context, Operand op, Operand shift, bool round, int eSize) + { + if (round) + { + Operand lblEnd = Label(); + + Operand zeroUL = Const(0UL); + Operand one = Const(1); + Operand oneUL = Const(1UL); + Operand eSizeMaxOp = Const(64); + Operand oneShl63UL = Const(1UL << 63); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroUL); + + context.BranchIf(lblEnd, shift, eSizeMaxOp, Comparison.Greater); + Operand roundConst = context.ShiftLeft(oneUL, context.Subtract(shift, one)); + Operand add = context.Add(op, roundConst); + Operand shr = context.ShiftRightUI(add, shift); + Operand isEqual = context.ICompareEqual(shift, eSizeMaxOp); + context.Copy(res, context.ConditionalSelect(isEqual, zeroUL, shr)); + if (eSize == 64) + { + context.BranchIf(lblEnd, add, op, Comparison.GreaterOrEqualUI); + Operand right = context.BitwiseOr(shr, context.ShiftRightUI(oneShl63UL, context.Subtract(shift, one))); + context.Copy(res, context.ConditionalSelect(isEqual, oneUL, right)); + } + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + else + { + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zeroUL = Const(0UL); + + Operand shr = context.ShiftRightUI(op, shift); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), shr); + + context.BranchIf(lblEnd, shift, eSizeOp, Comparison.Less); + context.Copy(res, zeroUL); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs new file mode 100644 index 000000000..e40600a47 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs @@ -0,0 +1,388 @@ +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 + { + public static void Vqrshrn(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + EmitRoundShrImmSaturatingNarrowOp(context, op.U ? ShrImmSaturatingNarrowFlags.VectorZxZx : ShrImmSaturatingNarrowFlags.VectorSxSx); + } + + public static void Vqrshrun(ArmEmitterContext context) + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + + public static void Vqshrn(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + EmitShrImmSaturatingNarrowOp(context, op.U ? ShrImmSaturatingNarrowFlags.VectorZxZx : ShrImmSaturatingNarrowFlags.VectorSxSx); + } + + public static void Vqshrun(ArmEmitterContext context) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + + public static void Vrshr(ArmEmitterContext context) + { + EmitRoundShrImmOp(context, accumulate: false); + } + + public static void Vrshrn(ArmEmitterContext context) + { + EmitRoundShrImmNarrowOp(context, signed: false); + } + + public static void Vrsra(ArmEmitterContext context) + { + EmitRoundShrImmOp(context, accumulate: true); + } + + public static void Vshl(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + EmitVectorUnaryOpZx32(context, (op1) => context.ShiftLeft(op1, Const(op.Shift))); + } + + public static void Vshl_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + EmitVectorBinaryOpZx32(context, (op1, op2) => EmitShlRegOp(context, op2, op1, op.Size, true)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => EmitShlRegOp(context, op2, op1, op.Size, false)); + } + } + + public static void Vshll(ArmEmitterContext context) + { + OpCode32SimdShImmLong op = (OpCode32SimdShImmLong)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); + } + } + + me = context.ShiftLeft(me, Const(op.Shift)); + + res = EmitVectorInsert(context, res, me, index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vshr(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + int maxShift = (8 << op.Size) - 1; + + if (op.U) + { + EmitVectorUnaryOpZx32(context, (op1) => (shift > maxShift) ? Const(op1.Type, 0) : context.ShiftRightUI(op1, Const(shift))); + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => context.ShiftRightSI(op1, Const(Math.Min(maxShift, shift)))); + } + } + + public static void Vshrn(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + + EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift))); + } + + public static void Vsra(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + int maxShift = (8 << op.Size) - 1; + + if (op.U) + { + EmitVectorImmBinaryQdQmOpZx32(context, (op1, op2) => + { + Operand shiftRes = shift > maxShift ? Const(op2.Type, 0) : context.ShiftRightUI(op2, Const(shift)); + + return context.Add(op1, shiftRes); + }); + } + else + { + EmitVectorImmBinaryQdQmOpSx32(context, (op1, op2) => context.Add(op1, context.ShiftRightSI(op2, Const(Math.Min(maxShift, shift))))); + } + } + + public static void EmitRoundShrImmOp(ArmEmitterContext context, bool accumulate) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + long roundConst = 1L << (shift - 1); + + if (op.U) + { + if (op.Size < 2) + { + EmitVectorUnaryOpZx32(context, (op1) => + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ShiftRightUI(op1, Const(shift)); + }, accumulate); + } + else if (op.Size == 2) + { + EmitVectorUnaryOpZx32(context, (op1) => + { + op1 = context.ZeroExtend32(OperandType.I64, op1); + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ConvertI64ToI32(context.ShiftRightUI(op1, Const(shift))); + }, accumulate); + } + else /* if (op.Size == 3) */ + { + EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: false, roundConst, shift), accumulate); + } + } + else + { + if (op.Size < 2) + { + EmitVectorUnaryOpSx32(context, (op1) => + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ShiftRightSI(op1, Const(shift)); + }, accumulate); + } + else if (op.Size == 2) + { + EmitVectorUnaryOpSx32(context, (op1) => + { + op1 = context.SignExtend32(OperandType.I64, op1); + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ConvertI64ToI32(context.ShiftRightSI(op1, Const(shift))); + }, accumulate); + } + else /* if (op.Size == 3) */ + { + EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: true, roundConst, shift), accumulate); + } + } + } + + private static void EmitRoundShrImmNarrowOp(ArmEmitterContext context, bool signed) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + long roundConst = 1L << (shift - 1); + + EmitVectorUnaryNarrowOp32(context, (op1) => + { + if (op.Size <= 1) + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + op1 = signed ? context.ShiftRightSI(op1, Const(shift)) : context.ShiftRightUI(op1, Const(shift)); + } + else /* if (op.Size == 2 && round) */ + { + op1 = EmitShrImm64(context, op1, signed, roundConst, shift); // shift <= 32 + } + + return op1; + }, signed); + } + + private static Operand EmitShlRegOp(ArmEmitterContext context, Operand op, Operand shiftLsB, int size, bool unsigned) + { + if (shiftLsB.Type == OperandType.I64) + { + shiftLsB = context.ConvertI64ToI32(shiftLsB); + } + + shiftLsB = context.SignExtend8(OperandType.I32, shiftLsB); + Debug.Assert((uint)size < 4u); + + Operand negShiftLsB = context.Negate(shiftLsB); + + Operand isPositive = context.ICompareGreaterOrEqual(shiftLsB, Const(0)); + + Operand shl = context.ShiftLeft(op, shiftLsB); + Operand shr = unsigned ? context.ShiftRightUI(op, negShiftLsB) : context.ShiftRightSI(op, negShiftLsB); + + Operand res = context.ConditionalSelect(isPositive, shl, shr); + + if (unsigned) + { + Operand isOutOfRange = context.BitwiseOr( + context.ICompareGreaterOrEqual(shiftLsB, Const(8 << size)), + context.ICompareGreaterOrEqual(negShiftLsB, Const(8 << size))); + + return context.ConditionalSelect(isOutOfRange, Const(op.Type, 0), res); + } + else + { + Operand isOutOfRange0 = context.ICompareGreaterOrEqual(shiftLsB, Const(8 << size)); + Operand isOutOfRangeN = context.ICompareGreaterOrEqual(negShiftLsB, Const(8 << size)); + + // Also zero if shift is too negative, but value was positive. + isOutOfRange0 = context.BitwiseOr(isOutOfRange0, context.BitwiseAnd(isOutOfRangeN, context.ICompareGreaterOrEqual(op, Const(op.Type, 0)))); + + Operand min = (op.Type == OperandType.I64) ? Const(-1L) : Const(-1); + + return context.ConditionalSelect(isOutOfRange0, Const(op.Type, 0), context.ConditionalSelect(isOutOfRangeN, min, res)); + } + } + + [Flags] + private enum ShrImmSaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + Round = 1 << 3, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0, + } + + private static void EmitRoundShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.Round | flags); + } + + private static void EmitShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + bool scalar = (flags & ShrImmSaturatingNarrowFlags.Scalar) != 0; + bool signedSrc = (flags & ShrImmSaturatingNarrowFlags.SignedSrc) != 0; + bool signedDst = (flags & ShrImmSaturatingNarrowFlags.SignedDst) != 0; + bool round = (flags & ShrImmSaturatingNarrowFlags.Round) != 0; + + if (scalar) + { + // TODO: Support scalar operation. + throw new NotImplementedException(); + } + + int shift = GetImmShr(op); + long roundConst = 1L << (shift - 1); + + EmitVectorUnaryNarrowOp32(context, (op1) => + { + if (op.Size <= 1 || !round) + { + if (round) + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + } + + op1 = signedSrc ? context.ShiftRightSI(op1, Const(shift)) : context.ShiftRightUI(op1, Const(shift)); + } + else /* if (op.Size == 2 && round) */ + { + op1 = EmitShrImm64(context, op1, signedSrc, roundConst, shift); // shift <= 32 + } + + return EmitSatQ(context, op1, 8 << op.Size, signedSrc, signedDst); + }, signedSrc); + } + + private static int GetImmShr(OpCode32SimdShImm op) + { + return (8 << op.Size) - op.Shift; // Shr amount is flipped. + } + + // dst64 = (Int(src64, signed) + roundConst) >> shift; + private static Operand EmitShrImm64( + ArmEmitterContext context, + Operand value, + bool signed, + long roundConst, + int shift) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShrImm64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShrImm64)); + + return context.Call(info, value, Const(roundConst), Const(shift)); + } + + private static Operand EmitSatQ(ArmEmitterContext context, Operand value, int eSize, bool signedSrc, bool signedDst) + { + Debug.Assert(eSize <= 32); + + long intMin = signedDst ? -(1L << (eSize - 1)) : 0; + long intMax = signedDst ? (1L << (eSize - 1)) - 1 : (1L << eSize) - 1; + + Operand gt = signedSrc + ? context.ICompareGreater(value, Const(value.Type, intMax)) + : context.ICompareGreaterUI(value, Const(value.Type, intMax)); + + Operand lt = signedSrc + ? context.ICompareLess(value, Const(value.Type, intMin)) + : context.ICompareLessUI(value, Const(value.Type, intMin)); + + value = context.ConditionalSelect(gt, Const(value.Type, intMax), value); + value = context.ConditionalSelect(lt, Const(value.Type, intMin), value); + + Operand lblNoSat = Label(); + + context.BranchIfFalse(lblNoSat, context.BitwiseOr(gt, lt)); + + SetFpFlag(context, FPState.QcFlag, Const(1)); + + context.MarkLabel(lblNoSat); + + return value; + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSystem.cs b/src/ARMeilleure/Instructions/InstEmitSystem.cs new file mode 100644 index 000000000..8c430fc23 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSystem.cs @@ -0,0 +1,278 @@ +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: + EmitGetTpidrEl0(context); + return; + case 0b11_011_1101_0000_011: + EmitGetTpidrroEl0(context); + return; + 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; + + 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: + EmitSetTpidrEl0(context); + return; + + default: + throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); + } + } + + 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; + + context.SyncQcFlag(); + + 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 EmitGetTpidrEl0(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset()))); + + SetIntOrZR(context, op.Rt, result); + } + + private static void EmitGetTpidrroEl0(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset()))); + + SetIntOrZR(context, op.Rt, result); + } + + 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))); + } + } + + context.UpdateArmFpMode(); + } + + private static void EmitSetFpsr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + context.ClearQcFlagIfModified(); + + 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))); + } + } + + context.UpdateArmFpMode(); + } + + private static void EmitSetTpidrEl0(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand value = GetIntOrZR(context, op.Rt); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSystem32.cs b/src/ARMeilleure/Instructions/InstEmitSystem32.cs new file mode 100644 index 000000000..74d6169c6 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSystem32.cs @@ -0,0 +1,338 @@ +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; + } + + 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: + EmitSetTpidrEl0(context); + return; + + default: + throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); + } + + 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}."); + } + } + + public static void Mrc(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + if (op.Coproc != 15 || op.Opc1 != 0) + { + InstEmit.Und(context); + + return; + } + + Operand result; + + 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})."); + } + + result = op.Opc2 switch + { + 2 => EmitGetTpidrEl0(context), + 3 => EmitGetTpidrroEl0(context), + _ => 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, result); + + return; + } + else + { + SetIntA32(context, op.Rt, result); + } + } + + 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 = op.CRm switch + { + // Timer. + 14 => opc switch + { + 0 => typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)), + _ => throw new NotImplementedException($"Unknown MRRC Opc1 0x{opc:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."), + }, + _ => 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))); + } + } + + context.UpdateArmFpMode(); + } + + private static Operand EmitGetTpidrEl0(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset()))); + } + + private static Operand EmitGetTpidrroEl0(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset()))); + } + + private static void EmitSetTpidrEl0(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + Operand value = GetIntA32(context, op.Rt); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), context.ZeroExtend32(OperandType.I64, value)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstName.cs b/src/ARMeilleure/Instructions/InstName.cs new file mode 100644 index 000000000..32ae38dad --- /dev/null +++ b/src/ARMeilleure/Instructions/InstName.cs @@ -0,0 +1,687 @@ +namespace ARMeilleure.Instructions +{ + enum InstName + { + // Base (AArch64) + Adc, + Adcs, + Add, + Adds, + Adr, + Adrp, + And, + Ands, + Asrv, + B, + B_Cond, + Bfm, + Bic, + Bics, + Bl, + Blr, + Br, + Brk, + Cbnz, + Cbz, + Ccmn, + Ccmp, + Clrex, + Cls, + Clz, + Crc32b, + Crc32h, + Crc32w, + Crc32x, + Crc32cb, + Crc32ch, + Crc32cw, + Crc32cx, + Csdb, + Csel, + Csinc, + Csinv, + Csneg, + Dmb, + Dsb, + Eon, + Eor, + Esb, + Extr, + Hint, + Isb, + It, + Ldar, + Ldaxp, + Ldaxr, + Ldp, + Ldr, + Ldr_Literal, + Ldrs, + Ldxr, + Ldxp, + Lslv, + Lsrv, + Madd, + Movk, + Movn, + Movz, + Mrs, + Msr, + Msub, + Nop, + Orn, + Orr, + Prfm, + Rbit, + Ret, + Rev16, + Rev32, + Rev64, + Rorv, + Sbc, + Sbcs, + Sbfm, + Sdiv, + Sel, + Sev, + Sevl, + Shsub8, + Smaddl, + Smsubl, + Smulh, + Smull, + Smulw_, + Ssat, + Ssat16, + Stlr, + Stlxp, + Stlxr, + Stp, + Str, + Stxp, + Stxr, + Sub, + Subs, + Svc, + Sxtb, + Sxth, + Sys, + Tbnz, + Tbz, + Tsb, + Ubfm, + Udiv, + Umaddl, + Umsubl, + Umulh, + Und, + Wfe, + Wfi, + Yield, + + // FP & SIMD (AArch64) + Abs_S, + Abs_V, + Add_S, + Add_V, + Addhn_V, + Addp_S, + Addp_V, + Addv_V, + Aesd_V, + Aese_V, + Aesimc_V, + Aesmc_V, + And_V, + Bic_V, + Bic_Vi, + Bif_V, + Bit_V, + Bsl_V, + Cls_V, + Clz_V, + Cmeq_S, + Cmeq_V, + Cmge_S, + Cmge_V, + Cmgt_S, + Cmgt_V, + Cmhi_S, + Cmhi_V, + Cmhs_S, + Cmhs_V, + Cmle_S, + Cmle_V, + Cmlt_S, + Cmlt_V, + Cmtst_S, + Cmtst_V, + Cnt_V, + Dup_Gp, + Dup_S, + Dup_V, + Eor_V, + Ext_V, + Fabd_S, + Fabd_V, + Fabs_S, + Fabs_V, + Facge_S, + Facge_V, + Facgt_S, + Facgt_V, + Fadd_S, + Fadd_V, + Faddp_S, + Faddp_V, + Fccmp_S, + Fccmpe_S, + Fcmeq_S, + Fcmeq_V, + Fcmge_S, + Fcmge_V, + Fcmgt_S, + Fcmgt_V, + Fcmle_S, + Fcmle_V, + Fcmlt_S, + Fcmlt_V, + Fcmp_S, + Fcmpe_S, + Fcsel_S, + Fcvt_S, + Fcvtas_Gp, + Fcvtas_S, + Fcvtas_V, + Fcvtau_Gp, + Fcvtau_S, + Fcvtau_V, + Fcvtl_V, + Fcvtms_Gp, + Fcvtms_V, + Fcvtmu_Gp, + Fcvtn_V, + Fcvtns_Gp, + Fcvtns_S, + Fcvtns_V, + Fcvtnu_S, + Fcvtnu_V, + Fcvtps_Gp, + Fcvtpu_Gp, + Fcvtzs_Gp, + Fcvtzs_Gp_Fixed, + Fcvtzs_S, + Fcvtzs_V, + Fcvtzs_V_Fixed, + Fcvtzu_Gp, + Fcvtzu_Gp_Fixed, + Fcvtzu_S, + Fcvtzu_V, + Fcvtzu_V_Fixed, + Fdiv_S, + Fdiv_V, + Fmadd_S, + Fmax_S, + Fmax_V, + Fmaxnm_S, + Fmaxnm_V, + Fmaxnmp_S, + Fmaxnmp_V, + Fmaxnmv_V, + Fmaxp_S, + Fmaxp_V, + Fmaxv_V, + Fmin_S, + Fmin_V, + Fminnm_S, + Fminnm_V, + Fminnmp_S, + Fminnmp_V, + Fminnmv_V, + Fminp_S, + Fminp_V, + Fminv_V, + Fmla_Se, + Fmla_V, + Fmla_Ve, + Fmls_Se, + Fmls_V, + Fmls_Ve, + Fmov_S, + Fmov_Si, + Fmov_Vi, + Fmov_Ftoi, + Fmov_Itof, + Fmov_Ftoi1, + Fmov_Itof1, + Fmsub_S, + Fmul_S, + Fmul_Se, + Fmul_V, + Fmul_Ve, + Fmulx_S, + Fmulx_Se, + Fmulx_V, + Fmulx_Ve, + Fneg_S, + Fneg_V, + Fnmadd_S, + Fnmsub_S, + Fnmul_S, + Frecpe_S, + Frecpe_V, + Frecps_S, + Frecps_V, + Frecpx_S, + Frinta_S, + Frinta_V, + Frinti_S, + Frinti_V, + Frintm_S, + Frintm_V, + Frintn_S, + Frintn_V, + Frintp_S, + Frintp_V, + Frintx_S, + Frintx_V, + Frintz_S, + Frintz_V, + Frsqrte_S, + Frsqrte_V, + Frsqrts_S, + Frsqrts_V, + Fsqrt_S, + Fsqrt_V, + Fsub_S, + Fsub_V, + Ins_Gp, + Ins_V, + Ld__Vms, + Ld__Vss, + Mla_V, + Mla_Ve, + Mls_V, + Mls_Ve, + Movi_V, + Mul_V, + Mul_Ve, + Mvni_V, + Neg_S, + Neg_V, + Not_V, + Orn_V, + Orr_V, + Orr_Vi, + Pmull_V, + Raddhn_V, + Rbit_V, + Rev16_V, + Rev32_V, + Rev64_V, + Rshrn_V, + Rsubhn_V, + Saba_V, + Sabal_V, + Sabd_V, + Sabdl_V, + Sadalp_V, + Saddl_V, + Saddlp_V, + Saddlv_V, + Saddw_V, + Scvtf_Gp, + Scvtf_Gp_Fixed, + Scvtf_S, + Scvtf_S_Fixed, + Scvtf_V, + Scvtf_V_Fixed, + Sha1c_V, + Sha1h_V, + Sha1m_V, + Sha1p_V, + Sha1su0_V, + Sha1su1_V, + Sha256h_V, + Sha256h2_V, + Sha256su0_V, + Sha256su1_V, + Shadd_V, + Shl_S, + Shl_V, + Shll_V, + Shrn_V, + Shsub_V, + Sli_S, + Sli_V, + Smax_V, + Smaxp_V, + Smaxv_V, + Smin_V, + Sminp_V, + Sminv_V, + Smlal_V, + Smlal_Ve, + Smlsl_V, + Smlsl_Ve, + Smov_S, + Smull_V, + Smull_Ve, + Sqabs_S, + Sqabs_V, + Sqadd_S, + Sqadd_V, + Sqdmulh_S, + Sqdmulh_V, + Sqdmulh_Ve, + Sqneg_S, + Sqneg_V, + Sqrdmulh_S, + Sqrdmulh_V, + Sqrdmulh_Ve, + Sqrshl_V, + Sqrshrn_S, + Sqrshrn_V, + Sqrshrun_S, + Sqrshrun_V, + Sqshl_V, + Sqshrn_S, + Sqshrn_V, + Sqshrun_S, + Sqshrun_V, + Sqsub_S, + Sqsub_V, + Sqxtn_S, + Sqxtn_V, + Sqxtun_S, + Sqxtun_V, + Srhadd_V, + Sri_S, + Sri_V, + Srshl_V, + Srshr_S, + Srshr_V, + Srsra_S, + Srsra_V, + Sshl_S, + Sshl_V, + Sshll_V, + Sshr_S, + Sshr_V, + Ssra_S, + Ssra_V, + Ssubl_V, + Ssubw_V, + St__Vms, + St__Vss, + Sub_S, + Sub_V, + Subhn_V, + Suqadd_S, + Suqadd_V, + Tbl_V, + Tbx_V, + Trn1_V, + Trn2_V, + Uaba_V, + Uabal_V, + Uabd_V, + Uabdl_V, + Uadalp_V, + Uaddl_V, + Uaddlp_V, + Uaddlv_V, + Uaddw_V, + Ucvtf_Gp, + Ucvtf_Gp_Fixed, + Ucvtf_S, + Ucvtf_S_Fixed, + Ucvtf_V, + Ucvtf_V_Fixed, + Uhadd_V, + Uhsub_V, + Umax_V, + Umaxp_V, + Umaxv_V, + Umin_V, + Uminp_V, + Uminv_V, + Umlal_V, + Umlal_Ve, + Umlsl_V, + Umlsl_Ve, + Umov_S, + Umull_V, + Umull_Ve, + Uqadd_S, + Uqadd_V, + Uqrshl_V, + Uqrshrn_S, + Uqrshrn_V, + Uqshl_V, + Uqshrn_S, + Uqshrn_V, + Uqsub_S, + Uqsub_V, + Uqxtn_S, + Uqxtn_V, + Urhadd_V, + Urshl_V, + Urshr_S, + Urshr_V, + Ursra_S, + Ursra_V, + Ushl_S, + Ushl_V, + Ushll_V, + Ushr_S, + Ushr_V, + Usqadd_S, + Usqadd_V, + Usra_S, + Usra_V, + Usubl_V, + Usubw_V, + Uzp1_V, + Uzp2_V, + Xtn_V, + Zip1_V, + Zip2_V, + + // Base (AArch32) + Bfc, + Bfi, + Blx, + Bx, + Cmp, + Cmn, + Movt, + Mul, + Lda, + Ldab, + Ldaex, + Ldaexb, + Ldaexd, + Ldaexh, + Ldah, + Ldm, + Ldrb, + Ldrd, + Ldrex, + Ldrexb, + Ldrexd, + Ldrexh, + Ldrh, + Ldrsb, + Ldrsh, + Mcr, + Mla, + Mls, + Mov, + Mrc, + Mrrc, + Mvn, + Pkh, + Pld, + Pop, + Push, + Rev, + Revsh, + Rsb, + Rsc, + Sadd8, + Sbfx, + Shadd8, + Smla__, + Smlal, + Smlal__, + Smlaw_, + Smmla, + Smmls, + Smul__, + Smmul, + Ssub8, + Stl, + Stlb, + Stlex, + Stlexb, + Stlexd, + Stlexh, + Stlh, + Stm, + Strb, + Strd, + Strex, + Strexb, + Strexd, + Strexh, + Strh, + Sxtb16, + Tbb, + Tbh, + Teq, + Trap, + Tst, + Uadd8, + Ubfx, + Uhadd8, + Uhsub8, + Umaal, + Umlal, + Umull, + Usat, + Usat16, + Usub8, + Uxtb, + Uxtb16, + Uxth, + + // FP & SIMD (AArch32) + Vabd, + Vabdl, + Vabs, + Vadd, + Vaddl, + Vaddw, + Vand, + Vbic, + Vbif, + Vbit, + Vbsl, + Vceq, + Vcge, + Vcgt, + Vcle, + Vclt, + Vcmp, + Vcmpe, + Vcnt, + Vcvt, + Vdiv, + Vdup, + Veor, + Vext, + Vfma, + Vfms, + Vfnma, + Vfnms, + Vhadd, + Vld1, + Vld2, + Vld3, + Vld4, + Vldm, + Vldr, + Vmax, + Vmaxnm, + Vmin, + Vminnm, + Vmla, + Vmlal, + Vmls, + Vmlsl, + Vmov, + Vmovl, + Vmovn, + Vmrs, + Vmsr, + Vmul, + Vmull, + Vmvn, + Vneg, + Vnmul, + Vnmla, + Vnmls, + Vorn, + Vorr, + Vpadd, + Vpaddl, + Vpmax, + Vpmin, + Vqadd, + Vqdmulh, + Vqmovn, + Vqmovun, + Vqrshrn, + Vqrshrun, + Vqshrn, + Vqshrun, + Vqsub, + Vrev, + Vrhadd, + Vrint, + Vrinta, + Vrintm, + Vrintn, + Vrintp, + Vrintx, + Vrshr, + Vrshrn, + Vsel, + Vshl, + Vshll, + Vshr, + Vshrn, + Vst1, + Vst2, + Vst3, + Vst4, + Vstm, + Vstr, + Vsqrt, + Vrecpe, + Vrecps, + Vrsqrte, + Vrsqrts, + Vrsra, + Vsra, + Vsub, + Vsubl, + Vsubw, + Vtbl, + Vtrn, + Vtst, + Vuzp, + Vzip, + } +} diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs new file mode 100644 index 000000000..d1b2e353c --- /dev/null +++ b/src/ARMeilleure/Instructions/NativeInterface.cs @@ -0,0 +1,195 @@ +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 GetContext().CtrEl0; + } + + public static ulong GetDczidEl0() + { + return GetContext().DczidEl0; + } + + public static ulong GetCntfrqEl0() + { + return GetContext().CntfrqEl0; + } + + public static ulong GetCntpctEl0() + { + return GetContext().CntpctEl0; + } + + public static ulong GetCntvctEl0() + { + return GetContext().CntvctEl0; + } + #endregion + + #region "Read" + public static byte ReadByte(ulong address) + { + return GetMemoryManager().ReadTracked(address); + } + + public static ushort ReadUInt16(ulong address) + { + return GetMemoryManager().ReadTracked(address); + } + + public static uint ReadUInt32(ulong address) + { + return GetMemoryManager().ReadTracked(address); + } + + public static ulong ReadUInt64(ulong address) + { + return GetMemoryManager().ReadTracked(address); + } + + public static V128 ReadVector128(ulong address) + { + return GetMemoryManager().ReadTracked(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.FuncPointer.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; + } + } +} diff --git a/src/ARMeilleure/Instructions/SoftFallback.cs b/src/ARMeilleure/Instructions/SoftFallback.cs new file mode 100644 index 000000000..c4fe677bf --- /dev/null +++ b/src/ARMeilleure/Instructions/SoftFallback.cs @@ -0,0 +1,648 @@ +using ARMeilleure.State; +using System; + +namespace ARMeilleure.Instructions +{ + static class SoftFallback + { + #region "ShrImm64" + public static long SignedShrImm64(long value, long roundConst, int shift) + { + if (roundConst == 0L) + { + if (shift <= 63) + { + return value >> shift; + } + else /* if (shift == 64) */ + { + if (value < 0L) + { + return -1L; + } + else /* if (value >= 0L) */ + { + return 0L; + } + } + } + else /* if (roundConst == 1L << (shift - 1)) */ + { + if (shift <= 63) + { + long add = value + roundConst; + + if ((~value & (value ^ add)) < 0L) + { + return (long)((ulong)add >> shift); + } + else + { + return add >> shift; + } + } + else /* if (shift == 64) */ + { + return 0L; + } + } + } + + public static ulong UnsignedShrImm64(ulong value, long roundConst, int shift) + { + if (roundConst == 0L) + { + if (shift <= 63) + { + return value >> shift; + } + else /* if (shift == 64) */ + { + return 0UL; + } + } + else /* if (roundConst == 1L << (shift - 1)) */ + { + ulong add = value + (ulong)roundConst; + + if ((add < value) && (add < (ulong)roundConst)) + { + if (shift <= 63) + { + return (add >> shift) | (0x8000000000000000UL >> (shift - 1)); + } + else /* if (shift == 64) */ + { + return 1UL; + } + } + else + { + if (shift <= 63) + { + return add >> shift; + } + else /* if (shift == 64) */ + { + return 0UL; + } + } + } + } + #endregion + + #region "Saturation" + public static int SatF32ToS32(float value) + { + if (float.IsNaN(value)) + { + return 0; + } + + return value >= int.MaxValue ? int.MaxValue : + value <= int.MinValue ? int.MinValue : (int)value; + } + + public static long SatF32ToS64(float value) + { + if (float.IsNaN(value)) + { + return 0; + } + + return value >= long.MaxValue ? long.MaxValue : + value <= long.MinValue ? long.MinValue : (long)value; + } + + public static uint SatF32ToU32(float value) + { + if (float.IsNaN(value)) + { + return 0; + } + + return value >= uint.MaxValue ? uint.MaxValue : + value <= uint.MinValue ? uint.MinValue : (uint)value; + } + + public static ulong SatF32ToU64(float value) + { + if (float.IsNaN(value)) + { + return 0; + } + + return value >= ulong.MaxValue ? ulong.MaxValue : + value <= ulong.MinValue ? ulong.MinValue : (ulong)value; + } + + public static int SatF64ToS32(double value) + { + if (double.IsNaN(value)) + { + return 0; + } + + return value >= int.MaxValue ? int.MaxValue : + value <= int.MinValue ? int.MinValue : (int)value; + } + + public static long SatF64ToS64(double value) + { + if (double.IsNaN(value)) + { + return 0; + } + + return value >= long.MaxValue ? long.MaxValue : + value <= long.MinValue ? long.MinValue : (long)value; + } + + public static uint SatF64ToU32(double value) + { + if (double.IsNaN(value)) + { + return 0; + } + + return value >= uint.MaxValue ? uint.MaxValue : + value <= uint.MinValue ? uint.MinValue : (uint)value; + } + + public static ulong SatF64ToU64(double value) + { + if (double.IsNaN(value)) + { + return 0; + } + + return value >= ulong.MaxValue ? ulong.MaxValue : + value <= ulong.MinValue ? ulong.MinValue : (ulong)value; + } + #endregion + + #region "Count" + public static ulong CountLeadingSigns(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). + { + value ^= value >> 1; + + int highBit = size - 2; + + for (int bit = highBit; bit >= 0; bit--) + { + if (((int)(value >> bit) & 0b1) != 0) + { + return (ulong)(highBit - bit); + } + } + + return (ulong)(size - 1); + } + + private static ReadOnlySpan ClzNibbleTbl => new byte[] { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; + + public static ulong CountLeadingZeros(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). + { + if (value == 0ul) + { + return (ulong)size; + } + + int nibbleIdx = size; + int preCount, count = 0; + + do + { + nibbleIdx -= 4; + preCount = ClzNibbleTbl[(int)(value >> nibbleIdx) & 0b1111]; + count += preCount; + } + while (preCount == 4); + + return (ulong)count; + } + #endregion + + #region "Table" + public static V128 Tbl1(V128 vector, int bytes, V128 tb0) + { + return TblOrTbx(default, vector, bytes, tb0); + } + + public static V128 Tbl2(V128 vector, int bytes, V128 tb0, V128 tb1) + { + return TblOrTbx(default, vector, bytes, tb0, tb1); + } + + public static V128 Tbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2) + { + return TblOrTbx(default, vector, bytes, tb0, tb1, tb2); + } + + public static V128 Tbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3) + { + return TblOrTbx(default, vector, bytes, tb0, tb1, tb2, tb3); + } + + public static V128 Tbx1(V128 dest, V128 vector, int bytes, V128 tb0) + { + return TblOrTbx(dest, vector, bytes, tb0); + } + + public static V128 Tbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1) + { + return TblOrTbx(dest, vector, bytes, tb0, tb1); + } + + public static V128 Tbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2) + { + return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2); + } + + public static V128 Tbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3) + { + return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2, tb3); + } + + private static V128 TblOrTbx(V128 dest, V128 vector, int bytes, params V128[] tb) + { + byte[] res = new byte[16]; + + if (dest != default) + { + Buffer.BlockCopy(dest.ToArray(), 0, res, 0, bytes); + } + + byte[] table = new byte[tb.Length * 16]; + + for (byte index = 0; index < tb.Length; index++) + { + Buffer.BlockCopy(tb[index].ToArray(), 0, table, index * 16, 16); + } + + byte[] v = vector.ToArray(); + + for (byte index = 0; index < bytes; index++) + { + byte tblIndex = v[index]; + + if (tblIndex < table.Length) + { + res[index] = table[tblIndex]; + } + } + + return new V128(res); + } + #endregion + + #region "Crc32" + private const uint Crc32RevPoly = 0xedb88320; + private const uint Crc32cRevPoly = 0x82f63b78; + + public static uint Crc32b(uint crc, byte value) => Crc32(crc, Crc32RevPoly, value); + public static uint Crc32h(uint crc, ushort value) => Crc32h(crc, Crc32RevPoly, value); + public static uint Crc32w(uint crc, uint value) => Crc32w(crc, Crc32RevPoly, value); + public static uint Crc32x(uint crc, ulong value) => Crc32x(crc, Crc32RevPoly, value); + + public static uint Crc32cb(uint crc, byte value) => Crc32(crc, Crc32cRevPoly, value); + public static uint Crc32ch(uint crc, ushort value) => Crc32h(crc, Crc32cRevPoly, value); + public static uint Crc32cw(uint crc, uint value) => Crc32w(crc, Crc32cRevPoly, value); + public static uint Crc32cx(uint crc, ulong value) => Crc32x(crc, Crc32cRevPoly, value); + + private static uint Crc32h(uint crc, uint poly, ushort val) + { + crc = Crc32(crc, poly, (byte)(val >> 0)); + crc = Crc32(crc, poly, (byte)(val >> 8)); + + return crc; + } + + private static uint Crc32w(uint crc, uint poly, uint val) + { + crc = Crc32(crc, poly, (byte)(val >> 0)); + crc = Crc32(crc, poly, (byte)(val >> 8)); + crc = Crc32(crc, poly, (byte)(val >> 16)); + crc = Crc32(crc, poly, (byte)(val >> 24)); + + return crc; + } + + private static uint Crc32x(uint crc, uint poly, ulong val) + { + crc = Crc32(crc, poly, (byte)(val >> 0)); + crc = Crc32(crc, poly, (byte)(val >> 8)); + crc = Crc32(crc, poly, (byte)(val >> 16)); + crc = Crc32(crc, poly, (byte)(val >> 24)); + crc = Crc32(crc, poly, (byte)(val >> 32)); + crc = Crc32(crc, poly, (byte)(val >> 40)); + crc = Crc32(crc, poly, (byte)(val >> 48)); + crc = Crc32(crc, poly, (byte)(val >> 56)); + + return crc; + } + + private static uint Crc32(uint crc, uint poly, byte val) + { + crc ^= val; + + for (int bit = 7; bit >= 0; bit--) + { + uint mask = (uint)(-(int)(crc & 1)); + + crc = (crc >> 1) ^ (poly & mask); + } + + return crc; + } + #endregion + + #region "Aes" + public static V128 Decrypt(V128 value, V128 roundKey) + { + return CryptoHelper.AesInvSubBytes(CryptoHelper.AesInvShiftRows(value ^ roundKey)); + } + + public static V128 Encrypt(V128 value, V128 roundKey) + { + return CryptoHelper.AesSubBytes(CryptoHelper.AesShiftRows(value ^ roundKey)); + } + + public static V128 InverseMixColumns(V128 value) + { + return CryptoHelper.AesInvMixColumns(value); + } + + public static V128 MixColumns(V128 value) + { + return CryptoHelper.AesMixColumns(value); + } + #endregion + + #region "Sha1" + public static V128 HashChoose(V128 hash_abcd, uint hash_e, V128 wk) + { + for (int e = 0; e <= 3; e++) + { + uint t = ShaChoose(hash_abcd.Extract(1), + hash_abcd.Extract(2), + hash_abcd.Extract(3)); + + hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e); + + t = Rol(hash_abcd.Extract(1), 30); + + hash_abcd.Insert(1, t); + + Rol32_160(ref hash_e, ref hash_abcd); + } + + return hash_abcd; + } + + public static uint FixedRotate(uint hash_e) + { + return hash_e.Rol(30); + } + + public static V128 HashMajority(V128 hash_abcd, uint hash_e, V128 wk) + { + for (int e = 0; e <= 3; e++) + { + uint t = ShaMajority(hash_abcd.Extract(1), + hash_abcd.Extract(2), + hash_abcd.Extract(3)); + + hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e); + + t = Rol(hash_abcd.Extract(1), 30); + + hash_abcd.Insert(1, t); + + Rol32_160(ref hash_e, ref hash_abcd); + } + + return hash_abcd; + } + + public static V128 HashParity(V128 hash_abcd, uint hash_e, V128 wk) + { + for (int e = 0; e <= 3; e++) + { + uint t = ShaParity(hash_abcd.Extract(1), + hash_abcd.Extract(2), + hash_abcd.Extract(3)); + + hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e); + + t = Rol(hash_abcd.Extract(1), 30); + + hash_abcd.Insert(1, t); + + Rol32_160(ref hash_e, ref hash_abcd); + } + + return hash_abcd; + } + + public static V128 Sha1SchedulePart1(V128 w0_3, V128 w4_7, V128 w8_11) + { + ulong t2 = w4_7.Extract(0); + ulong t1 = w0_3.Extract(1); + + V128 result = new(t1, t2); + + return result ^ (w0_3 ^ w8_11); + } + + public static V128 Sha1SchedulePart2(V128 tw0_3, V128 w12_15) + { + V128 t = tw0_3 ^ (w12_15 >> 32); + + uint tE0 = t.Extract(0); + uint tE1 = t.Extract(1); + uint tE2 = t.Extract(2); + uint tE3 = t.Extract(3); + + return new V128(tE0.Rol(1), tE1.Rol(1), tE2.Rol(1), tE3.Rol(1) ^ tE0.Rol(2)); + } + + private static void Rol32_160(ref uint y, ref V128 x) + { + uint xE3 = x.Extract(3); + + x <<= 32; + x.Insert(0, y); + + y = xE3; + } + + private static uint ShaChoose(uint x, uint y, uint z) + { + return ((y ^ z) & x) ^ z; + } + + private static uint ShaMajority(uint x, uint y, uint z) + { + return (x & y) | ((x | y) & z); + } + + private static uint ShaParity(uint x, uint y, uint z) + { + return x ^ y ^ z; + } + + private static uint Rol(this uint value, int count) + { + return (value << count) | (value >> (32 - count)); + } + #endregion + + #region "Sha256" + public static V128 HashLower(V128 hash_abcd, V128 hash_efgh, V128 wk) + { + return Sha256Hash(hash_abcd, hash_efgh, wk, part1: true); + } + + public static V128 HashUpper(V128 hash_abcd, V128 hash_efgh, V128 wk) + { + return Sha256Hash(hash_abcd, hash_efgh, wk, part1: false); + } + + public static V128 Sha256SchedulePart1(V128 w0_3, V128 w4_7) + { + V128 result = new(); + + for (int e = 0; e <= 3; e++) + { + uint elt = (e <= 2 ? w0_3 : w4_7).Extract(e <= 2 ? e + 1 : 0); + + elt = elt.Ror(7) ^ elt.Ror(18) ^ elt.Lsr(3); + + elt += w0_3.Extract(e); + + result.Insert(e, elt); + } + + return result; + } + + public static V128 Sha256SchedulePart2(V128 w0_3, V128 w8_11, V128 w12_15) + { + V128 result = new(); + + ulong t1 = w12_15.Extract(1); + + for (int e = 0; e <= 1; e++) + { + uint elt = t1.ULongPart(e); + + elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10); + + elt += w0_3.Extract(e) + w8_11.Extract(e + 1); + + result.Insert(e, elt); + } + + t1 = result.Extract(0); + + for (int e = 2; e <= 3; e++) + { + uint elt = t1.ULongPart(e - 2); + + elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10); + + elt += w0_3.Extract(e) + (e == 2 ? w8_11 : w12_15).Extract(e == 2 ? 3 : 0); + + result.Insert(e, elt); + } + + return result; + } + + private static V128 Sha256Hash(V128 x, V128 y, V128 w, bool part1) + { + for (int e = 0; e <= 3; e++) + { + uint chs = ShaChoose(y.Extract(0), + y.Extract(1), + y.Extract(2)); + + uint maj = ShaMajority(x.Extract(0), + x.Extract(1), + x.Extract(2)); + + uint t1 = y.Extract(3) + ShaHashSigma1(y.Extract(0)) + chs + w.Extract(e); + + uint t2 = t1 + x.Extract(3); + + x.Insert(3, t2); + + t2 = t1 + ShaHashSigma0(x.Extract(0)) + maj; + + y.Insert(3, t2); + + Rol32_256(ref y, ref x); + } + + return part1 ? x : y; + } + + private static void Rol32_256(ref V128 y, ref V128 x) + { + uint yE3 = y.Extract(3); + uint xE3 = x.Extract(3); + + y <<= 32; + x <<= 32; + + y.Insert(0, xE3); + x.Insert(0, yE3); + } + + private static uint ShaHashSigma0(uint x) + { + return x.Ror(2) ^ x.Ror(13) ^ x.Ror(22); + } + + private static uint ShaHashSigma1(uint x) + { + return x.Ror(6) ^ x.Ror(11) ^ x.Ror(25); + } + + private static uint Ror(this uint value, int count) + { + return (value >> count) | (value << (32 - count)); + } + + private static uint Lsr(this uint value, int count) + { + return value >> count; + } + + private static uint ULongPart(this ulong value, int part) + { + return part == 0 + ? (uint)(value & 0xFFFFFFFFUL) + : (uint)(value >> 32); + } + #endregion + + public static V128 PolynomialMult64_128(ulong op1, ulong op2) + { + V128 result = V128.Zero; + + V128 op2_128 = new(op2, 0); + + for (int i = 0; i < 64; i++) + { + if (((op1 >> i) & 1) == 1) + { + result ^= op2_128 << i; + } + } + + return result; + } + } +} diff --git a/src/ARMeilleure/Instructions/SoftFloat.cs b/src/ARMeilleure/Instructions/SoftFloat.cs new file mode 100644 index 000000000..a67349e6e --- /dev/null +++ b/src/ARMeilleure/Instructions/SoftFloat.cs @@ -0,0 +1,3526 @@ +using ARMeilleure.State; +using System; +using System.Diagnostics; + +namespace ARMeilleure.Instructions +{ + static class SoftFloat + { + static SoftFloat() + { + RecipEstimateTable = BuildRecipEstimateTable(); + RecipSqrtEstimateTable = BuildRecipSqrtEstimateTable(); + } + + public static readonly byte[] RecipEstimateTable; + public static readonly byte[] RecipSqrtEstimateTable; + + private static byte[] BuildRecipEstimateTable() + { + byte[] tbl = new byte[256]; + + for (int idx = 0; idx < 256; idx++) + { + uint src = (uint)idx + 256u; + + Debug.Assert(256u <= src && src < 512u); + + src = (src << 1) + 1u; + + uint aux = (1u << 19) / src; + + uint dst = (aux + 1u) >> 1; + + Debug.Assert(256u <= dst && dst < 512u); + + tbl[idx] = (byte)(dst - 256u); + } + + return tbl; + } + + private static byte[] BuildRecipSqrtEstimateTable() + { + byte[] tbl = new byte[384]; + + for (int idx = 0; idx < 384; idx++) + { + uint src = (uint)idx + 128u; + + Debug.Assert(128u <= src && src < 512u); + + if (src < 256u) + { + src = (src << 1) + 1u; + } + else + { + src = (src >> 1) << 1; + src = (src + 1u) << 1; + } + + uint aux = 512u; + + while (src * (aux + 1u) * (aux + 1u) < (1u << 28)) + { + aux++; + } + + uint dst = (aux + 1u) >> 1; + + Debug.Assert(256u <= dst && dst < 512u); + + tbl[idx] = (byte)(dst - 256u); + } + + return tbl; + } + + public static void FPProcessException(FPException exc, ExecutionContext context) + { + FPProcessException(exc, context, context.Fpcr); + } + + public static void FPProcessException(FPException exc, ExecutionContext context, FPCR fpcr) + { + int enable = (int)exc + 8; + + if ((fpcr & (FPCR)(1 << enable)) != 0) + { + throw new NotImplementedException("Floating-point trap handling."); + } + else + { + context.Fpsr |= (FPSR)(1 << (int)exc); + } + } + + public static FPRoundingMode GetRoundingMode(this FPCR fpcr) + { + const int RModeShift = 22; + + return (FPRoundingMode)(((uint)fpcr >> RModeShift) & 3u); + } + } + + static class SoftFloat16 + { + public static ushort FPDefaultNaN() + { + return (ushort)0x7E00u; + } + + public static ushort FPInfinity(bool sign) + { + return sign ? (ushort)0xFC00u : (ushort)0x7C00u; + } + + public static ushort FPZero(bool sign) + { + return sign ? (ushort)0x8000u : (ushort)0x0000u; + } + + public static ushort FPMaxNormal(bool sign) + { + return sign ? (ushort)0xFBFFu : (ushort)0x7BFFu; + } + + public static double FPUnpackCv( + this ushort valueBits, + out FPType type, + out bool sign, + ExecutionContext context) + { + sign = (~(uint)valueBits & 0x8000u) == 0u; + + uint exp16 = ((uint)valueBits & 0x7C00u) >> 10; + uint frac16 = (uint)valueBits & 0x03FFu; + + double real; + + if (exp16 == 0u) + { + if (frac16 == 0u) + { + type = FPType.Zero; + real = 0d; + } + else + { + type = FPType.Nonzero; // Subnormal. + real = Math.Pow(2d, -14) * ((double)frac16 * Math.Pow(2d, -10)); + } + } + else if (exp16 == 0x1Fu && (context.Fpcr & FPCR.Ahp) == 0) + { + if (frac16 == 0u) + { + type = FPType.Infinity; + real = Math.Pow(2d, 1000); + } + else + { + type = (~frac16 & 0x0200u) == 0u ? FPType.QNaN : FPType.SNaN; + real = 0d; + } + } + else + { + type = FPType.Nonzero; // Normal. + real = Math.Pow(2d, (int)exp16 - 15) * (1d + (double)frac16 * Math.Pow(2d, -10)); + } + + return sign ? -real : real; + } + + public static ushort FPRoundCv(double real, ExecutionContext context) + { + const int MinimumExp = -14; + + const int E = 5; + const int F = 10; + + bool sign; + double mantissa; + + if (real < 0d) + { + sign = true; + mantissa = -real; + } + else + { + sign = false; + mantissa = real; + } + + int exponent = 0; + + while (mantissa < 1d) + { + mantissa *= 2d; + exponent--; + } + + while (mantissa >= 2d) + { + mantissa /= 2d; + exponent++; + } + + uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0); + + if (biasedExp == 0u) + { + mantissa /= Math.Pow(2d, MinimumExp - exponent); + } + + uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, F)); + double error = mantissa * Math.Pow(2d, F) - (double)intMant; + + if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0)) + { + SoftFloat.FPProcessException(FPException.Underflow, context); + } + + bool overflowToInf; + bool roundUp; + + switch (context.Fpcr.GetRoundingMode()) + { + case FPRoundingMode.ToNearest: + roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u)); + overflowToInf = true; + break; + + case FPRoundingMode.TowardsPlusInfinity: + roundUp = (error != 0d && !sign); + overflowToInf = !sign; + break; + + case FPRoundingMode.TowardsMinusInfinity: + roundUp = (error != 0d && sign); + overflowToInf = sign; + break; + + case FPRoundingMode.TowardsZero: + roundUp = false; + overflowToInf = false; + break; + + default: + throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.GetRoundingMode()}\"."); + } + + if (roundUp) + { + intMant++; + + if (intMant == 1u << F) + { + biasedExp = 1u; + } + + if (intMant == 1u << (F + 1)) + { + biasedExp++; + intMant >>= 1; + } + } + + ushort resultBits; + + if ((context.Fpcr & FPCR.Ahp) == 0) + { + if (biasedExp >= (1u << E) - 1u) + { + resultBits = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu)); + } + } + else + { + if (biasedExp >= 1u << E) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + SoftFloat.FPProcessException(FPException.InvalidOp, context); + + error = 0d; + } + else + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu)); + } + } + + if (error != 0d) + { + SoftFloat.FPProcessException(FPException.Inexact, context); + } + + return resultBits; + } + } + + static class SoftFloat16_32 + { + public static float FPConvert(ushort valueBits) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if ((context.Fpcr & FPCR.Dn) != 0) + { + result = SoftFloat32.FPDefaultNaN(); + } + else + { + result = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + result = SoftFloat32.FPInfinity(sign); + } + else if (type == FPType.Zero) + { + result = SoftFloat32.FPZero(sign); + } + else + { + result = FPRoundCv(real, context); + } + + return result; + } + + private static float FPRoundCv(double real, ExecutionContext context) + { + const int MinimumExp = -126; + + const int E = 8; + const int F = 23; + + bool sign; + double mantissa; + + if (real < 0d) + { + sign = true; + mantissa = -real; + } + else + { + sign = false; + mantissa = real; + } + + int exponent = 0; + + while (mantissa < 1d) + { + mantissa *= 2d; + exponent--; + } + + while (mantissa >= 2d) + { + mantissa /= 2d; + exponent++; + } + + if ((context.Fpcr & FPCR.Fz) != 0 && exponent < MinimumExp) + { + context.Fpsr |= FPSR.Ufc; + + return SoftFloat32.FPZero(sign); + } + + uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0); + + if (biasedExp == 0u) + { + mantissa /= Math.Pow(2d, MinimumExp - exponent); + } + + uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, F)); + double error = mantissa * Math.Pow(2d, F) - (double)intMant; + + if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0)) + { + SoftFloat.FPProcessException(FPException.Underflow, context); + } + + bool overflowToInf; + bool roundUp; + + switch (context.Fpcr.GetRoundingMode()) + { + case FPRoundingMode.ToNearest: + roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u)); + overflowToInf = true; + break; + + case FPRoundingMode.TowardsPlusInfinity: + roundUp = (error != 0d && !sign); + overflowToInf = !sign; + break; + + case FPRoundingMode.TowardsMinusInfinity: + roundUp = (error != 0d && sign); + overflowToInf = sign; + break; + + case FPRoundingMode.TowardsZero: + roundUp = false; + overflowToInf = false; + break; + + default: + throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.GetRoundingMode()}\"."); + } + + if (roundUp) + { + intMant++; + + if (intMant == 1u << F) + { + biasedExp = 1u; + } + + if (intMant == 1u << (F + 1)) + { + biasedExp++; + intMant >>= 1; + } + } + + float result; + + if (biasedExp >= (1u << E) - 1u) + { + result = overflowToInf ? SoftFloat32.FPInfinity(sign) : SoftFloat32.FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (biasedExp & 0xFFu) << 23 | (intMant & 0x007FFFFFu))); + } + + if (error != 0d) + { + SoftFloat.FPProcessException(FPException.Inexact, context); + } + + return result; + } + + private static float FPConvertNaN(ushort valueBits) + { + return BitConverter.Int32BitsToSingle( + (int)(((uint)valueBits & 0x8000u) << 16 | 0x7FC00000u | ((uint)valueBits & 0x01FFu) << 13)); + } + } + + static class SoftFloat16_64 + { + public static double FPConvert(ushort valueBits) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if ((context.Fpcr & FPCR.Dn) != 0) + { + result = SoftFloat64.FPDefaultNaN(); + } + else + { + result = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + result = SoftFloat64.FPInfinity(sign); + } + else if (type == FPType.Zero) + { + result = SoftFloat64.FPZero(sign); + } + else + { + result = FPRoundCv(real, context); + } + + return result; + } + + private static double FPRoundCv(double real, ExecutionContext context) + { + const int MinimumExp = -1022; + + const int E = 11; + const int F = 52; + + bool sign; + double mantissa; + + if (real < 0d) + { + sign = true; + mantissa = -real; + } + else + { + sign = false; + mantissa = real; + } + + int exponent = 0; + + while (mantissa < 1d) + { + mantissa *= 2d; + exponent--; + } + + while (mantissa >= 2d) + { + mantissa /= 2d; + exponent++; + } + + if ((context.Fpcr & FPCR.Fz) != 0 && exponent < MinimumExp) + { + context.Fpsr |= FPSR.Ufc; + + return SoftFloat64.FPZero(sign); + } + + uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0); + + if (biasedExp == 0u) + { + mantissa /= Math.Pow(2d, MinimumExp - exponent); + } + + ulong intMant = (ulong)Math.Floor(mantissa * Math.Pow(2d, F)); + double error = mantissa * Math.Pow(2d, F) - (double)intMant; + + if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0)) + { + SoftFloat.FPProcessException(FPException.Underflow, context); + } + + bool overflowToInf; + bool roundUp; + + switch (context.Fpcr.GetRoundingMode()) + { + case FPRoundingMode.ToNearest: + roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u)); + overflowToInf = true; + break; + + case FPRoundingMode.TowardsPlusInfinity: + roundUp = (error != 0d && !sign); + overflowToInf = !sign; + break; + + case FPRoundingMode.TowardsMinusInfinity: + roundUp = (error != 0d && sign); + overflowToInf = sign; + break; + + case FPRoundingMode.TowardsZero: + roundUp = false; + overflowToInf = false; + break; + + default: + throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.GetRoundingMode()}\"."); + } + + if (roundUp) + { + intMant++; + + if (intMant == 1ul << F) + { + biasedExp = 1u; + } + + if (intMant == 1ul << (F + 1)) + { + biasedExp++; + intMant >>= 1; + } + } + + double result; + + if (biasedExp >= (1u << E) - 1u) + { + result = overflowToInf ? SoftFloat64.FPInfinity(sign) : SoftFloat64.FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + result = BitConverter.Int64BitsToDouble( + (long)((sign ? 1ul : 0ul) << 63 | (biasedExp & 0x7FFul) << 52 | (intMant & 0x000FFFFFFFFFFFFFul))); + } + + if (error != 0d) + { + SoftFloat.FPProcessException(FPException.Inexact, context); + } + + return result; + } + + private static double FPConvertNaN(ushort valueBits) + { + return BitConverter.Int64BitsToDouble( + (long)(((ulong)valueBits & 0x8000ul) << 48 | 0x7FF8000000000000ul | ((ulong)valueBits & 0x01FFul) << 42)); + } + } + + static class SoftFloat32_16 + { + public static ushort FPConvert(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = value.FPUnpackCv(out FPType type, out bool sign, out uint valueBits, context); + + bool altHp = (context.Fpcr & FPCR.Ahp) != 0; + + ushort resultBits; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if (altHp) + { + resultBits = SoftFloat16.FPZero(sign); + } + else if ((context.Fpcr & FPCR.Dn) != 0) + { + resultBits = SoftFloat16.FPDefaultNaN(); + } + else + { + resultBits = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN || altHp) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + if (altHp) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + else + { + resultBits = SoftFloat16.FPInfinity(sign); + } + } + else if (type == FPType.Zero) + { + resultBits = SoftFloat16.FPZero(sign); + } + else + { + resultBits = SoftFloat16.FPRoundCv(real, context); + } + + return resultBits; + } + + private static double FPUnpackCv( + this float value, + out FPType type, + out bool sign, + out uint valueBits, + ExecutionContext context) + { + valueBits = (uint)BitConverter.SingleToInt32Bits(value); + + sign = (~valueBits & 0x80000000u) == 0u; + + uint exp32 = (valueBits & 0x7F800000u) >> 23; + uint frac32 = valueBits & 0x007FFFFFu; + + double real; + + if (exp32 == 0u) + { + if (frac32 == 0u || (context.Fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + real = 0d; + + if (frac32 != 0u) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context); + } + } + else + { + type = FPType.Nonzero; // Subnormal. + real = Math.Pow(2d, -126) * ((double)frac32 * Math.Pow(2d, -23)); + } + } + else if (exp32 == 0xFFu) + { + if (frac32 == 0u) + { + type = FPType.Infinity; + real = Math.Pow(2d, 1000); + } + else + { + type = (~frac32 & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN; + real = 0d; + } + } + else + { + type = FPType.Nonzero; // Normal. + real = Math.Pow(2d, (int)exp32 - 127) * (1d + (double)frac32 * Math.Pow(2d, -23)); + } + + return sign ? -real : real; + } + + private static ushort FPConvertNaN(uint valueBits) + { + return (ushort)((valueBits & 0x80000000u) >> 16 | 0x7E00u | (valueBits & 0x003FE000u) >> 13); + } + } + + static class SoftFloat32 + { + public static float FPAdd(float value1, float value2) + { + return FPAddFpscr(value1, value2, false); + } + + public static float FPAddFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == !sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && !sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 + value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static int FPCompare(float value1, float value2, bool signalNaNs) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + int result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = 0b0011; + + if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + if (value1 == value2) + { + result = 0b0110; + } + else if (value1 < value2) + { + result = 0b1000; + } + else + { + result = 0b0010; + } + } + + return result; + } + + public static float FPCompareEQ(float value1, float value2) + { + return FPCompareEQFpscr(value1, value2, false); + } + + public static float FPCompareEQFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + if (type1 == FPType.SNaN || type2 == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + result = ZerosOrOnes(value1 == value2); + } + + return result; + } + + public static float FPCompareGE(float value1, float value2) + { + return FPCompareGEFpscr(value1, value2, false); + } + + public static float FPCompareGEFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 >= value2); + } + + return result; + } + + public static float FPCompareGT(float value1, float value2) + { + return FPCompareGTFpscr(value1, value2, false); + } + + public static float FPCompareGTFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 > value2); + } + + return result; + } + + public static float FPCompareLE(float value1, float value2) + { + return FPCompareGE(value2, value1); + } + + public static float FPCompareLT(float value1, float value2) + { + return FPCompareGT(value2, value1); + } + + public static float FPCompareLEFpscr(float value1, float value2, bool standardFpscr) + { + return FPCompareGEFpscr(value2, value1, standardFpscr); + } + + public static float FPCompareLTFpscr(float value1, float value2, bool standardFpscr) + { + return FPCompareGTFpscr(value2, value1, standardFpscr); + } + + public static float FPDiv(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && inf2) || (zero1 && zero2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || zero2) + { + result = FPInfinity(sign1 ^ sign2); + + if (!inf1) + { + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + } + else if (zero1 || inf2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 / value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPMax(float value1, float value2) + { + return FPMaxFpscr(value1, value2, false); + } + + public static float FPMaxFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 > value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + } + + return result; + } + + public static float FPMaxNum(float value1, float value2) + { + return FPMaxNumFpscr(value1, value2, false); + } + + public static float FPMaxNumFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(true); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(true); + } + + return FPMaxFpscr(value1, value2, standardFpscr); + } + + public static float FPMin(float value1, float value2) + { + return FPMinFpscr(value1, value2, false); + } + + public static float FPMinFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 < value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + } + + return result; + } + + public static float FPMinNum(float value1, float value2) + { + return FPMinNumFpscr(value1, value2, false); + } + + public static float FPMinNumFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(false); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(false); + } + + return FPMinFpscr(value1, value2, standardFpscr); + } + + public static float FPMul(float value1, float value2) + { + return FPMulFpscr(value1, value2, false); + } + + public static float FPMulFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPMulAdd(float valueA, float value1, float value2) + { + return FPMulAddFpscr(valueA, value1, value2, false); + } + + public static float FPMulAddFpscr(float valueA, float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out uint addend, context, fpcr); + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + float result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr); + + if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2))) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if (!done) + { + bool infA = typeA == FPType.Infinity; + bool zeroA = typeA == FPType.Zero; + + bool signP = sign1 ^ sign2; + bool infP = inf1 || inf2; + bool zeroP = zero1 || zero2; + + if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((infA && !signA) || (infP && !signP)) + { + result = FPInfinity(false); + } + else if ((infA && signA) || (infP && signP)) + { + result = FPInfinity(true); + } + else if (zeroA && zeroP && signA == signP) + { + result = FPZero(signA); + } + else + { + result = MathF.FusedMultiplyAdd(value1, value2, valueA); + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPMulSub(float valueA, float value1, float value2) + { + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPMulSubFpscr(float valueA, float value1, float value2, bool standardFpscr) + { + value1 = value1.FPNeg(); + + return FPMulAddFpscr(valueA, value1, value2, standardFpscr); + } + + public static float FPMulX(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(sign1 ^ sign2); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPNegMulAdd(float valueA, float value1, float value2) + { + valueA = valueA.FPNeg(); + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPNegMulSub(float valueA, float value1, float value2) + { + valueA = valueA.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPRecipEstimate(float value) + { + return FPRecipEstimateFpscr(value, false); + } + + public static float FPRecipEstimateFpscr(float value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(sign); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (MathF.Abs(value) < MathF.Pow(2f, -128)) + { + var overflowToInf = fpcr.GetRoundingMode() switch + { + FPRoundingMode.ToNearest => true, + FPRoundingMode.TowardsPlusInfinity => !sign, + FPRoundingMode.TowardsMinusInfinity => sign, + FPRoundingMode.TowardsZero => false, + _ => throw new ArgumentException($"Invalid rounding mode \"{fpcr.GetRoundingMode()}\"."), + }; + result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context, fpcr); + SoftFloat.FPProcessException(FPException.Inexact, context, fpcr); + } + else if ((fpcr & FPCR.Fz) != 0 && (MathF.Abs(value) >= MathF.Pow(2f, 126))) + { + result = FPZero(sign); + + context.Fpsr |= FPSR.Ufc; + } + else + { + ulong fraction = (ulong)(op & 0x007FFFFFu) << 29; + uint exp = (op & 0x7F800000u) >> 23; + + if (exp == 0u) + { + if ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2; + exp -= 1u; + } + else + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + } + + uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + + uint resultExp = 253u - exp; + + uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u; + + fraction = (ulong)(estimate & 0xFFu) << 44; + + if (resultExp == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1; + } + else if (resultExp + 1u == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2; + resultExp = 0u; + } + + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (resultExp & 0xFFu) << 23 | (uint)(fraction >> 29) & 0x007FFFFFu)); + } + + return result; + } + + public static float FPRecipStep(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out _, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + float product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPSubFpscr(FPTwo(false), product, true); + } + + return result; + } + + public static float FPRecipStepFused(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = MathF.FusedMultiplyAdd(value1, value2, 2f); + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPRecpX(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else + { + uint notExp = (~op >> 23) & 0xFFu; + uint maxExp = 0xFEu; + + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (notExp == 0xFFu ? maxExp : notExp) << 23)); + } + + return result; + } + + public static float FPRSqrtEstimate(float value) + { + return FPRSqrtEstimateFpscr(value, false); + } + + public static float FPRSqrtEstimateFpscr(float value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(false); + } + else + { + ulong fraction = (ulong)(op & 0x007FFFFFu) << 29; + uint exp = (op & 0x7F800000u) >> 23; + + if (exp == 0u) + { + while ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + exp -= 1u; + } + + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + + uint scaled; + + if ((exp & 1u) == 0u) + { + scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + } + else + { + scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45); + } + + uint resultExp = (380u - exp) >> 1; + + uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u; + + result = BitConverter.Int32BitsToSingle((int)((resultExp & 0xFFu) << 23 | (estimate & 0xFFu) << 15)); + } + + return result; + } + + public static float FPHalvedSub(float value1, float value2, ExecutionContext context, FPCR fpcr) + { + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = (value1 - value2) / 2.0f; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPRSqrtStep(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out _, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + float product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPHalvedSub(FPThree(false), product, context, fpcr); + } + + return result; + } + + public static float FPRSqrtStepFused(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPOnePointFive(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = MathF.FusedMultiplyAdd(value1, value2, 3f) / 2f; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPSqrt(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value = value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPZero(sign); + } + else if (type == FPType.Infinity && !sign) + { + result = FPInfinity(sign); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = MathF.Sqrt(value); + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + + return result; + } + + public static float FPSub(float value1, float value2) + { + return FPSubFpscr(value1, value2, false); + } + + public static float FPSubFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 - value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPDefaultNaN() + { + return BitConverter.Int32BitsToSingle(0x7fc00000); + } + + public static float FPInfinity(bool sign) + { + return sign ? float.NegativeInfinity : float.PositiveInfinity; + } + + public static float FPZero(bool sign) + { + return sign ? -0f : +0f; + } + + public static float FPMaxNormal(bool sign) + { + return sign ? float.MinValue : float.MaxValue; + } + + private static float FPTwo(bool sign) + { + return sign ? -2f : +2f; + } + + private static float FPThree(bool sign) + { + return sign ? -3f : +3f; + } + + private static float FPOnePointFive(bool sign) + { + return sign ? -1.5f : +1.5f; + } + + private static float FPNeg(this float value) + { + return -value; + } + + private static float ZerosOrOnes(bool ones) + { + return BitConverter.Int32BitsToSingle(ones ? -1 : 0); + } + + private static float FPUnpack( + this float value, + out FPType type, + out bool sign, + out uint valueBits, + ExecutionContext context, + FPCR fpcr) + { + valueBits = (uint)BitConverter.SingleToInt32Bits(value); + + sign = (~valueBits & 0x80000000u) == 0u; + + if ((valueBits & 0x7F800000u) == 0u) + { + if ((valueBits & 0x007FFFFFu) == 0u || (fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + value = FPZero(sign); + + if ((valueBits & 0x007FFFFFu) != 0u) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr); + } + } + else + { + type = FPType.Nonzero; + } + } + else if ((~valueBits & 0x7F800000u) == 0u) + { + if ((valueBits & 0x007FFFFFu) == 0u) + { + type = FPType.Infinity; + } + else + { + type = (~valueBits & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN; + value = FPZero(sign); + } + } + else + { + type = FPType.Nonzero; + } + + return value; + } + + private static float FPProcessNaNs( + FPType type1, + FPType type2, + uint op1, + uint op2, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static float FPProcessNaNs3( + FPType type1, + FPType type2, + FPType type3, + uint op1, + uint op2, + uint op3, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.SNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.QNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static float FPProcessNaN(FPType type, uint op, ExecutionContext context, FPCR fpcr) + { + if (type == FPType.SNaN) + { + op |= 1u << 22; + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if ((fpcr & FPCR.Dn) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int32BitsToSingle((int)op); + } + } + + static class SoftFloat64_16 + { + public static ushort FPConvert(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = value.FPUnpackCv(out FPType type, out bool sign, out ulong valueBits, context); + + bool altHp = (context.Fpcr & FPCR.Ahp) != 0; + + ushort resultBits; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if (altHp) + { + resultBits = SoftFloat16.FPZero(sign); + } + else if ((context.Fpcr & FPCR.Dn) != 0) + { + resultBits = SoftFloat16.FPDefaultNaN(); + } + else + { + resultBits = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN || altHp) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + if (altHp) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + else + { + resultBits = SoftFloat16.FPInfinity(sign); + } + } + else if (type == FPType.Zero) + { + resultBits = SoftFloat16.FPZero(sign); + } + else + { + resultBits = SoftFloat16.FPRoundCv(real, context); + } + + return resultBits; + } + + private static double FPUnpackCv( + this double value, + out FPType type, + out bool sign, + out ulong valueBits, + ExecutionContext context) + { + valueBits = (ulong)BitConverter.DoubleToInt64Bits(value); + + sign = (~valueBits & 0x8000000000000000ul) == 0u; + + ulong exp64 = (valueBits & 0x7FF0000000000000ul) >> 52; + ulong frac64 = valueBits & 0x000FFFFFFFFFFFFFul; + + double real; + + if (exp64 == 0u) + { + if (frac64 == 0u || (context.Fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + real = 0d; + + if (frac64 != 0u) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context); + } + } + else + { + type = FPType.Nonzero; // Subnormal. + real = Math.Pow(2d, -1022) * ((double)frac64 * Math.Pow(2d, -52)); + } + } + else if (exp64 == 0x7FFul) + { + if (frac64 == 0u) + { + type = FPType.Infinity; + real = Math.Pow(2d, 1000000); + } + else + { + type = (~frac64 & 0x0008000000000000ul) == 0u ? FPType.QNaN : FPType.SNaN; + real = 0d; + } + } + else + { + type = FPType.Nonzero; // Normal. + real = Math.Pow(2d, (int)exp64 - 1023) * (1d + (double)frac64 * Math.Pow(2d, -52)); + } + + return sign ? -real : real; + } + + private static ushort FPConvertNaN(ulong valueBits) + { + return (ushort)((valueBits & 0x8000000000000000ul) >> 48 | 0x7E00u | (valueBits & 0x0007FC0000000000ul) >> 42); + } + } + + static class SoftFloat64 + { + public static double FPAdd(double value1, double value2) + { + return FPAddFpscr(value1, value2, false); + } + + public static double FPAddFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == !sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && !sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 + value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static int FPCompare(double value1, double value2, bool signalNaNs) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + int result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = 0b0011; + + if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + if (value1 == value2) + { + result = 0b0110; + } + else if (value1 < value2) + { + result = 0b1000; + } + else + { + result = 0b0010; + } + } + + return result; + } + + public static double FPCompareEQ(double value1, double value2) + { + return FPCompareEQFpscr(value1, value2, false); + } + + public static double FPCompareEQFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + if (type1 == FPType.SNaN || type2 == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + result = ZerosOrOnes(value1 == value2); + } + + return result; + } + + public static double FPCompareGE(double value1, double value2) + { + return FPCompareGEFpscr(value1, value2, false); + } + + public static double FPCompareGEFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 >= value2); + } + + return result; + } + + public static double FPCompareGT(double value1, double value2) + { + return FPCompareGTFpscr(value1, value2, false); + } + + public static double FPCompareGTFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 > value2); + } + + return result; + } + + public static double FPCompareLE(double value1, double value2) + { + return FPCompareGE(value2, value1); + } + + public static double FPCompareLT(double value1, double value2) + { + return FPCompareGT(value2, value1); + } + + public static double FPCompareLEFpscr(double value1, double value2, bool standardFpscr) + { + return FPCompareGEFpscr(value2, value1, standardFpscr); + } + + public static double FPCompareLTFpscr(double value1, double value2, bool standardFpscr) + { + return FPCompareGTFpscr(value2, value1, standardFpscr); + } + + public static double FPDiv(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && inf2) || (zero1 && zero2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || zero2) + { + result = FPInfinity(sign1 ^ sign2); + + if (!inf1) + { + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + } + else if (zero1 || inf2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 / value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPMax(double value1, double value2) + { + return FPMaxFpscr(value1, value2, false); + } + + public static double FPMaxFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 > value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + } + + return result; + } + + public static double FPMaxNum(double value1, double value2) + { + return FPMaxNumFpscr(value1, value2, false); + } + + public static double FPMaxNumFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(true); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(true); + } + + return FPMaxFpscr(value1, value2, standardFpscr); + } + + public static double FPMin(double value1, double value2) + { + return FPMinFpscr(value1, value2, false); + } + + public static double FPMinFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 < value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + } + + return result; + } + + public static double FPMinNum(double value1, double value2) + { + return FPMinNumFpscr(value1, value2, false); + } + + public static double FPMinNumFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(false); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(false); + } + + return FPMinFpscr(value1, value2, standardFpscr); + } + + public static double FPMul(double value1, double value2) + { + return FPMulFpscr(value1, value2, false); + } + + public static double FPMulFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPMulAdd(double valueA, double value1, double value2) + { + return FPMulAddFpscr(valueA, value1, value2, false); + } + + public static double FPMulAddFpscr(double valueA, double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out ulong addend, context, fpcr); + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + double result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr); + + if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2))) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if (!done) + { + bool infA = typeA == FPType.Infinity; + bool zeroA = typeA == FPType.Zero; + + bool signP = sign1 ^ sign2; + bool infP = inf1 || inf2; + bool zeroP = zero1 || zero2; + + if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((infA && !signA) || (infP && !signP)) + { + result = FPInfinity(false); + } + else if ((infA && signA) || (infP && signP)) + { + result = FPInfinity(true); + } + else if (zeroA && zeroP && signA == signP) + { + result = FPZero(signA); + } + else + { + result = Math.FusedMultiplyAdd(value1, value2, valueA); + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPMulSub(double valueA, double value1, double value2) + { + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPMulSubFpscr(double valueA, double value1, double value2, bool standardFpscr) + { + value1 = value1.FPNeg(); + + return FPMulAddFpscr(valueA, value1, value2, standardFpscr); + } + + public static double FPMulX(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(sign1 ^ sign2); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPNegMulAdd(double valueA, double value1, double value2) + { + valueA = valueA.FPNeg(); + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPNegMulSub(double valueA, double value1, double value2) + { + valueA = valueA.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPRecipEstimate(double value) + { + return FPRecipEstimateFpscr(value, false); + } + + public static double FPRecipEstimateFpscr(double value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(sign); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (Math.Abs(value) < Math.Pow(2d, -1024)) + { + var overflowToInf = fpcr.GetRoundingMode() switch + { + FPRoundingMode.ToNearest => true, + FPRoundingMode.TowardsPlusInfinity => !sign, + FPRoundingMode.TowardsMinusInfinity => sign, + FPRoundingMode.TowardsZero => false, + _ => throw new ArgumentException($"Invalid rounding mode \"{fpcr.GetRoundingMode()}\"."), + }; + result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context, fpcr); + SoftFloat.FPProcessException(FPException.Inexact, context, fpcr); + } + else if ((fpcr & FPCR.Fz) != 0 && (Math.Abs(value) >= Math.Pow(2d, 1022))) + { + result = FPZero(sign); + + context.Fpsr |= FPSR.Ufc; + } + else + { + ulong fraction = op & 0x000FFFFFFFFFFFFFul; + uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52); + + if (exp == 0u) + { + if ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2; + exp -= 1u; + } + else + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + } + + uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + + uint resultExp = 2045u - exp; + + uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u; + + fraction = (ulong)(estimate & 0xFFu) << 44; + + if (resultExp == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1; + } + else if (resultExp + 1u == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2; + resultExp = 0u; + } + + result = BitConverter.Int64BitsToDouble( + (long)((sign ? 1ul : 0ul) << 63 | (resultExp & 0x7FFul) << 52 | (fraction & 0x000FFFFFFFFFFFFFul))); + } + + return result; + } + + public static double FPRecipStep(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out _, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + double product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPSubFpscr(FPTwo(false), product, true); + } + + return result; + } + + public static double FPRecipStepFused(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = Math.FusedMultiplyAdd(value1, value2, 2d); + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPRecpX(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else + { + ulong notExp = (~op >> 52) & 0x7FFul; + ulong maxExp = 0x7FEul; + + result = BitConverter.Int64BitsToDouble( + (long)((sign ? 1ul : 0ul) << 63 | (notExp == 0x7FFul ? maxExp : notExp) << 52)); + } + + return result; + } + + public static double FPRSqrtEstimate(double value) + { + return FPRSqrtEstimateFpscr(value, false); + } + + public static double FPRSqrtEstimateFpscr(double value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(false); + } + else + { + ulong fraction = op & 0x000FFFFFFFFFFFFFul; + uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52); + + if (exp == 0u) + { + while ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + exp -= 1u; + } + + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + + uint scaled; + + if ((exp & 1u) == 0u) + { + scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + } + else + { + scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45); + } + + uint resultExp = (3068u - exp) >> 1; + + uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u; + + result = BitConverter.Int64BitsToDouble((long)((resultExp & 0x7FFul) << 52 | (estimate & 0xFFul) << 44)); + } + + return result; + } + + public static double FPHalvedSub(double value1, double value2, ExecutionContext context, FPCR fpcr) + { + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = (value1 - value2) / 2.0; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPRSqrtStep(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out _, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + double product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPHalvedSub(FPThree(false), product, context, fpcr); + } + + return result; + } + + public static double FPRSqrtStepFused(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPOnePointFive(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = Math.FusedMultiplyAdd(value1, value2, 3d) / 2d; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPSqrt(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value = value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPZero(sign); + } + else if (type == FPType.Infinity && !sign) + { + result = FPInfinity(sign); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = Math.Sqrt(value); + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + + return result; + } + + public static double FPSub(double value1, double value2) + { + return FPSubFpscr(value1, value2, false); + } + + public static double FPSubFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 - value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPDefaultNaN() + { + return BitConverter.Int64BitsToDouble(0x7ff8000000000000); + } + + public static double FPInfinity(bool sign) + { + return sign ? double.NegativeInfinity : double.PositiveInfinity; + } + + public static double FPZero(bool sign) + { + return sign ? -0d : +0d; + } + + public static double FPMaxNormal(bool sign) + { + return sign ? double.MinValue : double.MaxValue; + } + + private static double FPTwo(bool sign) + { + return sign ? -2d : +2d; + } + + private static double FPThree(bool sign) + { + return sign ? -3d : +3d; + } + + private static double FPOnePointFive(bool sign) + { + return sign ? -1.5d : +1.5d; + } + + private static double FPNeg(this double value) + { + return -value; + } + + private static double ZerosOrOnes(bool ones) + { + return BitConverter.Int64BitsToDouble(ones ? -1L : 0L); + } + + private static double FPUnpack( + this double value, + out FPType type, + out bool sign, + out ulong valueBits, + ExecutionContext context, + FPCR fpcr) + { + valueBits = (ulong)BitConverter.DoubleToInt64Bits(value); + + sign = (~valueBits & 0x8000000000000000ul) == 0ul; + + if ((valueBits & 0x7FF0000000000000ul) == 0ul) + { + if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul || (fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + value = FPZero(sign); + + if ((valueBits & 0x000FFFFFFFFFFFFFul) != 0ul) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr); + } + } + else + { + type = FPType.Nonzero; + } + } + else if ((~valueBits & 0x7FF0000000000000ul) == 0ul) + { + if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul) + { + type = FPType.Infinity; + } + else + { + type = (~valueBits & 0x0008000000000000ul) == 0ul ? FPType.QNaN : FPType.SNaN; + value = FPZero(sign); + } + } + else + { + type = FPType.Nonzero; + } + + return value; + } + + private static double FPProcessNaNs( + FPType type1, + FPType type2, + ulong op1, + ulong op2, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static double FPProcessNaNs3( + FPType type1, + FPType type2, + FPType type3, + ulong op1, + ulong op2, + ulong op3, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.SNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.QNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static double FPProcessNaN(FPType type, ulong op, ExecutionContext context, FPCR fpcr) + { + if (type == FPType.SNaN) + { + op |= 1ul << 51; + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if ((fpcr & FPCR.Dn) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int64BitsToDouble((long)op); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs b/src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs new file mode 100644 index 000000000..810461d7c --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + class BasicBlock : IEquatable, IIntrusiveListNode + { + private const uint MaxSuccessors = 2; + + private int _succCount; + private BasicBlock _succ0; + private readonly BasicBlock _succ1; + private HashSet _domFrontiers; + + public int Index { get; set; } + public BasicBlockFrequency Frequency { get; set; } + public BasicBlock ListPrevious { get; set; } + public BasicBlock ListNext { get; set; } + public IntrusiveList Operations { get; } + public List Predecessors { get; } + public BasicBlock ImmediateDominator { get; set; } + + public int SuccessorsCount => _succCount; + + public HashSet DominanceFrontiers + { + get + { + _domFrontiers ??= new HashSet(); + + return _domFrontiers; + } + } + + public BasicBlock() : this(index: -1) { } + + public BasicBlock(int index) + { + Operations = new IntrusiveList(); + Predecessors = new List(); + + Index = index; + } + + public void AddSuccessor(BasicBlock block) + { + ArgumentNullException.ThrowIfNull(block); + + if ((uint)_succCount + 1 > MaxSuccessors) + { + ThrowSuccessorOverflow(); + } + + block.Predecessors.Add(this); + + GetSuccessorUnsafe(_succCount++) = block; + } + + public void RemoveSuccessor(int index) + { + if ((uint)index >= (uint)_succCount) + { + ThrowOutOfRange(nameof(index)); + } + + ref BasicBlock oldBlock = ref GetSuccessorUnsafe(index); + + oldBlock.Predecessors.Remove(this); + oldBlock = null; + + if (index == 0) + { + _succ0 = _succ1; + } + + _succCount--; + } + + public BasicBlock GetSuccessor(int index) + { + if ((uint)index >= (uint)_succCount) + { + ThrowOutOfRange(nameof(index)); + } + + return GetSuccessorUnsafe(index); + } + + private ref BasicBlock GetSuccessorUnsafe(int index) + { + return ref Unsafe.Add(ref _succ0, index); + } + + public void SetSuccessor(int index, BasicBlock block) + { + ArgumentNullException.ThrowIfNull(block); + + if ((uint)index >= (uint)_succCount) + { + ThrowOutOfRange(nameof(index)); + } + + ref BasicBlock oldBlock = ref GetSuccessorUnsafe(index); + + oldBlock.Predecessors.Remove(this); + block.Predecessors.Add(this); + + oldBlock = block; + } + + public void Append(Operation node) + { + Operation last = Operations.Last; + + // Append node before terminal or to end if no terminal. + if (last == default) + { + Operations.AddLast(node); + + return; + } + + switch (last.Instruction) + { + case Instruction.Return: + case Instruction.Tailcall: + case Instruction.BranchIf: + Operations.AddBefore(last, node); + break; + + default: + Operations.AddLast(node); + break; + } + } + + private static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name); + private static void ThrowSuccessorOverflow() => throw new OverflowException($"BasicBlock can only have {MaxSuccessors} successors."); + + public bool Equals(BasicBlock other) + { + return other == this; + } + + public override bool Equals(object obj) + { + return Equals(obj as BasicBlock); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs b/src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs new file mode 100644 index 000000000..e4f7ae809 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum BasicBlockFrequency + { + Default, + Cold, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Comparison.cs b/src/ARMeilleure/IntermediateRepresentation/Comparison.cs new file mode 100644 index 000000000..3d6a9d818 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Comparison.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Comparison + { + Equal = 0, + NotEqual = 1, + Greater = 2, + LessOrEqual = 3, + GreaterUI = 4, + LessOrEqualUI = 5, + GreaterOrEqual = 6, + Less = 7, + GreaterOrEqualUI = 8, + LessUI = 9, + } + + static class ComparisonExtensions + { + public static Comparison Invert(this Comparison comp) + { + return (Comparison)((int)comp ^ 1); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs b/src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs new file mode 100644 index 000000000..05be8a5ae --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + interface IIntrusiveListNode + { + T ListPrevious { get; set; } + T ListNext { get; set; } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Instruction.cs b/src/ARMeilleure/IntermediateRepresentation/Instruction.cs new file mode 100644 index 000000000..9bae8d1fb --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Instruction.cs @@ -0,0 +1,72 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Instruction : ushort + { + Add, + BitwiseAnd, + BitwiseExclusiveOr, + BitwiseNot, + BitwiseOr, + BranchIf, + ByteSwap, + Call, + Compare, + CompareAndSwap, + CompareAndSwap16, + CompareAndSwap8, + ConditionalSelect, + ConvertI64ToI32, + ConvertToFP, + ConvertToFPUI, + Copy, + CountLeadingZeros, + Divide, + DivideUI, + Load, + Load16, + Load8, + LoadArgument, + MemoryBarrier, + Multiply, + Multiply64HighSI, + Multiply64HighUI, + Negate, + Return, + RotateRight, + ShiftLeft, + ShiftRightSI, + ShiftRightUI, + SignExtend16, + SignExtend32, + SignExtend8, + StackAlloc, + Store, + Store16, + Store8, + Subtract, + Tailcall, + VectorCreateScalar, + VectorExtract, + VectorExtract16, + VectorExtract8, + VectorInsert, + VectorInsert16, + VectorInsert8, + VectorOne, + VectorZero, + VectorZeroUpper64, + VectorZeroUpper96, + ZeroExtend16, + ZeroExtend32, + ZeroExtend8, + + Clobber, + Extended, + Fill, + LoadFromContext, + Phi, + Spill, + SpillArg, + StoreToContext, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs b/src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs new file mode 100644 index 000000000..b9cab6674 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs @@ -0,0 +1,641 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.IntermediateRepresentation +{ + [Flags] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum Intrinsic : ushort + { + // X86 (SSE and AVX) + + 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, + X86Ldmxcsr, + X86Maxpd, + X86Maxps, + X86Maxsd, + X86Maxss, + X86Minpd, + X86Minps, + X86Minsd, + X86Minss, + X86Movhlps, + X86Movlhps, + X86Movss, + X86Mulpd, + X86Mulps, + X86Mulsd, + X86Mulss, + 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, + X86Stmxcsr, + X86Subpd, + X86Subps, + X86Subsd, + X86Subss, + X86Unpckhpd, + X86Unpckhps, + X86Unpcklpd, + X86Unpcklps, + X86Vcvtph2ps, + X86Vcvtps2ph, + X86Vfmadd231pd, + X86Vfmadd231ps, + X86Vfmadd231sd, + X86Vfmadd231ss, + X86Vfmsub231sd, + X86Vfmsub231ss, + X86Vfnmadd231pd, + X86Vfnmadd231ps, + X86Vfnmadd231sd, + X86Vfnmadd231ss, + X86Vfnmsub231sd, + X86Vfnmsub231ss, + X86Vpternlogd, + X86Xorpd, + X86Xorps, + + // Arm64 (FP and Advanced SIMD) + + Arm64AbsS, + Arm64AbsV, + Arm64AddhnV, + Arm64AddpS, + Arm64AddpV, + Arm64AddvV, + Arm64AddS, + Arm64AddV, + Arm64AesdV, + Arm64AeseV, + Arm64AesimcV, + Arm64AesmcV, + Arm64AndV, + Arm64BicVi, + Arm64BicV, + Arm64BifV, + Arm64BitV, + Arm64BslV, + Arm64ClsV, + Arm64ClzV, + Arm64CmeqS, + Arm64CmeqV, + Arm64CmeqSz, + Arm64CmeqVz, + Arm64CmgeS, + Arm64CmgeV, + Arm64CmgeSz, + Arm64CmgeVz, + Arm64CmgtS, + Arm64CmgtV, + Arm64CmgtSz, + Arm64CmgtVz, + Arm64CmhiS, + Arm64CmhiV, + Arm64CmhsS, + Arm64CmhsV, + Arm64CmleSz, + Arm64CmleVz, + Arm64CmltSz, + Arm64CmltVz, + Arm64CmtstS, + Arm64CmtstV, + Arm64CntV, + Arm64DupSe, + Arm64DupVe, + Arm64DupGp, + Arm64EorV, + Arm64ExtV, + Arm64FabdS, + Arm64FabdV, + Arm64FabsV, + Arm64FabsS, + Arm64FacgeS, + Arm64FacgeV, + Arm64FacgtS, + Arm64FacgtV, + Arm64FaddpS, + Arm64FaddpV, + Arm64FaddV, + Arm64FaddS, + Arm64FccmpeS, + Arm64FccmpS, + Arm64FcmeqS, + Arm64FcmeqV, + Arm64FcmeqSz, + Arm64FcmeqVz, + Arm64FcmgeS, + Arm64FcmgeV, + Arm64FcmgeSz, + Arm64FcmgeVz, + Arm64FcmgtS, + Arm64FcmgtV, + Arm64FcmgtSz, + Arm64FcmgtVz, + Arm64FcmleSz, + Arm64FcmleVz, + Arm64FcmltSz, + Arm64FcmltVz, + Arm64FcmpeS, + Arm64FcmpS, + Arm64FcselS, + Arm64FcvtasS, + Arm64FcvtasV, + Arm64FcvtasGp, + Arm64FcvtauS, + Arm64FcvtauV, + Arm64FcvtauGp, + Arm64FcvtlV, + Arm64FcvtmsS, + Arm64FcvtmsV, + Arm64FcvtmsGp, + Arm64FcvtmuS, + Arm64FcvtmuV, + Arm64FcvtmuGp, + Arm64FcvtnsS, + Arm64FcvtnsV, + Arm64FcvtnsGp, + Arm64FcvtnuS, + Arm64FcvtnuV, + Arm64FcvtnuGp, + Arm64FcvtnV, + Arm64FcvtpsS, + Arm64FcvtpsV, + Arm64FcvtpsGp, + Arm64FcvtpuS, + Arm64FcvtpuV, + Arm64FcvtpuGp, + Arm64FcvtxnS, + Arm64FcvtxnV, + Arm64FcvtzsSFixed, + Arm64FcvtzsVFixed, + Arm64FcvtzsS, + Arm64FcvtzsV, + Arm64FcvtzsGpFixed, + Arm64FcvtzsGp, + Arm64FcvtzuSFixed, + Arm64FcvtzuVFixed, + Arm64FcvtzuS, + Arm64FcvtzuV, + Arm64FcvtzuGpFixed, + Arm64FcvtzuGp, + Arm64FcvtS, + Arm64FdivV, + Arm64FdivS, + Arm64FmaddS, + Arm64FmaxnmpS, + Arm64FmaxnmpV, + Arm64FmaxnmvV, + Arm64FmaxnmV, + Arm64FmaxnmS, + Arm64FmaxpS, + Arm64FmaxpV, + Arm64FmaxvV, + Arm64FmaxV, + Arm64FmaxS, + Arm64FminnmpS, + Arm64FminnmpV, + Arm64FminnmvV, + Arm64FminnmV, + Arm64FminnmS, + Arm64FminpS, + Arm64FminpV, + Arm64FminvV, + Arm64FminV, + Arm64FminS, + Arm64FmlaSe, + Arm64FmlaVe, + Arm64FmlaV, + Arm64FmlsSe, + Arm64FmlsVe, + Arm64FmlsV, + Arm64FmovVi, + Arm64FmovS, + Arm64FmovGp, + Arm64FmovSi, + Arm64FmsubS, + Arm64FmulxSe, + Arm64FmulxVe, + Arm64FmulxS, + Arm64FmulxV, + Arm64FmulSe, + Arm64FmulVe, + Arm64FmulV, + Arm64FmulS, + Arm64FnegV, + Arm64FnegS, + Arm64FnmaddS, + Arm64FnmsubS, + Arm64FnmulS, + Arm64FrecpeS, + Arm64FrecpeV, + Arm64FrecpsS, + Arm64FrecpsV, + Arm64FrecpxS, + Arm64FrintaV, + Arm64FrintaS, + Arm64FrintiV, + Arm64FrintiS, + Arm64FrintmV, + Arm64FrintmS, + Arm64FrintnV, + Arm64FrintnS, + Arm64FrintpV, + Arm64FrintpS, + Arm64FrintxV, + Arm64FrintxS, + Arm64FrintzV, + Arm64FrintzS, + Arm64FrsqrteS, + Arm64FrsqrteV, + Arm64FrsqrtsS, + Arm64FrsqrtsV, + Arm64FsqrtV, + Arm64FsqrtS, + Arm64FsubV, + Arm64FsubS, + Arm64InsVe, + Arm64InsGp, + Arm64Ld1rV, + Arm64Ld1Vms, + Arm64Ld1Vss, + Arm64Ld2rV, + Arm64Ld2Vms, + Arm64Ld2Vss, + Arm64Ld3rV, + Arm64Ld3Vms, + Arm64Ld3Vss, + Arm64Ld4rV, + Arm64Ld4Vms, + Arm64Ld4Vss, + Arm64MlaVe, + Arm64MlaV, + Arm64MlsVe, + Arm64MlsV, + Arm64MoviV, + Arm64MrsFpcr, + Arm64MsrFpcr, + Arm64MrsFpsr, + Arm64MsrFpsr, + Arm64MulVe, + Arm64MulV, + Arm64MvniV, + Arm64NegS, + Arm64NegV, + Arm64NotV, + Arm64OrnV, + Arm64OrrVi, + Arm64OrrV, + Arm64PmullV, + Arm64PmulV, + Arm64RaddhnV, + Arm64RbitV, + Arm64Rev16V, + Arm64Rev32V, + Arm64Rev64V, + Arm64RshrnV, + Arm64RsubhnV, + Arm64SabalV, + Arm64SabaV, + Arm64SabdlV, + Arm64SabdV, + Arm64SadalpV, + Arm64SaddlpV, + Arm64SaddlvV, + Arm64SaddlV, + Arm64SaddwV, + Arm64ScvtfSFixed, + Arm64ScvtfVFixed, + Arm64ScvtfS, + Arm64ScvtfV, + Arm64ScvtfGpFixed, + Arm64ScvtfGp, + Arm64Sha1cV, + Arm64Sha1hV, + Arm64Sha1mV, + Arm64Sha1pV, + Arm64Sha1su0V, + Arm64Sha1su1V, + Arm64Sha256h2V, + Arm64Sha256hV, + Arm64Sha256su0V, + Arm64Sha256su1V, + Arm64ShaddV, + Arm64ShllV, + Arm64ShlS, + Arm64ShlV, + Arm64ShrnV, + Arm64ShsubV, + Arm64SliS, + Arm64SliV, + Arm64SmaxpV, + Arm64SmaxvV, + Arm64SmaxV, + Arm64SminpV, + Arm64SminvV, + Arm64SminV, + Arm64SmlalVe, + Arm64SmlalV, + Arm64SmlslVe, + Arm64SmlslV, + Arm64SmovV, + Arm64SmullVe, + Arm64SmullV, + Arm64SqabsS, + Arm64SqabsV, + Arm64SqaddS, + Arm64SqaddV, + Arm64SqdmlalSe, + Arm64SqdmlalVe, + Arm64SqdmlalS, + Arm64SqdmlalV, + Arm64SqdmlslSe, + Arm64SqdmlslVe, + Arm64SqdmlslS, + Arm64SqdmlslV, + Arm64SqdmulhSe, + Arm64SqdmulhVe, + Arm64SqdmulhS, + Arm64SqdmulhV, + Arm64SqdmullSe, + Arm64SqdmullVe, + Arm64SqdmullS, + Arm64SqdmullV, + Arm64SqnegS, + Arm64SqnegV, + Arm64SqrdmulhSe, + Arm64SqrdmulhVe, + Arm64SqrdmulhS, + Arm64SqrdmulhV, + Arm64SqrshlS, + Arm64SqrshlV, + Arm64SqrshrnS, + Arm64SqrshrnV, + Arm64SqrshrunS, + Arm64SqrshrunV, + Arm64SqshluS, + Arm64SqshluV, + Arm64SqshlSi, + Arm64SqshlVi, + Arm64SqshlS, + Arm64SqshlV, + Arm64SqshrnS, + Arm64SqshrnV, + Arm64SqshrunS, + Arm64SqshrunV, + Arm64SqsubS, + Arm64SqsubV, + Arm64SqxtnS, + Arm64SqxtnV, + Arm64SqxtunS, + Arm64SqxtunV, + Arm64SrhaddV, + Arm64SriS, + Arm64SriV, + Arm64SrshlS, + Arm64SrshlV, + Arm64SrshrS, + Arm64SrshrV, + Arm64SrsraS, + Arm64SrsraV, + Arm64SshllV, + Arm64SshlS, + Arm64SshlV, + Arm64SshrS, + Arm64SshrV, + Arm64SsraS, + Arm64SsraV, + Arm64SsublV, + Arm64SsubwV, + Arm64St1Vms, + Arm64St1Vss, + Arm64St2Vms, + Arm64St2Vss, + Arm64St3Vms, + Arm64St3Vss, + Arm64St4Vms, + Arm64St4Vss, + Arm64SubhnV, + Arm64SubS, + Arm64SubV, + Arm64SuqaddS, + Arm64SuqaddV, + Arm64TblV, + Arm64TbxV, + Arm64Trn1V, + Arm64Trn2V, + Arm64UabalV, + Arm64UabaV, + Arm64UabdlV, + Arm64UabdV, + Arm64UadalpV, + Arm64UaddlpV, + Arm64UaddlvV, + Arm64UaddlV, + Arm64UaddwV, + Arm64UcvtfSFixed, + Arm64UcvtfVFixed, + Arm64UcvtfS, + Arm64UcvtfV, + Arm64UcvtfGpFixed, + Arm64UcvtfGp, + Arm64UhaddV, + Arm64UhsubV, + Arm64UmaxpV, + Arm64UmaxvV, + Arm64UmaxV, + Arm64UminpV, + Arm64UminvV, + Arm64UminV, + Arm64UmlalVe, + Arm64UmlalV, + Arm64UmlslVe, + Arm64UmlslV, + Arm64UmovV, + Arm64UmullVe, + Arm64UmullV, + Arm64UqaddS, + Arm64UqaddV, + Arm64UqrshlS, + Arm64UqrshlV, + Arm64UqrshrnS, + Arm64UqrshrnV, + Arm64UqshlSi, + Arm64UqshlVi, + Arm64UqshlS, + Arm64UqshlV, + Arm64UqshrnS, + Arm64UqshrnV, + Arm64UqsubS, + Arm64UqsubV, + Arm64UqxtnS, + Arm64UqxtnV, + Arm64UrecpeV, + Arm64UrhaddV, + Arm64UrshlS, + Arm64UrshlV, + Arm64UrshrS, + Arm64UrshrV, + Arm64UrsqrteV, + Arm64UrsraS, + Arm64UrsraV, + Arm64UshllV, + Arm64UshlS, + Arm64UshlV, + Arm64UshrS, + Arm64UshrV, + Arm64UsqaddS, + Arm64UsqaddV, + Arm64UsraS, + Arm64UsraV, + Arm64UsublV, + Arm64UsubwV, + Arm64Uzp1V, + Arm64Uzp2V, + Arm64XtnV, + Arm64Zip1V, + Arm64Zip2V, + + Arm64VTypeShift = 13, + Arm64VTypeMask = 1 << Arm64VTypeShift, + Arm64V64 = 0 << Arm64VTypeShift, + Arm64V128 = 1 << Arm64VTypeShift, + + Arm64VSizeShift = 14, + Arm64VSizeMask = 3 << Arm64VSizeShift, + Arm64VFloat = 0 << Arm64VSizeShift, + Arm64VDouble = 1 << Arm64VSizeShift, + Arm64VByte = 0 << Arm64VSizeShift, + Arm64VHWord = 1 << Arm64VSizeShift, + Arm64VWord = 2 << Arm64VSizeShift, + Arm64VDWord = 3 << Arm64VSizeShift, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs b/src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs new file mode 100644 index 000000000..8d300075d --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + /// + /// Represents a efficient linked list that stores the pointer on the object directly and does not allocate. + /// + /// Type of the list items + class IntrusiveList where T : IEquatable, IIntrusiveListNode + { + /// + /// First item of the list, or null if empty. + /// + public T First { get; private set; } + + /// + /// Last item of the list, or null if empty. + /// + public T Last { get; private set; } + + /// + /// Total number of items on the list. + /// + public int Count { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// is not pointer sized. + public IntrusiveList() + { + if (Unsafe.SizeOf() != IntPtr.Size) + { + throw new ArgumentException("T must be a reference type or a pointer sized struct."); + } + } + + /// + /// Adds a item as the first item of the list. + /// + /// Item to be added + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddFirst(T newNode) + { + if (!EqualsNull(First)) + { + return AddBefore(First, newNode); + } + else + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + Debug.Assert(EqualsNull(Last)); + + First = newNode; + Last = newNode; + + Debug.Assert(Count == 0); + + Count = 1; + + return newNode; + } + } + + /// + /// Adds a item as the last item of the list. + /// + /// Item to be added + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddLast(T newNode) + { + if (!EqualsNull(Last)) + { + return AddAfter(Last, newNode); + } + else + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + Debug.Assert(EqualsNull(First)); + + First = newNode; + Last = newNode; + + Debug.Assert(Count == 0); + + Count = 1; + + return newNode; + } + } + + /// + /// Adds a item before a existing item on the list. + /// + /// Item on the list that will succeed the new item + /// Item to be added + /// New item + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddBefore(T node, T newNode) + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + + newNode.ListPrevious = node.ListPrevious; + newNode.ListNext = node; + + node.ListPrevious = newNode; + + if (!EqualsNull(newNode.ListPrevious)) + { + newNode.ListPrevious.ListNext = newNode; + } + + if (Equals(First, node)) + { + First = newNode; + } + + Count++; + + return newNode; + } + + /// + /// Adds a item after a existing item on the list. + /// + /// Item on the list that will preceed the new item + /// Item to be added + /// New item + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddAfter(T node, T newNode) + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + + newNode.ListPrevious = node; + newNode.ListNext = node.ListNext; + + node.ListNext = newNode; + + if (!EqualsNull(newNode.ListNext)) + { + newNode.ListNext.ListPrevious = newNode; + } + + if (Equals(Last, node)) + { + Last = newNode; + } + + Count++; + + return newNode; + } + + /// + /// Removes a item from the list. + /// + /// The item to be removed + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Remove(T node) + { + if (!EqualsNull(node.ListPrevious)) + { + node.ListPrevious.ListNext = node.ListNext; + } + else + { + Debug.Assert(Equals(First, node)); + + First = node.ListNext; + } + + if (!EqualsNull(node.ListNext)) + { + node.ListNext.ListPrevious = node.ListPrevious; + } + else + { + Debug.Assert(Equals(Last, node)); + + Last = node.ListPrevious; + } + + node.ListPrevious = default; + node.ListNext = default; + + Count--; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool EqualsNull(T a) + { + return EqualityComparer.Default.Equals(a, default); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool Equals(T a, T b) + { + return EqualityComparer.Default.Equals(a, b); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs b/src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs new file mode 100644 index 000000000..9b3df8ca4 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs @@ -0,0 +1,54 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + readonly unsafe struct MemoryOperand + { + private struct Data + { +#pragma warning disable CS0649 // Field is never assigned to + public byte Kind; + public byte Type; +#pragma warning restore CS0649 + public byte Scale; + public Operand BaseAddress; + public Operand Index; + public int Displacement; + } + + private readonly Data* _data; + + public MemoryOperand(Operand operand) + { + Debug.Assert(operand.Kind == OperandKind.Memory); + + _data = (Data*)Unsafe.As(ref operand); + } + + public Operand BaseAddress + { + get => _data->BaseAddress; + set => _data->BaseAddress = value; + } + + public Operand Index + { + get => _data->Index; + set => _data->Index = value; + } + + public Multiplier Scale + { + get => (Multiplier)_data->Scale; + set => _data->Scale = (byte)value; + } + + public int Displacement + { + get => _data->Displacement; + set => _data->Displacement = value; + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Multiplier.cs b/src/ARMeilleure/IntermediateRepresentation/Multiplier.cs new file mode 100644 index 000000000..6bcdda014 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Multiplier.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Multiplier + { + x1 = 0, + x2 = 1, + x4 = 2, + x8 = 3, + x16 = 4, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Operand.cs b/src/ARMeilleure/IntermediateRepresentation/Operand.cs new file mode 100644 index 000000000..89aefacb1 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Operand.cs @@ -0,0 +1,598 @@ +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 + { + 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 readonly OperandKind Kind + { + get => (OperandKind)_data->Kind; + private set => _data->Kind = (byte)value; + } + + public readonly OperandType Type + { + get => (OperandType)_data->Type; + private set => _data->Type = (byte)value; + } + + public readonly ulong Value + { + get => _data->Value; + private set => _data->Value = value; + } + + public readonly 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 readonly ReadOnlySpan Assignments + { + get + { + Debug.Assert(Kind != OperandKind.Memory); + + return new ReadOnlySpan(_data->Assignments, _data->AssignmentsCount); + } + } + + public readonly ReadOnlySpan Uses + { + get + { + Debug.Assert(Kind != OperandKind.Memory); + + return new ReadOnlySpan(_data->Uses, (int)_data->UsesCount); + } + } + + public readonly int UsesCount => (int)_data->UsesCount; + public readonly int AssignmentsCount => _data->AssignmentsCount; + + public readonly bool Relocatable => Symbol.Type != SymbolType.None; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Register GetRegister() + { + Debug.Assert(Kind == OperandKind.Register); + + return new Register((int)Value & 0xffffff, (RegisterType)(Value >> 24)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly MemoryOperand GetMemory() + { + Debug.Assert(Kind == OperandKind.Memory); + + return new MemoryOperand(this); + } + + public readonly int GetLocalNumber() + { + Debug.Assert(Kind == OperandKind.LocalVariable); + + return (int)Value; + } + + public readonly byte AsByte() + { + return (byte)Value; + } + + public readonly short AsInt16() + { + return (short)Value; + } + + public readonly int AsInt32() + { + return (int)Value; + } + + public readonly long AsInt64() + { + return (long)Value; + } + + public readonly float AsFloat() + { + return BitConverter.Int32BitsToSingle((int)Value); + } + + public readonly double AsDouble() + { + return BitConverter.Int64BitsToDouble((long)Value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly 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 readonly 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 readonly 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 readonly 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 readonly 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); + } + } + } + + public readonly Span GetUses(ref Span buffer) + { + ReadOnlySpan uses = Uses; + + if (buffer.Length < uses.Length) + { + buffer = Allocators.Default.AllocateSpan((uint)uses.Length); + } + + uses.CopyTo(buffer); + + return buffer[..uses.Length]; + } + + private static void New(ref T* data, ref ushort count, ref ushort capacity, ushort initialCapacity) where T : unmanaged + { + count = 0; + capacity = initialCapacity; + data = Allocators.References.Allocate(initialCapacity); + } + + private static void New(ref T* data, ref uint count, ref uint capacity, uint initialCapacity) where T : unmanaged + { + count = 0; + capacity = initialCapacity; + data = Allocators.References.Allocate(initialCapacity); + } + + private static void Add(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(data, count); + + capacity = newCapacity; + data = Allocators.References.Allocate(capacity); + + oldSpan.CopyTo(new Span(data, count)); + + data[count] = item; + count = newCount; + } + } + + private static void Add(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(data, (int)count); + + capacity = newCapacity; + data = Allocators.References.Allocate(capacity); + + oldSpan.CopyTo(new Span(data, (int)count)); + + data[count] = item; + count = newCount; + } + } + + private static void Remove(in T item, ref T* data, ref ushort count) where T : unmanaged + { + var span = new Span(data, count); + + for (int i = 0; i < span.Length; i++) + { + if (EqualityComparer.Default.Equals(span[i], item)) + { + if (i + 1 < count) + { + span[(i + 1)..].CopyTo(span[i..]); + } + + count--; + + return; + } + } + } + + private static void Remove(in T item, ref T* data, ref uint count) where T : unmanaged + { + var span = new Span(data, (int)count); + + for (int i = 0; i < span.Length; i++) + { + if (EqualityComparer.Default.Equals(span[i], item)) + { + if (i + 1 < count) + { + span[(i + 1)..].CopyTo(span[i..]); + } + + count--; + + return; + } + } + } + + public readonly override int GetHashCode() + { + return ((ulong)_data).GetHashCode(); + } + + public readonly bool Equals(Operand operand) + { + return operand._data == _data; + } + + public readonly 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(_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() + { + _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 = default; + + Operand result = new() + { + _data = data, + Value = value, + Kind = kind, + 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(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; + } + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/OperandKind.cs b/src/ARMeilleure/IntermediateRepresentation/OperandKind.cs new file mode 100644 index 000000000..2b973f006 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/OperandKind.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum OperandKind + { + None, + Constant, + Label, + LocalVariable, + Memory, + Register, + Undefined, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/OperandType.cs b/src/ARMeilleure/IntermediateRepresentation/OperandType.cs new file mode 100644 index 000000000..67ebdcde4 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/OperandType.cs @@ -0,0 +1,62 @@ +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) + { + return type switch + { + OperandType.FP32 => RegisterType.Vector, + OperandType.FP64 => RegisterType.Vector, + OperandType.I32 => RegisterType.Integer, + OperandType.I64 => RegisterType.Integer, + OperandType.V128 => RegisterType.Vector, + _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."), + }; + } + + public static int GetSizeInBytes(this OperandType type) + { + return type switch + { + OperandType.FP32 => 4, + OperandType.FP64 => 8, + OperandType.I32 => 4, + OperandType.I64 => 8, + OperandType.V128 => 16, + _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."), + }; + } + + public static int GetSizeInBytesLog2(this OperandType type) + { + return type switch + { + OperandType.FP32 => 2, + OperandType.FP64 => 3, + OperandType.I32 => 2, + OperandType.I64 => 3, + OperandType.V128 => 4, + _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."), + }; + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Operation.cs b/src/ARMeilleure/IntermediateRepresentation/Operation.cs new file mode 100644 index 000000000..bc3a71b31 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Operation.cs @@ -0,0 +1,378 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + unsafe struct Operation : IEquatable, IIntrusiveListNode + { + internal struct Data + { + public ushort Instruction; + public ushort Intrinsic; + public ushort SourcesCount; + public ushort DestinationsCount; + public Operation ListPrevious; + public Operation ListNext; + public Operand* Destinations; + public Operand* Sources; + } + + private Data* _data; + + public readonly Instruction Instruction + { + get => (Instruction)_data->Instruction; + private set => _data->Instruction = (ushort)value; + } + + public readonly Intrinsic Intrinsic + { + get => (Intrinsic)_data->Intrinsic; + private set => _data->Intrinsic = (ushort)value; + } + + public readonly Operation ListPrevious + { + get => _data->ListPrevious; + set => _data->ListPrevious = value; + } + + public readonly Operation ListNext + { + get => _data->ListNext; + set => _data->ListNext = value; + } + + public readonly Operand Destination + { + get => _data->DestinationsCount != 0 ? GetDestination(0) : default; + set => SetDestination(value); + } + + public readonly int DestinationsCount => _data->DestinationsCount; + public readonly int SourcesCount => _data->SourcesCount; + + internal readonly Span DestinationsUnsafe => new(_data->Destinations, _data->DestinationsCount); + internal readonly Span SourcesUnsafe => new(_data->Sources, _data->SourcesCount); + + public readonly PhiOperation AsPhi() + { + Debug.Assert(Instruction == Instruction.Phi); + + return new PhiOperation(this); + } + + public readonly Operand GetDestination(int index) + { + return DestinationsUnsafe[index]; + } + + public readonly Operand GetSource(int index) + { + return SourcesUnsafe[index]; + } + + public readonly void SetDestination(int index, Operand dest) + { + ref Operand curDest = ref DestinationsUnsafe[index]; + + RemoveAssignment(curDest); + AddAssignment(dest); + + curDest = dest; + } + + public readonly void SetSource(int index, Operand src) + { + ref Operand curSrc = ref SourcesUnsafe[index]; + + RemoveUse(curSrc); + AddUse(src); + + curSrc = src; + } + + private readonly void RemoveOldDestinations() + { + for (int i = 0; i < _data->DestinationsCount; i++) + { + RemoveAssignment(_data->Destinations[i]); + } + } + + public readonly void SetDestination(Operand dest) + { + RemoveOldDestinations(); + + if (dest == default) + { + _data->DestinationsCount = 0; + } + else + { + EnsureCapacity(ref _data->Destinations, ref _data->DestinationsCount, 1); + + _data->Destinations[0] = dest; + + AddAssignment(dest); + } + } + + public readonly void SetDestinations(Operand[] dests) + { + RemoveOldDestinations(); + + EnsureCapacity(ref _data->Destinations, ref _data->DestinationsCount, dests.Length); + + for (int index = 0; index < dests.Length; index++) + { + Operand newOp = dests[index]; + + _data->Destinations[index] = newOp; + + AddAssignment(newOp); + } + } + + private readonly void RemoveOldSources() + { + for (int index = 0; index < _data->SourcesCount; index++) + { + RemoveUse(_data->Sources[index]); + } + } + + public readonly void SetSource(Operand src) + { + RemoveOldSources(); + + if (src == default) + { + _data->SourcesCount = 0; + } + else + { + EnsureCapacity(ref _data->Sources, ref _data->SourcesCount, 1); + + _data->Sources[0] = src; + + AddUse(src); + } + } + + public readonly void SetSources(Operand[] srcs) + { + RemoveOldSources(); + + EnsureCapacity(ref _data->Sources, ref _data->SourcesCount, srcs.Length); + + for (int index = 0; index < srcs.Length; index++) + { + Operand newOp = srcs[index]; + + _data->Sources[index] = newOp; + + AddUse(newOp); + } + } + + public void TurnIntoCopy(Operand source) + { + Instruction = Instruction.Copy; + + SetSource(source); + } + + private readonly void AddAssignment(Operand op) + { + if (op != default) + { + op.AddAssignment(this); + } + } + + private readonly void RemoveAssignment(Operand op) + { + if (op != default) + { + op.RemoveAssignment(this); + } + } + + private readonly void AddUse(Operand op) + { + if (op != default) + { + op.AddUse(this); + } + } + + private readonly void RemoveUse(Operand op) + { + if (op != default) + { + op.RemoveUse(this); + } + } + + public readonly bool Equals(Operation operation) + { + return operation._data == _data; + } + + public readonly override bool Equals(object obj) + { + return obj is Operation operation && Equals(operation); + } + + public readonly override int GetHashCode() + { + return HashCode.Combine((IntPtr)_data); + } + + public static bool operator ==(Operation a, Operation b) + { + return a.Equals(b); + } + + public static bool operator !=(Operation a, Operation b) + { + return !a.Equals(b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EnsureCapacity(ref Operand* list, ref ushort capacity, int newCapacity) + { + if (newCapacity > ushort.MaxValue) + { + ThrowOverflow(newCapacity); + } + // We only need to allocate a new buffer if we're increasing the size. + else if (newCapacity > capacity) + { + list = Allocators.References.Allocate((uint)newCapacity); + } + + capacity = (ushort)newCapacity; + } + + private static void ThrowOverflow(int count) => + throw new OverflowException($"Exceeded maximum size for Source or Destinations. Required {count}."); + + public static class Factory + { + private static Operation Make(Instruction inst, int destCount, int srcCount) + { + Data* data = Allocators.Operations.Allocate(); + *data = default; + + Operation result = new() + { + _data = data, + Instruction = inst, + }; + + EnsureCapacity(ref result._data->Destinations, ref result._data->DestinationsCount, destCount); + EnsureCapacity(ref result._data->Sources, ref result._data->SourcesCount, srcCount); + + result.DestinationsUnsafe.Clear(); + result.SourcesUnsafe.Clear(); + + return result; + } + + public static Operation Operation(Instruction inst, Operand dest) + { + Operation result = Make(inst, 0, 0); + result.SetDestination(dest); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand src0) + { + Operation result = Make(inst, 0, 1); + result.SetDestination(dest); + result.SetSource(0, src0); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand src0, Operand src1) + { + Operation result = Make(inst, 0, 2); + result.SetDestination(dest); + result.SetSource(0, src0); + result.SetSource(1, src1); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand src0, Operand src1, Operand src2) + { + Operation result = Make(inst, 0, 3); + result.SetDestination(dest); + result.SetSource(0, src0); + result.SetSource(1, src1); + result.SetSource(2, src2); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, int srcCount) + { + Operation result = Make(inst, 0, srcCount); + result.SetDestination(dest); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand[] srcs) + { + Operation result = Make(inst, 0, srcs.Length); + + result.SetDestination(dest); + + for (int index = 0; index < srcs.Length; index++) + { + result.SetSource(index, srcs[index]); + } + + return result; + } + + public static Operation Operation(Intrinsic intrin, Operand dest, params Operand[] srcs) + { + Operation result = Make(Instruction.Extended, 0, srcs.Length); + + result.Intrinsic = intrin; + result.SetDestination(dest); + + for (int index = 0; index < srcs.Length; index++) + { + result.SetSource(index, srcs[index]); + } + + return result; + } + + public static Operation Operation(Instruction inst, Operand[] dests, Operand[] srcs) + { + Operation result = Make(inst, dests.Length, srcs.Length); + + for (int index = 0; index < dests.Length; index++) + { + result.SetDestination(index, dests[index]); + } + + for (int index = 0; index < srcs.Length; index++) + { + result.SetSource(index, srcs[index]); + } + + return result; + } + + public static Operation PhiOperation(Operand dest, int srcCount) + { + return Operation(Instruction.Phi, dest, srcCount * 2); + } + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs b/src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs new file mode 100644 index 000000000..672c280fa --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs @@ -0,0 +1,37 @@ +using ARMeilleure.Translation; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.IntermediateRepresentation +{ + readonly struct PhiOperation + { + private readonly Operation _operation; + + public PhiOperation(Operation operation) + { + _operation = operation; + } + + public int SourcesCount => _operation.SourcesCount / 2; + + public BasicBlock GetBlock(ControlFlowGraph cfg, int index) + { + return cfg.PostOrderBlocks[cfg.PostOrderMap[_operation.GetSource(index * 2).AsInt32()]]; + } + + public void SetBlock(int index, BasicBlock block) + { + _operation.SetSource(index * 2, Const(block.Index)); + } + + public Operand GetSource(int index) + { + return _operation.GetSource(index * 2 + 1); + } + + public void SetSource(int index, Operand operand) + { + _operation.SetSource(index * 2 + 1, operand); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Register.cs b/src/ARMeilleure/IntermediateRepresentation/Register.cs new file mode 100644 index 000000000..208f94be1 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Register.cs @@ -0,0 +1,43 @@ +using System; + +namespace ARMeilleure.IntermediateRepresentation +{ + readonly struct Register : IEquatable + { + public int Index { get; } + + public RegisterType Type { get; } + + public Register(int index, RegisterType type) + { + Index = index; + Type = type; + } + + public override int GetHashCode() + { + return (ushort)Index | ((int)Type << 16); + } + + public static bool operator ==(Register x, Register y) + { + return x.Equals(y); + } + + public static bool operator !=(Register x, Register y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is Register reg && Equals(reg); + } + + public bool Equals(Register other) + { + return other.Index == Index && + other.Type == Type; + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/RegisterType.cs b/src/ARMeilleure/IntermediateRepresentation/RegisterType.cs new file mode 100644 index 000000000..2b4c9068c --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/RegisterType.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum RegisterType + { + Integer, + Vector, + Flag, + FpFlag, + } +} diff --git a/src/ARMeilleure/Memory/IJitMemoryAllocator.cs b/src/ARMeilleure/Memory/IJitMemoryAllocator.cs new file mode 100644 index 000000000..171bfd2f1 --- /dev/null +++ b/src/ARMeilleure/Memory/IJitMemoryAllocator.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Memory +{ + public interface IJitMemoryAllocator + { + IJitMemoryBlock Allocate(ulong size); + IJitMemoryBlock Reserve(ulong size); + + ulong GetPageSize(); + } +} diff --git a/src/ARMeilleure/Memory/IJitMemoryBlock.cs b/src/ARMeilleure/Memory/IJitMemoryBlock.cs new file mode 100644 index 000000000..cd49f314a --- /dev/null +++ b/src/ARMeilleure/Memory/IJitMemoryBlock.cs @@ -0,0 +1,14 @@ +using System; + +namespace ARMeilleure.Memory +{ + public interface IJitMemoryBlock : IDisposable + { + IntPtr Pointer { get; } + + void Commit(ulong offset, ulong size); + + void MapAsRx(ulong offset, ulong size); + void MapAsRwx(ulong offset, ulong size); + } +} diff --git a/src/ARMeilleure/Memory/IMemoryManager.cs b/src/ARMeilleure/Memory/IMemoryManager.cs new file mode 100644 index 000000000..952cd2b4f --- /dev/null +++ b/src/ARMeilleure/Memory/IMemoryManager.cs @@ -0,0 +1,77 @@ +using System; + +namespace ARMeilleure.Memory +{ + public interface IMemoryManager + { + int AddressSpaceBits { get; } + + IntPtr PageTablePointer { get; } + + MemoryManagerType Type { get; } + + event Action UnmapEvent; + + /// + /// Reads data from CPU mapped memory. + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T Read(ulong va) where T : unmanaged; + + /// + /// Reads data from CPU mapped memory, with read tracking + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T ReadTracked(ulong va) where T : unmanaged; + + /// + /// Writes data to CPU mapped memory. + /// + /// Type of the data being written + /// Virtual address to write the data into + /// Data to be written + void Write(ulong va, T value) where T : unmanaged; + + /// + /// Gets a read-only span of data from CPU mapped memory. + /// + /// Virtual address of the data + /// Size of the data + /// True if read tracking is triggered on the span + /// A read-only span of the data + ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false); + + /// + /// Gets a reference for the given type at the specified virtual memory address. + /// + /// + /// The data must be located at a contiguous memory region. + /// + /// Type of the data to get the reference + /// Virtual address of the data + /// A reference to the data in memory + ref T GetRef(ulong va) where T : unmanaged; + + /// + /// Checks if the page at a given CPU virtual address is mapped. + /// + /// Virtual address to check + /// True if the address is mapped, false otherwise + bool IsMapped(ulong va); + + /// + /// Alerts the memory tracking that a given region has been read from or written to. + /// This should be called before read/write is performed. + /// + /// Virtual address of the region + /// Size of the region + /// True if the region was written, false if read + /// True if the access is precise, false otherwise + /// Optional ID of the handles that should not be signalled + void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null); + } +} diff --git a/src/ARMeilleure/Memory/InvalidAccessException.cs b/src/ARMeilleure/Memory/InvalidAccessException.cs new file mode 100644 index 000000000..ad5407195 --- /dev/null +++ b/src/ARMeilleure/Memory/InvalidAccessException.cs @@ -0,0 +1,23 @@ +using System; + +namespace ARMeilleure.Memory +{ + class InvalidAccessException : Exception + { + public InvalidAccessException() + { + } + + public InvalidAccessException(ulong address) : base($"Invalid memory access at virtual address 0x{address:X16}.") + { + } + + public InvalidAccessException(string message) : base(message) + { + } + + public InvalidAccessException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/ARMeilleure/Memory/MemoryManagerType.cs b/src/ARMeilleure/Memory/MemoryManagerType.cs new file mode 100644 index 000000000..1e656ba27 --- /dev/null +++ b/src/ARMeilleure/Memory/MemoryManagerType.cs @@ -0,0 +1,41 @@ +namespace ARMeilleure.Memory +{ + /// + /// Indicates the type of a memory manager and the method it uses for memory mapping + /// and address translation. This controls the code generated for memory accesses on the JIT. + /// + public enum MemoryManagerType + { + /// + /// Complete software MMU implementation, the read/write methods are always called, + /// without any attempt to perform faster memory access. + /// + SoftwareMmu, + + /// + /// High level implementation using a software flat page table for address translation, + /// used to speed up address translation if possible without calling the read/write methods. + /// + SoftwarePageTable, + + /// + /// High level implementation with mappings managed by the host OS, effectively using hardware + /// page tables. No address translation is performed in software and the memory is just accessed directly. + /// + HostMapped, + + /// + /// Same as the host mapped memory manager type, but without masking the address within the address space. + /// Allows invalid access from JIT code to the rest of the program, but is faster. + /// + HostMappedUnsafe, + } + + static class MemoryManagerTypeExtensions + { + public static bool IsHostMapped(this MemoryManagerType type) + { + return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe; + } + } +} diff --git a/src/ARMeilleure/Memory/ReservedRegion.cs b/src/ARMeilleure/Memory/ReservedRegion.cs new file mode 100644 index 000000000..d0ffa8f1b --- /dev/null +++ b/src/ARMeilleure/Memory/ReservedRegion.cs @@ -0,0 +1,58 @@ +using System; + +namespace ARMeilleure.Memory +{ + class ReservedRegion + { + public 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(); + } + } +} diff --git a/src/ARMeilleure/Native/JitSupportDarwin.cs b/src/ARMeilleure/Native/JitSupportDarwin.cs new file mode 100644 index 000000000..98eee7e18 --- /dev/null +++ b/src/ARMeilleure/Native/JitSupportDarwin.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace ARMeilleure.Native +{ + [SupportedOSPlatform("macos")] + [SupportedOSPlatform("ios")] + internal static partial class JitSupportDarwin + { + [LibraryImport("libarmeilleure-jitsupport", EntryPoint = "armeilleure_jit_memcpy")] + public static partial void Copy(IntPtr dst, IntPtr src, ulong n); + } +} diff --git a/src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib b/src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib new file mode 100644 index 000000000..c65b0a4ef Binary files /dev/null and b/src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib differ diff --git a/src/ARMeilleure/Native/macos_jit_support/Makefile b/src/ARMeilleure/Native/macos_jit_support/Makefile new file mode 100644 index 000000000..d6da35d52 --- /dev/null +++ b/src/ARMeilleure/Native/macos_jit_support/Makefile @@ -0,0 +1,8 @@ +NAME = libarmeilleure-jitsupport.dylib + +all: ${NAME} + +${NAME}: + clang -O3 -dynamiclib support.c -o ${NAME} +clean: + rm -f ${NAME} diff --git a/src/ARMeilleure/Native/macos_jit_support/support.c b/src/ARMeilleure/Native/macos_jit_support/support.c new file mode 100644 index 000000000..1b13d9066 --- /dev/null +++ b/src/ARMeilleure/Native/macos_jit_support/support.c @@ -0,0 +1,14 @@ +#include +#include +#include + +#include + +void armeilleure_jit_memcpy(void *dst, const void *src, size_t n) { + pthread_jit_write_protect_np(0); + memcpy(dst, src, n); + pthread_jit_write_protect_np(1); + + // Ensure that the instruction cache for this range is invalidated. + sys_icache_invalidate(dst, n); +} diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs new file mode 100644 index 000000000..8fe478e47 --- /dev/null +++ b/src/ARMeilleure/Optimizations.cs @@ -0,0 +1,70 @@ +namespace ARMeilleure +{ + using Arm64HardwareCapabilities = ARMeilleure.CodeGen.Arm64.HardwareCapabilities; + using X86HardwareCapabilities = ARMeilleure.CodeGen.X86.HardwareCapabilities; + + 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 UseAdvSimdIfAvailable { get; set; } = true; + public static bool UseArm64AesIfAvailable { get; set; } = true; + public static bool UseArm64PmullIfAvailable { 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 UseAvx512FIfAvailable { get; set; } = true; + public static bool UseAvx512VlIfAvailable { get; set; } = true; + public static bool UseAvx512BwIfAvailable { get; set; } = true; + public static bool UseAvx512DqIfAvailable { 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 => X86HardwareCapabilities.ForceLegacySse; + set => X86HardwareCapabilities.ForceLegacySse = value; + } + +#pragma warning disable IDE0055 // Disable formatting + internal static bool UseAdvSimd => UseAdvSimdIfAvailable && Arm64HardwareCapabilities.SupportsAdvSimd; + internal static bool UseArm64Aes => UseArm64AesIfAvailable && Arm64HardwareCapabilities.SupportsAes; + internal static bool UseArm64Pmull => UseArm64PmullIfAvailable && Arm64HardwareCapabilities.SupportsPmull; + + internal static bool UseSse => UseSseIfAvailable && X86HardwareCapabilities.SupportsSse; + internal static bool UseSse2 => UseSse2IfAvailable && X86HardwareCapabilities.SupportsSse2; + internal static bool UseSse3 => UseSse3IfAvailable && X86HardwareCapabilities.SupportsSse3; + internal static bool UseSsse3 => UseSsse3IfAvailable && X86HardwareCapabilities.SupportsSsse3; + internal static bool UseSse41 => UseSse41IfAvailable && X86HardwareCapabilities.SupportsSse41; + internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42; + internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt; + internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse; + internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse; + internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse; + internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse; + internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse; + internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c; + internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma; + internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni; + internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq; + internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha; + internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni; +#pragma warning restore IDE0055 + + internal static bool UseAvx512Ortho => UseAvx512F && UseAvx512Vl; + internal static bool UseAvx512OrthoFloat => UseAvx512Ortho && UseAvx512Dq; + } +} diff --git a/src/ARMeilleure/Signal/NativeSignalHandler.cs b/src/ARMeilleure/Signal/NativeSignalHandler.cs new file mode 100644 index 000000000..a32830268 --- /dev/null +++ b/src/ARMeilleure/Signal/NativeSignalHandler.cs @@ -0,0 +1,449 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.Translation; +using ARMeilleure.Translation.Cache; +using Ryujinx.Common; +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 + { + /// + /// The byte offset of the faulting address in the SigInfo or ExceptionRecord struct. + /// + public int StructAddressOffset; + + /// + /// The byte offset of the write flag in the SigInfo or ExceptionRecord struct. + /// + public int StructWriteOffset; + + /// + /// The sigaction handler that was registered before this one. (unix only) + /// + public nuint UnixOldSigaction; + + /// + /// The type of the previous sigaction. True for the 3 argument variant. (unix only) + /// + 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 static ulong _pageSize; + private static ulong _pageMask; + + private static readonly IntPtr _handlerConfig; + private static IntPtr _signalHandlerPtr; + private static IntPtr _signalHandlerHandle; + + private static readonly object _lock = new(); + private static bool _initialized; + + static NativeSignalHandler() + { + _handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf()); + ref SignalHandlerConfig config = ref GetConfigRef(); + + config = new SignalHandlerConfig(); + } + + public static void Initialize(IJitMemoryAllocator allocator) + { + JitCache.Initialize(allocator); + } + + public static void InitializeSignalHandler(ulong pageSize, Func customSignalHandlerFactory = null) + { + if (_initialized) + { + return; + } + + lock (_lock) + { + if (_initialized) + { + return; + } + + _pageSize = pageSize; + _pageMask = pageSize - 1; + + ref SignalHandlerConfig config = ref GetConfigRef(); + + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + if (PlatformInfo.IsBionic) + { + config.StructAddressOffset = 16; // si_addr + config.StructWriteOffset = 8; // si_code + } + + _signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig)); + + if (customSignalHandlerFactory != null) + { + _signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr); + } + + var 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)); + + if (customSignalHandlerFactory != null) + { + _signalHandlerPtr = customSignalHandlerFactory(IntPtr.Zero, _signalHandlerPtr); + } + + _signalHandlerHandle = WindowsSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr); + } + + _initialized = true; + } + } + + public static void InstallUnixAlternateStackForCurrentThread(IntPtr stackPtr, ulong stackSize) + { + UnixSignalHandlerRegistration.RegisterAlternateStack(stackPtr, stackSize); + } + + public static void UninstallUnixAlternateStackForCurrentThread() + { + UnixSignalHandlerRegistration.UnregisterAlternateStack(); + } + + public static void InstallUnixSignalHandler(int sigNum, IntPtr action) + { + UnixSignalHandlerRegistration.RegisterExceptionHandler(sigNum, action); + } + + private static unsafe ref SignalHandlerConfig GetConfigRef() + { + return ref Unsafe.AsRef((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()); + + 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); + 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 Operand GenerateUnixFaultAddress(EmitterContext context, Operand sigInfoPtr) + { + ulong structAddressOffset = (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) ? 24ul : 16ul; // si_addr + return context.Load(OperandType.I64, context.Add(sigInfoPtr, Const(structAddressOffset))); + } + + private static Operand GenerateUnixWriteFlag(EmitterContext context, Operand ucontextPtr) + { + if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + const ulong McontextOffset = 48; // uc_mcontext + Operand ctxPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(McontextOffset))); + + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + const ulong EsrOffset = 8; // __es.__esr + Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(EsrOffset))); + return context.BitwiseAnd(esr, Const(0x40ul)); + } + + if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + const ulong ErrOffset = 4; // __es.__err + Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(ErrOffset))); + return context.BitwiseAnd(err, Const(2ul)); + } + } + else if (OperatingSystem.IsLinux()) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + Operand auxPtr = context.AllocateLocal(OperandType.I64); + + Operand loopLabel = Label(); + Operand successLabel = Label(); + + const ulong AuxOffset = 464; // uc_mcontext.__reserved + const uint EsrMagic = 0x45535201; + + context.Copy(auxPtr, context.Add(ucontextPtr, Const(AuxOffset))); + + context.MarkLabel(loopLabel); + + // _aarch64_ctx::magic + Operand magic = context.Load(OperandType.I32, auxPtr); + // _aarch64_ctx::size + Operand size = context.Load(OperandType.I32, context.Add(auxPtr, Const(4ul))); + + context.BranchIf(successLabel, magic, Const(EsrMagic), Comparison.Equal); + + context.Copy(auxPtr, context.Add(auxPtr, context.ZeroExtend32(OperandType.I64, size))); + + context.Branch(loopLabel); + + context.MarkLabel(successLabel); + + // esr_context::esr + Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul))); + return context.BitwiseAnd(esr, Const(0x40ul)); + } + + if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + const int ErrOffset = 192; // uc_mcontext.gregs[REG_ERR] + Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(ErrOffset))); + return context.BitwiseAnd(err, Const(2ul)); + } + } + + throw new PlatformNotSupportedException(); + } + + private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr) + { + EmitterContext context = new(); + + // (int sig, SigInfo* sigInfo, void* ucontext) + Operand sigInfoPtr = context.LoadArgument(OperandType.I64, 1); + Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2); + + Operand faultAddress = GenerateUnixFaultAddress(context, sigInfoPtr); + Operand writeFlag = GenerateUnixWriteFlag(context, ucontextPtr); + + 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, RuntimeInformation.ProcessArchitecture).Map(); + } + + private static VectoredExceptionHandler GenerateWindowsSignalHandler(IntPtr signalStructPtr) + { + EmitterContext context = new(); + + // (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, RuntimeInformation.ProcessArchitecture).Map(); + } + } +} diff --git a/src/ARMeilleure/Signal/TestMethods.cs b/src/ARMeilleure/Signal/TestMethods.cs new file mode 100644 index 000000000..0a8b3f5ff --- /dev/null +++ b/src/ARMeilleure/Signal/TestMethods.cs @@ -0,0 +1,84 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Runtime.InteropServices; +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(); + + 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, RuntimeInformation.ProcessArchitecture).Map(); + } + + public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(IntPtr structPtr) + { + EmitterContext context = new(); + + 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, RuntimeInformation.ProcessArchitecture).Map(); + } + + public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop() + { + EmitterContext context = new(); + + // 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, RuntimeInformation.ProcessArchitecture).Map(); + } + } +} diff --git a/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs b/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs new file mode 100644 index 000000000..849f7a860 --- /dev/null +++ b/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs @@ -0,0 +1,261 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace ARMeilleure.Signal +{ + static partial class UnixSignalHandlerRegistration + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct SigSet + { + fixed long sa_mask[16]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SigAction + { + public IntPtr sa_handler; + public SigSet sa_mask; + public int sa_flags; + public IntPtr sa_restorer; + } + + [SupportedOSPlatform("android"), StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct SigActionBionic + { + public int sa_flags; + public IntPtr sa_handler; + public SigSet sa_mask; + public IntPtr sa_restorer; + } + + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct Stack + { + public IntPtr ss_sp; + public int ss_flags; + public IntPtr ss_size; + } + + private const int SIGSEGV = 11; + private const int SIGBUS = 10; + private const int SA_SIGINFO = 0x00000004; + private const int SA_ONSTACK = 0x08000000; + private const int SS_DISABLE = 2; + private const int SS_AUTODISARM = 1 << 31; + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigaction(int signum, ref SigAction sigAction, out SigAction oldAction); + + [SupportedOSPlatform("android"), LibraryImport("libc", SetLastError = true)] + private static partial int sigaction(int signum, ref SigActionBionic sigAction, out SigActionBionic oldAction); + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigaction(int signum, IntPtr sigAction, out SigAction oldAction); + + [SupportedOSPlatform("android"), LibraryImport("libc", SetLastError = true)] + private static partial int sigaction(int signum, IntPtr sigAction, out SigActionBionic oldAction); + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigemptyset(ref SigSet set); + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigaltstack(ref Stack ss, out Stack oldSs); + + public static SigAction GetSegfaultExceptionHandler() + { + int result; + SigAction old; + + if (Ryujinx.Common.PlatformInfo.IsBionic) + { + result = sigaction(SIGSEGV, IntPtr.Zero, out SigActionBionic tmp); + + old = new SigAction + { + sa_handler = tmp.sa_handler, + sa_mask = tmp.sa_mask, + sa_flags = tmp.sa_flags, + sa_restorer = tmp.sa_restorer + }; + } + else + { + result = sigaction(SIGSEGV, IntPtr.Zero, out old); + } + + if (result != 0) + { + throw new SystemException($"Could not get SIGSEGV sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}"); + } + + return old; + } + + public static SigAction RegisterExceptionHandler(IntPtr action) + { + int result; + SigAction old; + + if (Ryujinx.Common.PlatformInfo.IsBionic) + { + SigActionBionic sig = new() + { + sa_handler = action, + sa_flags = SA_SIGINFO | SA_ONSTACK, + }; + + sigemptyset(ref sig.sa_mask); + + result = sigaction(SIGSEGV, ref sig, out SigActionBionic tmp); + + old = new SigAction + { + sa_handler = tmp.sa_handler, + sa_mask = tmp.sa_mask, + sa_flags = tmp.sa_flags, + sa_restorer = tmp.sa_restorer + }; + } + else + { + SigAction sig = new SigAction + { + sa_handler = action, + sa_flags = SA_SIGINFO | SA_ONSTACK, + }; + + sigemptyset(ref sig.sa_mask); + + result = sigaction(SIGSEGV, ref sig, out old); + + if (result != 0) + { + throw new SystemException($"Could not register SIGSEGV sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}"); + } + + if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + result = sigaction(SIGBUS, ref sig, out _); + + if (result != 0) + { + throw new SystemException($"Could not register SIGBUS sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}"); + } + } + } + + return old; + } + + public static void RegisterAlternateStack(IntPtr stackPtr, ulong stackSize) + { + Stack stack = new() + { + ss_sp = stackPtr, + ss_flags = SS_AUTODISARM, + ss_size = (IntPtr)stackSize + }; + + int result = sigaltstack(ref stack, out _); + + if (result != 0) + { + throw new SystemException($"Could not set alternate stack. Error: {Marshal.GetLastPInvokeErrorMessage()}"); + } + } + + public static void UnregisterAlternateStack() + { + Stack stack = new() + { + ss_flags = SS_DISABLE + }; + + int result = sigaltstack(ref stack, out _); + + if (result != 0) + { + throw new SystemException($"Could not remove alternate stack. Error: {Marshal.GetLastPInvokeErrorMessage()}"); + } + } + + public static void RegisterExceptionHandler(int sigNum, IntPtr action) + { + int result; + + if (Ryujinx.Common.PlatformInfo.IsBionic) + { + SigActionBionic sig = new() + { + sa_handler = action, + sa_flags = SA_SIGINFO | SA_ONSTACK + }; + + sigemptyset(ref sig.sa_mask); + + result = sigaction(sigNum, ref sig, out SigActionBionic oldu); + + if (oldu.sa_handler != IntPtr.Zero) + { + throw new InvalidOperationException($"SIG{sigNum} is already in use."); + } + + if (result != 0) + { + throw new SystemException($"Could not register SIG{sigNum} sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}"); + } + } + else + { + SigAction sig = new() + { + sa_handler = action, + sa_flags = SA_SIGINFO | SA_ONSTACK, + }; + + sigemptyset(ref sig.sa_mask); + + result = sigaction(sigNum, ref sig, out SigAction oldu); + + if (oldu.sa_handler != IntPtr.Zero) + { + throw new InvalidOperationException($"SIG{sigNum} is already in use."); + } + + if (result != 0) + { + throw new SystemException($"Could not register SIG{sigNum} sigaction. Error: {Marshal.GetLastPInvokeErrorMessage()}"); + } + } + } + + public static bool RestoreExceptionHandler(SigAction oldAction) + { + if (Ryujinx.Common.PlatformInfo.IsBionic) + { + SigActionBionic tmp = new SigActionBionic + { + sa_handler = oldAction.sa_handler, + sa_mask = oldAction.sa_mask, + sa_flags = oldAction.sa_flags, + sa_restorer = oldAction.sa_restorer + }; + + return sigaction(SIGSEGV, ref tmp, out SigActionBionic _) == 0; + } + else + { + bool success = sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0; + + if (success && (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())) + { + success = sigaction(SIGBUS, ref oldAction, out SigAction _) == 0; + } + + return success; + } + } + } +} diff --git a/src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs b/src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs new file mode 100644 index 000000000..27a9ea83c --- /dev/null +++ b/src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs @@ -0,0 +1,188 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using Ryujinx.Common.Memory.PartialUnmaps; +using System; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Signal +{ + /// + /// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods. + /// + internal static class WindowsPartialUnmapHandler + { + public static Operand EmitRetryFromAccessViolation(EmitterContext context) + { + IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState; + IntPtr localCountsPtr = IntPtr.Add(partialRemapStatePtr, PartialUnmapState.LocalCountsOffset); + + // Get the lock first. + EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset)); + + IntPtr getCurrentThreadId = WindowsSignalHandlerRegistration.GetCurrentThreadIdFunc(); + Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32); + Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0)); + + Operand endLabel = Label(); + Operand retry = context.AllocateLocal(OperandType.I32); + Operand threadIndexValidLabel = Label(); + + context.BranchIfFalse(threadIndexValidLabel, context.ICompareEqual(threadIndex, Const(-1))); + + context.Copy(retry, Const(1)); // Always retry when thread local cannot be allocated. + + context.Branch(endLabel); + + context.MarkLabel(threadIndexValidLabel); + + Operand threadLocalPartialUnmapsPtr = EmitThreadLocalMapIntGetValuePtr(context, localCountsPtr, threadIndex); + Operand threadLocalPartialUnmaps = context.Load(OperandType.I32, threadLocalPartialUnmapsPtr); + Operand partialUnmapsCount = context.Load(OperandType.I32, Const((ulong)IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapsCountOffset))); + + context.Copy(retry, context.ICompareNotEqual(threadLocalPartialUnmaps, partialUnmapsCount)); + + Operand noRetryLabel = Label(); + + context.BranchIfFalse(noRetryLabel, retry); + + // if (retry) { + + context.Store(threadLocalPartialUnmapsPtr, partialUnmapsCount); + + context.Branch(endLabel); + + context.MarkLabel(noRetryLabel); + + // } + + context.MarkLabel(endLabel); + + // Finally, release the lock and return the retry value. + EmitNativeReaderLockRelease(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset)); + + return retry; + } + + public static Operand EmitThreadLocalMapIntGetOrReserve(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand initialState) + { + Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.ThreadIdsOffset)); + + Operand i = context.AllocateLocal(OperandType.I32); + + context.Copy(i, Const(0)); + + // (Loop 1) Check all slots for a matching Thread ID (while also trying to allocate) + + Operand endLabel = Label(); + + Operand loopLabel = Label(); + context.MarkLabel(loopLabel); + + Operand offset = context.Multiply(i, Const(sizeof(int))); + Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset)); + + // Check that this slot has the thread ID. + Operand existingId = context.CompareAndSwap(idPtr, threadId, threadId); + + // If it was already the thread ID, then we just need to return i. + context.BranchIfTrue(endLabel, context.ICompareEqual(existingId, threadId)); + + context.Copy(i, context.Add(i, Const(1))); + + context.BranchIfTrue(loopLabel, context.ICompareLess(i, Const(ThreadLocalMap.MapSize))); + + // (Loop 2) Try take a slot that is 0 with our Thread ID. + + context.Copy(i, Const(0)); // Reset i. + + Operand loop2Label = Label(); + context.MarkLabel(loop2Label); + + Operand offset2 = context.Multiply(i, Const(sizeof(int))); + Operand idPtr2 = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset2)); + + // Try and swap in the thread id on top of 0. + Operand existingId2 = context.CompareAndSwap(idPtr2, Const(0), threadId); + + Operand idNot0Label = Label(); + + // If it was 0, then we need to initialize the struct entry and return i. + context.BranchIfFalse(idNot0Label, context.ICompareEqual(existingId2, Const(0))); + + Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.StructsOffset)); + Operand structPtr = context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset2)); + context.Store(structPtr, initialState); + + context.Branch(endLabel); + + context.MarkLabel(idNot0Label); + + context.Copy(i, context.Add(i, Const(1))); + + context.BranchIfTrue(loop2Label, context.ICompareLess(i, Const(ThreadLocalMap.MapSize))); + + context.Copy(i, Const(-1)); // Could not place the thread in the list. + + context.MarkLabel(endLabel); + + return context.Copy(i); + } + + private static Operand EmitThreadLocalMapIntGetValuePtr(EmitterContext context, IntPtr threadLocalMapPtr, Operand index) + { + Operand offset = context.Multiply(index, Const(sizeof(int))); + Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.StructsOffset)); + + return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset)); + } + +#pragma warning disable IDE0051 // Remove unused private member + private static void EmitThreadLocalMapIntRelease(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand index) + { + Operand offset = context.Multiply(index, Const(sizeof(int))); + Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.ThreadIdsOffset)); + Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset)); + + context.CompareAndSwap(idPtr, threadId, Const(0)); + } +#pragma warning restore IDE0051 + + private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive) + { + Operand loop = Label(); + context.MarkLabel(loop); + + Operand initial = context.Load(OperandType.I32, ptr); + Operand newValue = context.Add(initial, additive); + + Operand replaced = context.CompareAndSwap(ptr, initial, newValue); + + context.BranchIfFalse(loop, context.ICompareEqual(initial, replaced)); + } + + private static void EmitNativeReaderLockAcquire(EmitterContext context, IntPtr nativeReaderLockPtr) + { + Operand writeLockPtr = Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.WriteLockOffset)); + + // Spin until we can acquire the write lock. + Operand spinLabel = Label(); + context.MarkLabel(spinLabel); + + // Old value must be 0 to continue (we gained the write lock) + context.BranchIfTrue(spinLabel, context.CompareAndSwap(writeLockPtr, Const(0), Const(1))); + + // Increment reader count. + EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(1)); + + // Release write lock. + context.CompareAndSwap(writeLockPtr, Const(1), Const(0)); + } + + private static void EmitNativeReaderLockRelease(EmitterContext context, IntPtr nativeReaderLockPtr) + { + // Decrement reader count. + EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(-1)); + } + } +} diff --git a/src/ARMeilleure/Signal/WindowsSignalHandlerRegistration.cs b/src/ARMeilleure/Signal/WindowsSignalHandlerRegistration.cs new file mode 100644 index 000000000..5444da0ca --- /dev/null +++ b/src/ARMeilleure/Signal/WindowsSignalHandlerRegistration.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Signal +{ + unsafe partial class WindowsSignalHandlerRegistration + { + [LibraryImport("kernel32.dll")] + private static partial IntPtr AddVectoredExceptionHandler(uint first, IntPtr handler); + + [LibraryImport("kernel32.dll")] + private static partial ulong RemoveVectoredExceptionHandler(IntPtr handle); + + [LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")] + private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName); + + private static IntPtr _getCurrentThreadIdPtr; + + public static IntPtr RegisterExceptionHandler(IntPtr action) + { + return AddVectoredExceptionHandler(1, action); + } + + public static bool RemoveExceptionHandler(IntPtr handle) + { + return RemoveVectoredExceptionHandler(handle) != 0; + } + + public static IntPtr GetCurrentThreadIdFunc() + { + if (_getCurrentThreadIdPtr == IntPtr.Zero) + { + IntPtr handle = LoadLibrary("kernel32.dll"); + + _getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId"); + } + + return _getCurrentThreadIdPtr; + } + } +} diff --git a/src/ARMeilleure/State/Aarch32Mode.cs b/src/ARMeilleure/State/Aarch32Mode.cs new file mode 100644 index 000000000..add1cd26f --- /dev/null +++ b/src/ARMeilleure/State/Aarch32Mode.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.State +{ + enum Aarch32Mode + { + User = 0b10000, + Fiq = 0b10001, + Irq = 0b10010, + Supervisor = 0b10011, + Monitor = 0b10110, + Abort = 0b10111, + Hypervisor = 0b11010, + Undefined = 0b11011, + System = 0b11111, + } +} diff --git a/src/ARMeilleure/State/ExceptionCallback.cs b/src/ARMeilleure/State/ExceptionCallback.cs new file mode 100644 index 000000000..2a4e9656a --- /dev/null +++ b/src/ARMeilleure/State/ExceptionCallback.cs @@ -0,0 +1,5 @@ +namespace ARMeilleure.State +{ + public delegate void ExceptionCallbackNoArgs(ExecutionContext context); + public delegate void ExceptionCallback(ExecutionContext context, ulong address, int id); +} diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs new file mode 100644 index 000000000..ce10a591c --- /dev/null +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -0,0 +1,175 @@ +using ARMeilleure.Memory; +using System; + +namespace ARMeilleure.State +{ + public class ExecutionContext + { + private const int MinCountForCheck = 4000; + + private readonly NativeContext _nativeContext; + + internal IntPtr NativeContextPtr => _nativeContext.BasePtr; + + private bool _interrupted; + + private readonly ICounter _counter; + + public ulong Pc => _nativeContext.GetPc(); + +#pragma warning disable CA1822 // Mark member as static + public uint CtrEl0 => 0x8444c004; + public uint DczidEl0 => 0x00000004; +#pragma warning restore CA1822 + + 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 => _nativeContext.GetTpidrEl0(); + set => _nativeContext.SetTpidrEl0(value); + } + + public long TpidrroEl0 + { + get => _nativeContext.GetTpidrroEl0(); + set => _nativeContext.SetTpidrroEl0(value); + } + + 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(); + } + } +} diff --git a/src/ARMeilleure/State/ExecutionMode.cs b/src/ARMeilleure/State/ExecutionMode.cs new file mode 100644 index 000000000..e1fb722bd --- /dev/null +++ b/src/ARMeilleure/State/ExecutionMode.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.State +{ + enum ExecutionMode + { + Aarch32Arm = 0, + Aarch32Thumb = 1, + Aarch64 = 2, + } +} diff --git a/src/ARMeilleure/State/FPCR.cs b/src/ARMeilleure/State/FPCR.cs new file mode 100644 index 000000000..427300add --- /dev/null +++ b/src/ARMeilleure/State/FPCR.cs @@ -0,0 +1,22 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPCR : uint + { + Ioe = 1u << 8, + Dze = 1u << 9, + Ofe = 1u << 10, + Ufe = 1u << 11, + Ixe = 1u << 12, + Ide = 1u << 15, + RMode0 = 1u << 22, + RMode1 = 1u << 23, + Fz = 1u << 24, + Dn = 1u << 25, + Ahp = 1u << 26, + + Mask = Ahp | Dn | Fz | RMode1 | RMode0 | Ide | Ixe | Ufe | Ofe | Dze | Ioe, // 0x07C09F00u + } +} diff --git a/src/ARMeilleure/State/FPException.cs b/src/ARMeilleure/State/FPException.cs new file mode 100644 index 000000000..5b13659ab --- /dev/null +++ b/src/ARMeilleure/State/FPException.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.State +{ + enum FPException + { + InvalidOp = 0, + DivideByZero = 1, + Overflow = 2, + Underflow = 3, + Inexact = 4, + InputDenorm = 7, + } +} diff --git a/src/ARMeilleure/State/FPRoundingMode.cs b/src/ARMeilleure/State/FPRoundingMode.cs new file mode 100644 index 000000000..0913175e7 --- /dev/null +++ b/src/ARMeilleure/State/FPRoundingMode.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.State +{ + public enum FPRoundingMode + { + ToNearest = 0, // With ties to even. + TowardsPlusInfinity = 1, + TowardsMinusInfinity = 2, + TowardsZero = 3, + ToNearestAway = 4, // With ties to away. + } +} diff --git a/src/ARMeilleure/State/FPSCR.cs b/src/ARMeilleure/State/FPSCR.cs new file mode 100644 index 000000000..65a060ebd --- /dev/null +++ b/src/ARMeilleure/State/FPSCR.cs @@ -0,0 +1,15 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPSCR : uint + { + V = 1u << 28, + C = 1u << 29, + Z = 1u << 30, + N = 1u << 31, + + Mask = N | Z | C | V | FPSR.Mask | FPCR.Mask, // 0xFFC09F9Fu + } +} diff --git a/src/ARMeilleure/State/FPSR.cs b/src/ARMeilleure/State/FPSR.cs new file mode 100644 index 000000000..915b2fb31 --- /dev/null +++ b/src/ARMeilleure/State/FPSR.cs @@ -0,0 +1,18 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPSR : uint + { + Ioc = 1u << 0, + Dzc = 1u << 1, + Ofc = 1u << 2, + Ufc = 1u << 3, + Ixc = 1u << 4, + Idc = 1u << 7, + Qc = 1u << 27, + + Mask = Qc | Idc | Ixc | Ufc | Ofc | Dzc | Ioc, // 0x0800009Fu + } +} diff --git a/src/ARMeilleure/State/FPState.cs b/src/ARMeilleure/State/FPState.cs new file mode 100644 index 000000000..e76f48240 --- /dev/null +++ b/src/ARMeilleure/State/FPState.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.State +{ + public enum FPState + { + // FPSR Flags. + IocFlag = 0, + DzcFlag = 1, + OfcFlag = 2, + UfcFlag = 3, + IxcFlag = 4, + IdcFlag = 7, + QcFlag = 27, + VFlag = 28, + CFlag = 29, + ZFlag = 30, + NFlag = 31, + + // FPCR Flags. + IoeFlag = 8, + DzeFlag = 9, + OfeFlag = 10, + UfeFlag = 11, + IxeFlag = 12, + IdeFlag = 15, + RMode0Flag = 22, + RMode1Flag = 23, + FzFlag = 24, + DnFlag = 25, + AhpFlag = 26, + } +} diff --git a/src/ARMeilleure/State/FPType.cs b/src/ARMeilleure/State/FPType.cs new file mode 100644 index 000000000..367082ffc --- /dev/null +++ b/src/ARMeilleure/State/FPType.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.State +{ + enum FPType + { + Nonzero, + Zero, + Infinity, + QNaN, + SNaN, + } +} diff --git a/src/ARMeilleure/State/ICounter.cs b/src/ARMeilleure/State/ICounter.cs new file mode 100644 index 000000000..7aa1cce73 --- /dev/null +++ b/src/ARMeilleure/State/ICounter.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.State +{ + /// + /// CPU Counter interface. + /// + public interface ICounter + { + /// + /// Counter frequency in Hertz. + /// + ulong Frequency { get; } + + /// + /// Current counter value. + /// + ulong Counter { get; } + } +} diff --git a/src/ARMeilleure/State/NativeContext.cs b/src/ARMeilleure/State/NativeContext.cs new file mode 100644 index 000000000..5403042ea --- /dev/null +++ b/src/ARMeilleure/State/NativeContext.cs @@ -0,0 +1,269 @@ +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 long TpidrEl0; + public long TpidrroEl0; + public int Counter; + public ulong DispatchAddress; + public ulong ExclusiveAddress; + public ulong ExclusiveValueLow; + public ulong ExclusiveValueHigh; + public int Running; + } + + private static NativeCtxStorage _dummyStorage = new(); + + private readonly IJitMemoryBlock _block; + + public IntPtr BasePtr => _block.Pointer; + + public NativeContext(IJitMemoryAllocator allocator) + { + _block = allocator.Allocate((ulong)Unsafe.SizeOf()); + + 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(0); + GetStorage().V[index * 2 + 1] = value.Extract(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 long GetTpidrEl0() => GetStorage().TpidrEl0; + public void SetTpidrEl0(long value) => GetStorage().TpidrEl0 = value; + + public long GetTpidrroEl0() => GetStorage().TpidrroEl0; + public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value; + + 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 GetTpidrEl0Offset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrEl0); + } + + public static int GetTpidrroEl0Offset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0); + } + + 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(ref NativeCtxStorage storage, ref T target) + { + return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); + } + + private unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef((void*)_block.Pointer); + + public void Dispose() => _block.Dispose(); + } +} diff --git a/src/ARMeilleure/State/PState.cs b/src/ARMeilleure/State/PState.cs new file mode 100644 index 000000000..d4ddc8656 --- /dev/null +++ b/src/ARMeilleure/State/PState.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.State +{ + public enum PState + { + TFlag = 5, + EFlag = 9, + GE0Flag = 16, + GE1Flag = 17, + GE2Flag = 18, + GE3Flag = 19, + QFlag = 27, + VFlag = 28, + CFlag = 29, + ZFlag = 30, + NFlag = 31, + } +} diff --git a/src/ARMeilleure/State/RegisterAlias.cs b/src/ARMeilleure/State/RegisterAlias.cs new file mode 100644 index 000000000..a95740891 --- /dev/null +++ b/src/ARMeilleure/State/RegisterAlias.cs @@ -0,0 +1,42 @@ +namespace ARMeilleure.State +{ + static class RegisterAlias + { + public const int R8Usr = 8; + public const int R9Usr = 9; + public const int R10Usr = 10; + public const int R11Usr = 11; + public const int R12Usr = 12; + public const int SpUsr = 13; + public const int LrUsr = 14; + + public const int SpHyp = 15; + + public const int LrIrq = 16; + public const int SpIrq = 17; + + public const int LrSvc = 18; + public const int SpSvc = 19; + + public const int LrAbt = 20; + public const int SpAbt = 21; + + public const int LrUnd = 22; + public const int SpUnd = 23; + + public const int R8Fiq = 24; + public const int R9Fiq = 25; + public const int R10Fiq = 26; + public const int R11Fiq = 27; + public const int R12Fiq = 28; + public const int SpFiq = 29; + public const int LrFiq = 30; + + public const int Aarch32Sp = 13; + public const int Aarch32Lr = 14; + public const int Aarch32Pc = 15; + + public const int Lr = 30; + public const int Zr = 31; + } +} diff --git a/src/ARMeilleure/State/RegisterConsts.cs b/src/ARMeilleure/State/RegisterConsts.cs new file mode 100644 index 000000000..b43f8d646 --- /dev/null +++ b/src/ARMeilleure/State/RegisterConsts.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.State +{ + static class RegisterConsts + { + public const int IntRegsCount = 32; + public const int VecRegsCount = 32; + public const int FlagsCount = 32; + public const int FpFlagsCount = 32; + public const int IntAndVecRegsCount = IntRegsCount + VecRegsCount; + public const int FpFlagsOffset = IntRegsCount + VecRegsCount + FlagsCount; + public const int TotalCount = IntRegsCount + VecRegsCount + FlagsCount + FpFlagsCount; + + public const int ZeroIndex = 31; + } +} diff --git a/src/ARMeilleure/State/V128.cs b/src/ARMeilleure/State/V128.cs new file mode 100644 index 000000000..cbcaddfc2 --- /dev/null +++ b/src/ARMeilleure/State/V128.cs @@ -0,0 +1,316 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ARMeilleure.State +{ + /// + /// Represents a 128-bit vector. + /// + [StructLayout(LayoutKind.Sequential, Size = 16)] + public struct V128 : IEquatable + { + // _e0 & _e1 could be marked as readonly, however they are not readonly because we modify them through the Unsafe + // APIs. This also means that one should be careful when changing the layout of this struct. + + private readonly ulong _e0; + private readonly ulong _e1; + + /// + /// Gets a new with all bits set to zero. + /// + public static V128 Zero => new(0, 0); + + /// + /// Initializes a new instance of the struct with the specified value + /// as a scalar. + /// + /// Scalar value + public V128(double value) : this(value, 0) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + public V128(double e0, double e1) + { + _e0 = (ulong)BitConverter.DoubleToInt64Bits(e0); + _e1 = (ulong)BitConverter.DoubleToInt64Bits(e1); + } + + /// + /// Initializes a new instance of the struct with the specified value as a + /// scalar. + /// + /// Scalar value + public V128(float value) : this(value, 0, 0, 0) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + /// Element 2 + /// Element 3 + public V128(float e0, float e1, float e2, float e3) + { + _e0 = (ulong)(uint)BitConverter.SingleToInt32Bits(e0) << 0; + _e0 |= (ulong)(uint)BitConverter.SingleToInt32Bits(e1) << 32; + _e1 = (ulong)(uint)BitConverter.SingleToInt32Bits(e2) << 0; + _e1 |= (ulong)(uint)BitConverter.SingleToInt32Bits(e3) << 32; + } + + /// + /// Initializes a new instance of the struct with the specified + /// elements. + /// + /// Element 0 + /// Element 1 + public V128(long e0, long e1) : this((ulong)e0, (ulong)e1) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + public V128(ulong e0, ulong e1) + { + _e0 = e0; + _e1 = e1; + } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + /// Element 2 + /// Element 3 + public V128(int e0, int e1, int e2, int e3) : this((uint)e0, (uint)e1, (uint)e2, (uint)e3) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + /// Element 2 + /// Element 3 + public V128(uint e0, uint e1, uint e2, uint e3) + { + _e0 = (ulong)e0 << 0; + _e0 |= (ulong)e1 << 32; + _e1 = (ulong)e2 << 0; + _e1 |= (ulong)e3 << 32; + } + + /// + /// Initializes a new instance of the struct from the specified array. + /// + /// array to use + public V128(byte[] data) + { + _e0 = (ulong)BitConverter.ToInt64(data, 0); + _e1 = (ulong)BitConverter.ToInt64(data, 8); + } + + /// + /// Returns the value of the as a scalar. + /// + /// Type of scalar + /// Value of the as a scalar + /// Size of is larger than 16 bytes + public T As() where T : unmanaged + { + return Extract(0); + } + + /// + /// Extracts the element at the specified index as a from the . + /// + /// Element type + /// Index of element + /// Element at the specified index as a from the + /// + /// is out of bound or the size of is larger than 16 bytes + /// + public T Extract(int index) where T : unmanaged + { + if ((uint)index >= GetElementCount()) + { + ThrowIndexOutOfRange(); + } + + // Performs: + // return *((*T)this + index); + return Unsafe.Add(ref Unsafe.As(ref this), index); + } + + /// + /// Inserts the specified value into the element at the specified index in the . + /// + /// Element type + /// Index of element + /// Value to insert + /// + /// is out of bound or the size of is larger than 16 bytes + /// + public void Insert(int index, T value) where T : unmanaged + { + if ((uint)index >= GetElementCount()) + { + ThrowIndexOutOfRange(); + } + + // Performs: + // *((*T)this + index) = value; + Unsafe.Add(ref Unsafe.As(ref this), index) = value; + } + + /// + /// Returns a new array which represents the . + /// + /// A new array which represents the + public readonly byte[] ToArray() + { + byte[] data = new byte[16]; + Span span = data; + + BitConverter.TryWriteBytes(span, _e0); + BitConverter.TryWriteBytes(span[8..], _e1); + + return data; + } + + /// + /// Performs a bitwise logical left shift on the specified by the specified shift count. + /// + /// instance + /// Number of shifts + /// Result of left shift + /// + /// This supports shift counts up to 63; anything above may result in unexpected behaviour. + /// + public static V128 operator <<(V128 x, int shift) + { + if (shift == 0) + { + return new V128(x._e0, x._e1); + } + + ulong shiftOut = x._e0 >> (64 - shift); + + return new V128(x._e0 << shift, (x._e1 << shift) | shiftOut); + } + + /// + /// Performs a bitwise logical right shift on the specified by the specified shift count. + /// + /// instance + /// Number of shifts + /// Result of right shift + /// + /// This supports shift counts up to 63; anything above may result in unexpected behaviour. + /// + public static V128 operator >>(V128 x, int shift) + { + if (shift == 0) + { + return new V128(x._e0, x._e1); + } + + ulong shiftOut = x._e1 & ((1UL << shift) - 1); + + return new V128((x._e0 >> shift) | (shiftOut << (64 - shift)), x._e1 >> shift); + } + + /// + /// Performs a bitwise not on the specified . + /// + /// Target + /// Result of not operation + public static V128 operator ~(V128 x) => new(~x._e0, ~x._e1); + + /// + /// Performs a bitwise and on the specified instances. + /// + /// First instance + /// Second instance + /// Result of and operation + public static V128 operator &(V128 x, V128 y) => new(x._e0 & y._e0, x._e1 & y._e1); + + /// + /// Performs a bitwise or on the specified instances. + /// + /// First instance + /// Second instance + /// Result of or operation + public static V128 operator |(V128 x, V128 y) => new(x._e0 | y._e0, x._e1 | y._e1); + + /// + /// Performs a bitwise exlusive or on the specified instances. + /// + /// First instance + /// Second instance + /// Result of exclusive or operation + public static V128 operator ^(V128 x, V128 y) => new(x._e0 ^ y._e0, x._e1 ^ y._e1); + + /// + /// Determines if the specified instances are equal. + /// + /// First instance + /// Second instance + /// true if equal; otherwise false + public static bool operator ==(V128 x, V128 y) => x.Equals(y); + + /// + /// Determines if the specified instances are not equal. + /// + /// First instance + /// Second instance + /// true if not equal; otherwise false + public static bool operator !=(V128 x, V128 y) => !x.Equals(y); + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// true if equal; otherwise false + public readonly bool Equals(V128 other) + { + return other._e0 == _e0 && other._e1 == _e1; + } + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// true if equal; otherwise false + public readonly override bool Equals(object obj) + { + return obj is V128 vector && Equals(vector); + } + + /// + public readonly override int GetHashCode() + { + return HashCode.Combine(_e0, _e1); + } + + /// + public readonly override string ToString() + { + return $"0x{_e1:X16}{_e0:X16}"; + } + + private static uint GetElementCount() where T : unmanaged + { + return (uint)(Unsafe.SizeOf() / Unsafe.SizeOf()); + } + + private static void ThrowIndexOutOfRange() + { + throw new ArgumentOutOfRangeException("index"); + } + } +} diff --git a/src/ARMeilleure/Statistics.cs b/src/ARMeilleure/Statistics.cs new file mode 100644 index 000000000..2f873bcfa --- /dev/null +++ b/src/ARMeilleure/Statistics.cs @@ -0,0 +1,96 @@ +#if M_PROFILE +using System; +#endif +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace ARMeilleure +{ + public static class Statistics + { + private const int ReportMaxFunctions = 100; + +#if M_PROFILE + [ThreadStatic] + private static Stopwatch _executionTimer; +#endif + + private static readonly ConcurrentDictionary _ticksPerFunction; + + static Statistics() + { + _ticksPerFunction = new ConcurrentDictionary(); + } + + public static void InitializeTimer() + { +#if M_PROFILE + if (_executionTimer == null) + { + _executionTimer = new Stopwatch(); + } +#endif + } + + internal static void StartTimer() + { +#if M_PROFILE + _executionTimer.Restart(); +#endif + } + + internal static void StopTimer(ulong funcAddr) + { +#if M_PROFILE + _executionTimer.Stop(); + + long ticks = _executionTimer.ElapsedTicks; + + TicksPerFunction.AddOrUpdate(funcAddr, ticks, (key, oldTicks) => oldTicks + ticks); +#endif + } + + internal static void ResumeTimer() + { +#if M_PROFILE + _executionTimer.Start(); +#endif + } + + internal static void PauseTimer() + { +#if M_PROFILE + _executionTimer.Stop(); +#endif + } + + public static string GetReport() + { + int count = 0; + + StringBuilder sb = new(); + + sb.AppendLine(" Function address | Time"); + sb.AppendLine("--------------------------"); + + KeyValuePair[] funcTable = _ticksPerFunction.ToArray(); + + foreach (KeyValuePair kv in funcTable.OrderByDescending(x => x.Value)) + { + long timeInMs = (kv.Value * 1000) / Stopwatch.Frequency; + + sb.AppendLine($" 0x{kv.Key:X16} | {timeInMs} ms"); + + if (count++ >= ReportMaxFunctions) + { + break; + } + } + + return sb.ToString(); + } + } +} diff --git a/src/ARMeilleure/Translation/ArmEmitterContext.cs b/src/ARMeilleure/Translation/ArmEmitterContext.cs new file mode 100644 index 000000000..e24074739 --- /dev/null +++ b/src/ARMeilleure/Translation/ArmEmitterContext.cs @@ -0,0 +1,286 @@ +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 _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(); + } + } + + private bool _pendingQcFlagSync; + + public OpCode CurrOp { get; set; } + + public IMemoryManager Memory { get; } + + public EntryTable CountTable { get; } + public AddressTable 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 = Array.Empty(); + public bool IsInIfThenBlock => _ifThenBlockStateIndex < _ifThenBlockState.Length; + public Condition CurrentIfThenBlockCond => _ifThenBlockState[_ifThenBlockStateIndex]; + + public ArmEmitterContext( + IMemoryManager memory, + EntryTable countTable, + AddressTable 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(); + } + + public override Operand Call(MethodInfo info, params Operand[] callArgs) + { + SyncQcFlag(); + + 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(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 void SetPendingQcFlagSync() + { + _pendingQcFlagSync = true; + } + + public void SyncQcFlag() + { + if (_pendingQcFlagSync) + { + if (Optimizations.UseAdvSimd) + { + Operand fpsr = AddIntrinsicInt(Intrinsic.Arm64MrsFpsr); + + uint qcFlagMask = (uint)FPSR.Qc; + + Operand qcClearLabel = Label(); + + BranchIfFalse(qcClearLabel, BitwiseAnd(fpsr, Const(qcFlagMask))); + + AddIntrinsicNoRet(Intrinsic.Arm64MsrFpsr, Const(0)); + InstEmitHelper.SetFpFlag(this, FPState.QcFlag, Const(1)); + + MarkLabel(qcClearLabel); + } + + _pendingQcFlagSync = false; + } + } + + public void ClearQcFlag() + { + if (Optimizations.UseAdvSimd) + { + AddIntrinsicNoRet(Intrinsic.Arm64MsrFpsr, Const(0)); + } + } + + public void ClearQcFlagIfModified() + { + if (_pendingQcFlagSync && Optimizations.UseAdvSimd) + { + AddIntrinsicNoRet(Intrinsic.Arm64MsrFpsr, Const(0)); + } + } + + public void EnterArmFpMode() + { + InstEmitSimdHelper.EnterArmFpMode(this, InstEmitHelper.GetFpFlag); + } + + public void UpdateArmFpMode() + { + EnterArmFpMode(); + } + + public void ExitArmFpMode() + { + InstEmitSimdHelper.ExitArmFpMode(this, (flag, value) => InstEmitHelper.SetFpFlag(this, flag, value)); + } + + 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) + { +#pragma warning disable IDE0055 // Disable formatting + 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); +#pragma warning restore IDE0055 + } + } + 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) + { +#pragma warning disable IDE0055 // Disable formatting + 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); +#pragma warning restore IDE0055 + } + } + + return default; + } + + public void SetIfThenBlockState(Condition[] state) + { + _ifThenBlockState = state; + _ifThenBlockStateIndex = 0; + } + + public void AdvanceIfThenBlockState() + { + if (IsInIfThenBlock) + { + _ifThenBlockStateIndex++; + } + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/CacheEntry.cs b/src/ARMeilleure/Translation/Cache/CacheEntry.cs new file mode 100644 index 000000000..25b06f781 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/CacheEntry.cs @@ -0,0 +1,26 @@ +using ARMeilleure.CodeGen.Unwinding; +using System; +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.Translation.Cache +{ + readonly struct CacheEntry : IComparable + { + public int Offset { get; } + public int Size { get; } + + public UnwindInfo UnwindInfo { get; } + + public CacheEntry(int offset, int size, UnwindInfo unwindInfo) + { + Offset = offset; + Size = size; + UnwindInfo = unwindInfo; + } + + public int CompareTo([AllowNull] CacheEntry other) + { + return Offset.CompareTo(other.Offset); + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs b/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs new file mode 100644 index 000000000..f36bf7a3d --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.Translation.Cache +{ + class CacheMemoryAllocator + { + private readonly struct MemoryBlock : IComparable + { + public int Offset { get; } + public int Size { get; } + + public MemoryBlock(int offset, int size) + { + Offset = offset; + Size = size; + } + + public int CompareTo([AllowNull] MemoryBlock other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly List _blocks = new(); + + public CacheMemoryAllocator(int capacity) + { + _blocks.Add(new MemoryBlock(0, capacity)); + } + + public int Allocate(int size) + { + for (int i = 0; i < _blocks.Count; i++) + { + MemoryBlock block = _blocks[i]; + + if (block.Size > size) + { + _blocks[i] = new MemoryBlock(block.Offset + size, block.Size - size); + return block.Offset; + } + else if (block.Size == size) + { + _blocks.RemoveAt(i); + return block.Offset; + } + } + + // We don't have enough free memory to perform the allocation. + return -1; + } + + public void Free(int offset, int size) + { + Insert(new MemoryBlock(offset, size)); + } + + private void Insert(MemoryBlock block) + { + int index = _blocks.BinarySearch(block); + + if (index < 0) + { + index = ~index; + } + + if (index < _blocks.Count) + { + MemoryBlock next = _blocks[index]; + + int endOffs = block.Offset + block.Size; + + if (next.Offset == endOffs) + { + block = new MemoryBlock(block.Offset, block.Size + next.Size); + _blocks.RemoveAt(index); + } + } + + if (index > 0) + { + MemoryBlock prev = _blocks[index - 1]; + + if (prev.Offset + prev.Size == block.Offset) + { + block = new MemoryBlock(block.Offset - prev.Size, block.Size + prev.Size); + _blocks.RemoveAt(--index); + } + } + + _blocks.Insert(index, block); + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/JitCache.cs b/src/ARMeilleure/Translation/Cache/JitCache.cs new file mode 100644 index 000000000..e2b5e2d10 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/JitCache.cs @@ -0,0 +1,207 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Memory; +using ARMeilleure.Native; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace ARMeilleure.Translation.Cache +{ + static partial class JitCache + { + private static readonly int _pageSize = (int)MemoryBlock.GetPageSize(); + private static readonly int _pageMask = _pageSize - 1; + + private const int CodeAlignment = 4; // Bytes. + private const int CacheSize = 2047 * 1024 * 1024; + + private static ReservedRegion _jitRegion; + private static JitCacheInvalidation _jitCacheInvalidator; + + private static CacheMemoryAllocator _cacheAllocator; + + private static readonly List _cacheEntries = new(); + + private static readonly object _lock = new(); + private static bool _initialized; + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32.dll", SetLastError = true)] + public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize); + + public static void Initialize(IJitMemoryAllocator allocator) + { + if (_initialized) + { + return; + } + + lock (_lock) + { + if (_initialized) + { + return; + } + + _jitRegion = new ReservedRegion(allocator, CacheSize); + + if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) + { + _jitCacheInvalidator = new JitCacheInvalidation(allocator); + } + + _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; + + if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + unsafe + { + fixed (byte* codePtr = code) + { + JitSupportDarwin.Copy(funcPtr, (IntPtr)codePtr, (ulong)code.Length); + } + } + } + else + { + ReprotectAsWritable(funcOffset, code.Length); + Marshal.Copy(code, 0, funcPtr, code.Length); + ReprotectAsExecutable(funcOffset, code.Length); + + if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (UIntPtr)code.Length); + } + else + { + _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)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()); + + if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) + { + _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); + _cacheEntries.RemoveAt(entryIndex); + } + } + } + + 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(offset, size, unwindInfo); + + int index = _cacheEntries.BinarySearch(entry); + + if (index < 0) + { + index = ~index; + } + + _cacheEntries.Insert(index, entry); + } + + public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex) + { + lock (_lock) + { + int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); + + if (index < 0) + { + index = ~index - 1; + } + + if (index >= 0) + { + entry = _cacheEntries[index]; + entryIndex = index; + return true; + } + } + + entry = default; + entryIndex = 0; + return false; + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs b/src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs new file mode 100644 index 000000000..3aa2e19f1 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs @@ -0,0 +1,79 @@ +using ARMeilleure.Memory; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.Cache +{ + class JitCacheInvalidation + { + private static readonly int[] _invalidationCode = new int[] + { + unchecked((int)0xd53b0022), // mrs x2, ctr_el0 + unchecked((int)0xd3504c44), // ubfx x4, x2, #16, #4 + unchecked((int)0x52800083), // mov w3, #0x4 + unchecked((int)0x12000c45), // and w5, w2, #0xf + unchecked((int)0x1ac42064), // lsl w4, w3, w4 + unchecked((int)0x51000482), // sub w2, w4, #0x1 + unchecked((int)0x8a220002), // bic x2, x0, x2 + unchecked((int)0x1ac52063), // lsl w3, w3, w5 + unchecked((int)0xeb01005f), // cmp x2, x1 + unchecked((int)0x93407c84), // sxtw x4, w4 + unchecked((int)0x540000a2), // b.cs 3c + unchecked((int)0xd50b7b22), // dc cvau, x2 + unchecked((int)0x8b040042), // add x2, x2, x4 + unchecked((int)0xeb02003f), // cmp x1, x2 + unchecked((int)0x54ffffa8), // b.hi 2c + unchecked((int)0xd5033b9f), // dsb ish + unchecked((int)0x51000462), // sub w2, w3, #0x1 + unchecked((int)0x93407c63), // sxtw x3, w3 + unchecked((int)0x8a220000), // bic x0, x0, x2 + unchecked((int)0xeb00003f), // cmp x1, x0 + unchecked((int)0x540000a9), // b.ls 64 + unchecked((int)0xd50b7520), // ic ivau, x0 + unchecked((int)0x8b030000), // add x0, x0, x3 + unchecked((int)0xeb00003f), // cmp x1, x0 + unchecked((int)0x54ffffa8), // b.hi 54 + unchecked((int)0xd5033b9f), // dsb ish + unchecked((int)0xd5033fdf), // isb + unchecked((int)0xd65f03c0), // ret + }; + + private delegate void InvalidateCache(ulong start, ulong end); + + private readonly InvalidateCache _invalidateCache; + private readonly ReservedRegion _invalidateCacheCodeRegion; + + private readonly bool _needsInvalidation; + + public JitCacheInvalidation(IJitMemoryAllocator allocator) + { + // On macOS and Windows, a different path is used to write to the JIT cache, which does the invalidation. + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + ulong size = (ulong)_invalidationCode.Length * sizeof(int); + ulong mask = (ulong)ReservedRegion.DefaultGranularity - 1; + + size = (size + mask) & ~mask; + + _invalidateCacheCodeRegion = new ReservedRegion(allocator, size); + _invalidateCacheCodeRegion.ExpandIfNeeded(size); + + Marshal.Copy(_invalidationCode, 0, _invalidateCacheCodeRegion.Pointer, _invalidationCode.Length); + + _invalidateCacheCodeRegion.Block.MapAsRx(0, size); + + _invalidateCache = Marshal.GetDelegateForFunctionPointer(_invalidateCacheCodeRegion.Pointer); + + _needsInvalidation = true; + } + } + + public void Invalidate(IntPtr basePointer, ulong size) + { + if (_needsInvalidation) + { + _invalidateCache((ulong)basePointer, (ulong)basePointer + size); + } + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs b/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs new file mode 100644 index 000000000..3957a7559 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs @@ -0,0 +1,190 @@ +// https://github.com/MicrosoftDocs/cpp-docs/blob/master/docs/build/exception-handling-x64.md + +using ARMeilleure.CodeGen.Unwinding; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.Cache +{ + static partial class JitUnwindWindows + { + private const int MaxUnwindCodesArraySize = 32; // Must be an even value. + + private struct RuntimeFunction + { + public uint BeginAddress; + public uint EndAddress; + public uint UnwindData; + } + + private struct UnwindInfo + { + public byte VersionAndFlags; + public byte SizeOfProlog; + public byte CountOfUnwindCodes; + public byte FrameRegister; + public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize]; + } + + private enum UnwindOp + { + PushNonvol = 0, + AllocLarge = 1, + AllocSmall = 2, + SetFpreg = 3, + SaveNonvol = 4, + SaveNonvolFar = 5, + SaveXmm128 = 8, + SaveXmm128Far = 9, + PushMachframe = 10, + } + + private unsafe delegate RuntimeFunction* GetRuntimeFunctionCallback(ulong controlPc, IntPtr context); + + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static unsafe partial bool RtlInstallFunctionTableCallback( + ulong tableIdentifier, + ulong baseAddress, + uint length, + GetRuntimeFunctionCallback callback, + IntPtr context, + [MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll); + + private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback; + + private static int _sizeOfRuntimeFunction; + + private unsafe static RuntimeFunction* _runtimeFunction; + + private unsafe static UnwindInfo* _unwindInfo; + + public static void InstallFunctionTableHandler(IntPtr codeCachePointer, uint codeCacheLength, IntPtr workBufferPtr) + { + ulong codeCachePtr = (ulong)codeCachePointer.ToInt64(); + + _sizeOfRuntimeFunction = Marshal.SizeOf(); + + bool result; + + unsafe + { + _runtimeFunction = (RuntimeFunction*)workBufferPtr; + + _unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction); + + _getRuntimeFunctionCallback = new GetRuntimeFunctionCallback(FunctionTableHandler); + + result = RtlInstallFunctionTableCallback( + codeCachePtr | 3, + codeCachePtr, + codeCacheLength, + _getRuntimeFunctionCallback, + codeCachePointer, + null); + } + + if (!result) + { + throw new InvalidOperationException("Failure installing function table callback."); + } + } + + private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, IntPtr context) + { + int offset = (int)((long)controlPc - context.ToInt64()); + + if (!JitCache.TryFind(offset, out CacheEntry funcEntry, out _)) + { + return null; // Not found. + } + + var unwindInfo = funcEntry.UnwindInfo; + + int codeIndex = 0; + + for (int index = unwindInfo.PushEntries.Length - 1; index >= 0; index--) + { + var entry = unwindInfo.PushEntries[index]; + + switch (entry.PseudoOp) + { + case UnwindPseudoOp.SaveXmm128: + { + int stackOffset = entry.StackOffsetOrAllocSize; + + Debug.Assert(stackOffset % 16 == 0); + + if (stackOffset <= 0xFFFF0) + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16); + } + else + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16); + } + + break; + } + + case UnwindPseudoOp.AllocStack: + { + int allocSize = entry.StackOffsetOrAllocSize; + + Debug.Assert(allocSize % 8 == 0); + + if (allocSize <= 128) + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1); + } + else if (allocSize <= 0x7FFF8) + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8); + } + else + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16); + } + + break; + } + + case UnwindPseudoOp.PushReg: + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex); + + break; + } + + default: + throw new NotImplementedException($"({nameof(entry.PseudoOp)} = {entry.PseudoOp})"); + } + } + + Debug.Assert(codeIndex <= MaxUnwindCodesArraySize); + + _unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler. + _unwindInfo->SizeOfProlog = (byte)unwindInfo.PrologSize; + _unwindInfo->CountOfUnwindCodes = (byte)codeIndex; + _unwindInfo->FrameRegister = 0; + + _runtimeFunction->BeginAddress = (uint)funcEntry.Offset; + _runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size); + _runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction; + + return _runtimeFunction; + } + + private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo) + { + return (ushort)(prologOffset | ((int)op << 8) | (opInfo << 12)); + } + } +} diff --git a/src/ARMeilleure/Translation/Compiler.cs b/src/ARMeilleure/Translation/Compiler.cs new file mode 100644 index 000000000..293e63496 --- /dev/null +++ b/src/ARMeilleure/Translation/Compiler.cs @@ -0,0 +1,68 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation +{ + static class Compiler + { + public static CompiledFunction Compile( + ControlFlowGraph cfg, + OperandType[] argTypes, + OperandType retType, + CompilerOptions options, + Architecture target) + { + 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); + } + + if (target == Architecture.X64) + { + return CodeGen.X86.CodeGenerator.Generate(cctx); + } + else if (target == Architecture.Arm64) + { + return CodeGen.Arm64.CodeGenerator.Generate(cctx); + } + else + { + throw new NotImplementedException(target.ToString()); + } + } + } +} diff --git a/src/ARMeilleure/Translation/CompilerContext.cs b/src/ARMeilleure/Translation/CompilerContext.cs new file mode 100644 index 000000000..5b10686b3 --- /dev/null +++ b/src/ARMeilleure/Translation/CompilerContext.cs @@ -0,0 +1,26 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.Translation +{ + readonly struct CompilerContext + { + public ControlFlowGraph Cfg { get; } + + public OperandType[] FuncArgTypes { get; } + public OperandType FuncReturnType { get; } + + public CompilerOptions Options { get; } + + public CompilerContext( + ControlFlowGraph cfg, + OperandType[] funcArgTypes, + OperandType funcReturnType, + CompilerOptions options) + { + Cfg = cfg; + FuncArgTypes = funcArgTypes; + FuncReturnType = funcReturnType; + Options = options; + } + } +} diff --git a/src/ARMeilleure/Translation/CompilerOptions.cs b/src/ARMeilleure/Translation/CompilerOptions.cs new file mode 100644 index 000000000..d454de7f2 --- /dev/null +++ b/src/ARMeilleure/Translation/CompilerOptions.cs @@ -0,0 +1,17 @@ +using System; + +namespace ARMeilleure.Translation +{ + [Flags] + enum CompilerOptions + { + None = 0, + SsaForm = 1 << 0, + Optimize = 1 << 1, + Lsra = 1 << 2, + Relocatable = 1 << 3, + + MediumCq = SsaForm | Optimize, + HighCq = SsaForm | Optimize | Lsra, + } +} diff --git a/src/ARMeilleure/Translation/ControlFlowGraph.cs b/src/ARMeilleure/Translation/ControlFlowGraph.cs new file mode 100644 index 000000000..3ead49c93 --- /dev/null +++ b/src/ARMeilleure/Translation/ControlFlowGraph.cs @@ -0,0 +1,155 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.Translation +{ + class ControlFlowGraph + { + private BasicBlock[] _postOrderBlocks; + private int[] _postOrderMap; + + public int LocalsCount { get; private set; } + public BasicBlock Entry { get; } + public IntrusiveList Blocks { get; } + public BasicBlock[] PostOrderBlocks => _postOrderBlocks; + public int[] PostOrderMap => _postOrderMap; + + public ControlFlowGraph(BasicBlock entry, IntrusiveList blocks, int localsCount) + { + Entry = entry; + Blocks = blocks; + LocalsCount = localsCount; + + Update(); + } + + public Operand AllocateLocal(OperandType type) + { + Operand result = Operand.Factory.Local(type); + + result.NumberLocal(++LocalsCount); + + return result; + } + + public void Update() + { + RemoveUnreachableBlocks(Blocks); + + var visited = new HashSet(); + var blockStack = new Stack(); + + Array.Resize(ref _postOrderBlocks, Blocks.Count); + Array.Resize(ref _postOrderMap, Blocks.Count); + + visited.Add(Entry); + blockStack.Push(Entry); + + int index = 0; + + while (blockStack.TryPop(out BasicBlock block)) + { + bool visitedNew = false; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock succ = block.GetSuccessor(i); + + if (visited.Add(succ)) + { + blockStack.Push(block); + blockStack.Push(succ); + + visitedNew = true; + + break; + } + } + + if (!visitedNew) + { + PostOrderMap[block.Index] = index; + + PostOrderBlocks[index++] = block; + } + } + } + + private void RemoveUnreachableBlocks(IntrusiveList blocks) + { + var visited = new HashSet(); + var workQueue = new Queue(); + + visited.Add(Entry); + workQueue.Enqueue(Entry); + + while (workQueue.TryDequeue(out BasicBlock block)) + { + Debug.Assert(block.Index != -1, "Invalid block index."); + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock succ = block.GetSuccessor(i); + + if (visited.Add(succ)) + { + workQueue.Enqueue(succ); + } + } + } + + if (visited.Count < blocks.Count) + { + // Remove unreachable blocks and renumber. + int index = 0; + + for (BasicBlock block = blocks.First; block != null;) + { + BasicBlock nextBlock = block.ListNext; + + if (!visited.Contains(block)) + { + while (block.SuccessorsCount > 0) + { + block.RemoveSuccessor(index: block.SuccessorsCount - 1); + } + + blocks.Remove(block); + } + else + { + block.Index = index++; + } + + block = nextBlock; + } + } + } + + public BasicBlock SplitEdge(BasicBlock predecessor, BasicBlock successor) + { + BasicBlock splitBlock = new(Blocks.Count); + + for (int i = 0; i < predecessor.SuccessorsCount; i++) + { + if (predecessor.GetSuccessor(i) == successor) + { + predecessor.SetSuccessor(i, splitBlock); + } + } + + if (splitBlock.Predecessors.Count == 0) + { + throw new ArgumentException("Predecessor and successor are not connected."); + } + + splitBlock.AddSuccessor(successor); + + Blocks.AddBefore(successor, splitBlock); + + return splitBlock; + } + } +} diff --git a/src/ARMeilleure/Translation/DelegateInfo.cs b/src/ARMeilleure/Translation/DelegateInfo.cs new file mode 100644 index 000000000..706625437 --- /dev/null +++ b/src/ARMeilleure/Translation/DelegateInfo.cs @@ -0,0 +1,19 @@ +using System; + +namespace ARMeilleure.Translation +{ + class DelegateInfo + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly Delegate _dlg; // Ensure that this delegate will not be garbage collected. +#pragma warning restore IDE0052 + + public IntPtr FuncPtr { get; } + + public DelegateInfo(Delegate dlg, IntPtr funcPtr) + { + _dlg = dlg; + FuncPtr = funcPtr; + } + } +} diff --git a/src/ARMeilleure/Translation/Delegates.cs b/src/ARMeilleure/Translation/Delegates.cs new file mode 100644 index 000000000..66412b8e6 --- /dev/null +++ b/src/ARMeilleure/Translation/Delegates.cs @@ -0,0 +1,610 @@ +using ARMeilleure.Instructions; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; + +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(Delegate dlg, IntPtr funcPtr) + { + string key = GetKey(dlg.Method); + + _delegates.Add(key, new DelegateInfo(dlg, funcPtr)); // ArgumentException (key). + } + + private static string GetKey(MethodInfo info) + { + return $"{info.DeclaringType.Name}.{info.Name}"; + } + + private static readonly SortedList _delegates; + + static Delegates() + { + _delegates = new SortedList(); + + var dlgMathAbs = new MathAbs(Math.Abs); + var dlgMathCeiling = new MathCeiling(Math.Ceiling); + var dlgMathFloor = new MathFloor(Math.Floor); + var dlgMathRound = new MathRound(Math.Round); + var dlgMathTruncate = new MathTruncate(Math.Truncate); + + var dlgMathFAbs = new MathFAbs(MathF.Abs); + var dlgMathFCeiling = new MathFCeiling(MathF.Ceiling); + var dlgMathFFloor = new MathFFloor(MathF.Floor); + var dlgMathFRound = new MathFRound(MathF.Round); + var dlgMathFTruncate = new MathFTruncate(MathF.Truncate); + + var dlgNativeInterfaceBreak = new NativeInterfaceBreak(NativeInterface.Break); + var dlgNativeInterfaceCheckSynchronization = new NativeInterfaceCheckSynchronization(NativeInterface.CheckSynchronization); + var dlgNativeInterfaceEnqueueForRejit = new NativeInterfaceEnqueueForRejit(NativeInterface.EnqueueForRejit); + var dlgNativeInterfaceGetCntfrqEl0 = new NativeInterfaceGetCntfrqEl0(NativeInterface.GetCntfrqEl0); + var dlgNativeInterfaceGetCntpctEl0 = new NativeInterfaceGetCntpctEl0(NativeInterface.GetCntpctEl0); + var dlgNativeInterfaceGetCntvctEl0 = new NativeInterfaceGetCntvctEl0(NativeInterface.GetCntvctEl0); + var dlgNativeInterfaceGetCtrEl0 = new NativeInterfaceGetCtrEl0(NativeInterface.GetCtrEl0); + var dlgNativeInterfaceGetDczidEl0 = new NativeInterfaceGetDczidEl0(NativeInterface.GetDczidEl0); + var dlgNativeInterfaceGetFunctionAddress = new NativeInterfaceGetFunctionAddress(NativeInterface.GetFunctionAddress); + var dlgNativeInterfaceInvalidateCacheLine = new NativeInterfaceInvalidateCacheLine(NativeInterface.InvalidateCacheLine); + var dlgNativeInterfaceReadByte = new NativeInterfaceReadByte(NativeInterface.ReadByte); + var dlgNativeInterfaceReadUInt16 = new NativeInterfaceReadUInt16(NativeInterface.ReadUInt16); + var dlgNativeInterfaceReadUInt32 = new NativeInterfaceReadUInt32(NativeInterface.ReadUInt32); + var dlgNativeInterfaceReadUInt64 = new NativeInterfaceReadUInt64(NativeInterface.ReadUInt64); + var dlgNativeInterfaceReadVector128 = new NativeInterfaceReadVector128(NativeInterface.ReadVector128); + var dlgNativeInterfaceSignalMemoryTracking = new NativeInterfaceSignalMemoryTracking(NativeInterface.SignalMemoryTracking); + var dlgNativeInterfaceSupervisorCall = new NativeInterfaceSupervisorCall(NativeInterface.SupervisorCall); + var dlgNativeInterfaceThrowInvalidMemoryAccess = new NativeInterfaceThrowInvalidMemoryAccess(NativeInterface.ThrowInvalidMemoryAccess); + var dlgNativeInterfaceUndefined = new NativeInterfaceUndefined(NativeInterface.Undefined); + var dlgNativeInterfaceWriteByte = new NativeInterfaceWriteByte(NativeInterface.WriteByte); + var dlgNativeInterfaceWriteUInt16 = new NativeInterfaceWriteUInt16(NativeInterface.WriteUInt16); + var dlgNativeInterfaceWriteUInt32 = new NativeInterfaceWriteUInt32(NativeInterface.WriteUInt32); + var dlgNativeInterfaceWriteUInt64 = new NativeInterfaceWriteUInt64(NativeInterface.WriteUInt64); + var dlgNativeInterfaceWriteVector128 = new NativeInterfaceWriteVector128(NativeInterface.WriteVector128); + + var dlgSoftFallbackCountLeadingSigns = new SoftFallbackCountLeadingSigns(SoftFallback.CountLeadingSigns); + var dlgSoftFallbackCountLeadingZeros = new SoftFallbackCountLeadingZeros(SoftFallback.CountLeadingZeros); + var dlgSoftFallbackCrc32b = new SoftFallbackCrc32b(SoftFallback.Crc32b); + var dlgSoftFallbackCrc32cb = new SoftFallbackCrc32cb(SoftFallback.Crc32cb); + var dlgSoftFallbackCrc32ch = new SoftFallbackCrc32ch(SoftFallback.Crc32ch); + var dlgSoftFallbackCrc32cw = new SoftFallbackCrc32cw(SoftFallback.Crc32cw); + var dlgSoftFallbackCrc32cx = new SoftFallbackCrc32cx(SoftFallback.Crc32cx); + var dlgSoftFallbackCrc32h = new SoftFallbackCrc32h(SoftFallback.Crc32h); + var dlgSoftFallbackCrc32w = new SoftFallbackCrc32w(SoftFallback.Crc32w); + var dlgSoftFallbackCrc32x = new SoftFallbackCrc32x(SoftFallback.Crc32x); + var dlgSoftFallbackDecrypt = new SoftFallbackDecrypt(SoftFallback.Decrypt); + var dlgSoftFallbackEncrypt = new SoftFallbackEncrypt(SoftFallback.Encrypt); + var dlgSoftFallbackFixedRotate = new SoftFallbackFixedRotate(SoftFallback.FixedRotate); + var dlgSoftFallbackHashChoose = new SoftFallbackHashChoose(SoftFallback.HashChoose); + var dlgSoftFallbackHashLower = new SoftFallbackHashLower(SoftFallback.HashLower); + var dlgSoftFallbackHashMajority = new SoftFallbackHashMajority(SoftFallback.HashMajority); + var dlgSoftFallbackHashParity = new SoftFallbackHashParity(SoftFallback.HashParity); + var dlgSoftFallbackHashUpper = new SoftFallbackHashUpper(SoftFallback.HashUpper); + var dlgSoftFallbackInverseMixColumns = new SoftFallbackInverseMixColumns(SoftFallback.InverseMixColumns); + var dlgSoftFallbackMixColumns = new SoftFallbackMixColumns(SoftFallback.MixColumns); + var dlgSoftFallbackPolynomialMult64_128 = new SoftFallbackPolynomialMult64_128(SoftFallback.PolynomialMult64_128); + var dlgSoftFallbackSatF32ToS32 = new SoftFallbackSatF32ToS32(SoftFallback.SatF32ToS32); + var dlgSoftFallbackSatF32ToS64 = new SoftFallbackSatF32ToS64(SoftFallback.SatF32ToS64); + var dlgSoftFallbackSatF32ToU32 = new SoftFallbackSatF32ToU32(SoftFallback.SatF32ToU32); + var dlgSoftFallbackSatF32ToU64 = new SoftFallbackSatF32ToU64(SoftFallback.SatF32ToU64); + var dlgSoftFallbackSatF64ToS32 = new SoftFallbackSatF64ToS32(SoftFallback.SatF64ToS32); + var dlgSoftFallbackSatF64ToS64 = new SoftFallbackSatF64ToS64(SoftFallback.SatF64ToS64); + var dlgSoftFallbackSatF64ToU32 = new SoftFallbackSatF64ToU32(SoftFallback.SatF64ToU32); + var dlgSoftFallbackSatF64ToU64 = new SoftFallbackSatF64ToU64(SoftFallback.SatF64ToU64); + var dlgSoftFallbackSha1SchedulePart1 = new SoftFallbackSha1SchedulePart1(SoftFallback.Sha1SchedulePart1); + var dlgSoftFallbackSha1SchedulePart2 = new SoftFallbackSha1SchedulePart2(SoftFallback.Sha1SchedulePart2); + var dlgSoftFallbackSha256SchedulePart1 = new SoftFallbackSha256SchedulePart1(SoftFallback.Sha256SchedulePart1); + var dlgSoftFallbackSha256SchedulePart2 = new SoftFallbackSha256SchedulePart2(SoftFallback.Sha256SchedulePart2); + var dlgSoftFallbackSignedShrImm64 = new SoftFallbackSignedShrImm64(SoftFallback.SignedShrImm64); + var dlgSoftFallbackTbl1 = new SoftFallbackTbl1(SoftFallback.Tbl1); + var dlgSoftFallbackTbl2 = new SoftFallbackTbl2(SoftFallback.Tbl2); + var dlgSoftFallbackTbl3 = new SoftFallbackTbl3(SoftFallback.Tbl3); + var dlgSoftFallbackTbl4 = new SoftFallbackTbl4(SoftFallback.Tbl4); + var dlgSoftFallbackTbx1 = new SoftFallbackTbx1(SoftFallback.Tbx1); + var dlgSoftFallbackTbx2 = new SoftFallbackTbx2(SoftFallback.Tbx2); + var dlgSoftFallbackTbx3 = new SoftFallbackTbx3(SoftFallback.Tbx3); + var dlgSoftFallbackTbx4 = new SoftFallbackTbx4(SoftFallback.Tbx4); + var dlgSoftFallbackUnsignedShrImm64 = new SoftFallbackUnsignedShrImm64(SoftFallback.UnsignedShrImm64); + + var dlgSoftFloat16_32FPConvert = new SoftFloat16_32FPConvert(SoftFloat16_32.FPConvert); + var dlgSoftFloat16_64FPConvert = new SoftFloat16_64FPConvert(SoftFloat16_64.FPConvert); + + var dlgSoftFloat32FPAdd = new SoftFloat32FPAdd(SoftFloat32.FPAdd); + var dlgSoftFloat32FPAddFpscr = new SoftFloat32FPAddFpscr(SoftFloat32.FPAddFpscr); // A32 only. + var dlgSoftFloat32FPCompare = new SoftFloat32FPCompare(SoftFloat32.FPCompare); + var dlgSoftFloat32FPCompareEQ = new SoftFloat32FPCompareEQ(SoftFloat32.FPCompareEQ); + var dlgSoftFloat32FPCompareEQFpscr = new SoftFloat32FPCompareEQFpscr(SoftFloat32.FPCompareEQFpscr); // A32 only. + var dlgSoftFloat32FPCompareGE = new SoftFloat32FPCompareGE(SoftFloat32.FPCompareGE); + var dlgSoftFloat32FPCompareGEFpscr = new SoftFloat32FPCompareGEFpscr(SoftFloat32.FPCompareGEFpscr); // A32 only. + var dlgSoftFloat32FPCompareGT = new SoftFloat32FPCompareGT(SoftFloat32.FPCompareGT); + var dlgSoftFloat32FPCompareGTFpscr = new SoftFloat32FPCompareGTFpscr(SoftFloat32.FPCompareGTFpscr); // A32 only. + var dlgSoftFloat32FPCompareLE = new SoftFloat32FPCompareLE(SoftFloat32.FPCompareLE); + var dlgSoftFloat32FPCompareLEFpscr = new SoftFloat32FPCompareLEFpscr(SoftFloat32.FPCompareLEFpscr); // A32 only. + var dlgSoftFloat32FPCompareLT = new SoftFloat32FPCompareLT(SoftFloat32.FPCompareLT); + var dlgSoftFloat32FPCompareLTFpscr = new SoftFloat32FPCompareLTFpscr(SoftFloat32.FPCompareLTFpscr); // A32 only. + var dlgSoftFloat32FPDiv = new SoftFloat32FPDiv(SoftFloat32.FPDiv); + var dlgSoftFloat32FPMax = new SoftFloat32FPMax(SoftFloat32.FPMax); + var dlgSoftFloat32FPMaxFpscr = new SoftFloat32FPMaxFpscr(SoftFloat32.FPMaxFpscr); // A32 only. + var dlgSoftFloat32FPMaxNum = new SoftFloat32FPMaxNum(SoftFloat32.FPMaxNum); + var dlgSoftFloat32FPMaxNumFpscr = new SoftFloat32FPMaxNumFpscr(SoftFloat32.FPMaxNumFpscr); // A32 only. + var dlgSoftFloat32FPMin = new SoftFloat32FPMin(SoftFloat32.FPMin); + var dlgSoftFloat32FPMinFpscr = new SoftFloat32FPMinFpscr(SoftFloat32.FPMinFpscr); // A32 only. + var dlgSoftFloat32FPMinNum = new SoftFloat32FPMinNum(SoftFloat32.FPMinNum); + var dlgSoftFloat32FPMinNumFpscr = new SoftFloat32FPMinNumFpscr(SoftFloat32.FPMinNumFpscr); // A32 only. + var dlgSoftFloat32FPMul = new SoftFloat32FPMul(SoftFloat32.FPMul); + var dlgSoftFloat32FPMulFpscr = new SoftFloat32FPMulFpscr(SoftFloat32.FPMulFpscr); // A32 only. + var dlgSoftFloat32FPMulAdd = new SoftFloat32FPMulAdd(SoftFloat32.FPMulAdd); + var dlgSoftFloat32FPMulAddFpscr = new SoftFloat32FPMulAddFpscr(SoftFloat32.FPMulAddFpscr); // A32 only. + var dlgSoftFloat32FPMulSub = new SoftFloat32FPMulSub(SoftFloat32.FPMulSub); + var dlgSoftFloat32FPMulSubFpscr = new SoftFloat32FPMulSubFpscr(SoftFloat32.FPMulSubFpscr); // A32 only. + var dlgSoftFloat32FPMulX = new SoftFloat32FPMulX(SoftFloat32.FPMulX); + var dlgSoftFloat32FPNegMulAdd = new SoftFloat32FPNegMulAdd(SoftFloat32.FPNegMulAdd); + var dlgSoftFloat32FPNegMulSub = new SoftFloat32FPNegMulSub(SoftFloat32.FPNegMulSub); + var dlgSoftFloat32FPRecipEstimate = new SoftFloat32FPRecipEstimate(SoftFloat32.FPRecipEstimate); + var dlgSoftFloat32FPRecipEstimateFpscr = new SoftFloat32FPRecipEstimateFpscr(SoftFloat32.FPRecipEstimateFpscr); // A32 only. + var dlgSoftFloat32FPRecipStep = new SoftFloat32FPRecipStep(SoftFloat32.FPRecipStep); // A32 only. + var dlgSoftFloat32FPRecipStepFused = new SoftFloat32FPRecipStepFused(SoftFloat32.FPRecipStepFused); + var dlgSoftFloat32FPRecpX = new SoftFloat32FPRecpX(SoftFloat32.FPRecpX); + var dlgSoftFloat32FPRSqrtEstimate = new SoftFloat32FPRSqrtEstimate(SoftFloat32.FPRSqrtEstimate); + var dlgSoftFloat32FPRSqrtEstimateFpscr = new SoftFloat32FPRSqrtEstimateFpscr(SoftFloat32.FPRSqrtEstimateFpscr); // A32 only. + var dlgSoftFloat32FPRSqrtStep = new SoftFloat32FPRSqrtStep(SoftFloat32.FPRSqrtStep); // A32 only. + var dlgSoftFloat32FPRSqrtStepFused = new SoftFloat32FPRSqrtStepFused(SoftFloat32.FPRSqrtStepFused); + var dlgSoftFloat32FPSqrt = new SoftFloat32FPSqrt(SoftFloat32.FPSqrt); + var dlgSoftFloat32FPSub = new SoftFloat32FPSub(SoftFloat32.FPSub); + + var dlgSoftFloat32_16FPConvert = new SoftFloat32_16FPConvert(SoftFloat32_16.FPConvert); + + var dlgSoftFloat64FPAdd = new SoftFloat64FPAdd(SoftFloat64.FPAdd); + var dlgSoftFloat64FPAddFpscr = new SoftFloat64FPAddFpscr(SoftFloat64.FPAddFpscr); // A32 only. + var dlgSoftFloat64FPCompare = new SoftFloat64FPCompare(SoftFloat64.FPCompare); + var dlgSoftFloat64FPCompareEQ = new SoftFloat64FPCompareEQ(SoftFloat64.FPCompareEQ); + var dlgSoftFloat64FPCompareEQFpscr = new SoftFloat64FPCompareEQFpscr(SoftFloat64.FPCompareEQFpscr); // A32 only. + var dlgSoftFloat64FPCompareGE = new SoftFloat64FPCompareGE(SoftFloat64.FPCompareGE); + var dlgSoftFloat64FPCompareGEFpscr = new SoftFloat64FPCompareGEFpscr(SoftFloat64.FPCompareGEFpscr); // A32 only. + var dlgSoftFloat64FPCompareGT = new SoftFloat64FPCompareGT(SoftFloat64.FPCompareGT); + var dlgSoftFloat64FPCompareGTFpscr = new SoftFloat64FPCompareGTFpscr(SoftFloat64.FPCompareGTFpscr); // A32 only. + var dlgSoftFloat64FPCompareLE = new SoftFloat64FPCompareLE(SoftFloat64.FPCompareLE); + var dlgSoftFloat64FPCompareLEFpscr = new SoftFloat64FPCompareLEFpscr(SoftFloat64.FPCompareLEFpscr); // A32 only. + var dlgSoftFloat64FPCompareLT = new SoftFloat64FPCompareLT(SoftFloat64.FPCompareLT); + var dlgSoftFloat64FPCompareLTFpscr = new SoftFloat64FPCompareLTFpscr(SoftFloat64.FPCompareLTFpscr); // A32 only. + var dlgSoftFloat64FPDiv = new SoftFloat64FPDiv(SoftFloat64.FPDiv); + var dlgSoftFloat64FPMax = new SoftFloat64FPMax(SoftFloat64.FPMax); + var dlgSoftFloat64FPMaxFpscr = new SoftFloat64FPMaxFpscr(SoftFloat64.FPMaxFpscr); // A32 only. + var dlgSoftFloat64FPMaxNum = new SoftFloat64FPMaxNum(SoftFloat64.FPMaxNum); + var dlgSoftFloat64FPMaxNumFpscr = new SoftFloat64FPMaxNumFpscr(SoftFloat64.FPMaxNumFpscr); // A32 only. + var dlgSoftFloat64FPMin = new SoftFloat64FPMin(SoftFloat64.FPMin); + var dlgSoftFloat64FPMinFpscr = new SoftFloat64FPMinFpscr(SoftFloat64.FPMinFpscr); // A32 only. + var dlgSoftFloat64FPMinNum = new SoftFloat64FPMinNum(SoftFloat64.FPMinNum); + var dlgSoftFloat64FPMinNumFpscr = new SoftFloat64FPMinNumFpscr(SoftFloat64.FPMinNumFpscr); // A32 only. + var dlgSoftFloat64FPMul = new SoftFloat64FPMul(SoftFloat64.FPMul); + var dlgSoftFloat64FPMulFpscr = new SoftFloat64FPMulFpscr(SoftFloat64.FPMulFpscr); // A32 only. + var dlgSoftFloat64FPMulAdd = new SoftFloat64FPMulAdd(SoftFloat64.FPMulAdd); + var dlgSoftFloat64FPMulAddFpscr = new SoftFloat64FPMulAddFpscr(SoftFloat64.FPMulAddFpscr); // A32 only. + var dlgSoftFloat64FPMulSub = new SoftFloat64FPMulSub(SoftFloat64.FPMulSub); + var dlgSoftFloat64FPMulSubFpscr = new SoftFloat64FPMulSubFpscr(SoftFloat64.FPMulSubFpscr); // A32 only. + var dlgSoftFloat64FPMulX = new SoftFloat64FPMulX(SoftFloat64.FPMulX); + var dlgSoftFloat64FPNegMulAdd = new SoftFloat64FPNegMulAdd(SoftFloat64.FPNegMulAdd); + var dlgSoftFloat64FPNegMulSub = new SoftFloat64FPNegMulSub(SoftFloat64.FPNegMulSub); + var dlgSoftFloat64FPRecipEstimate = new SoftFloat64FPRecipEstimate(SoftFloat64.FPRecipEstimate); + var dlgSoftFloat64FPRecipEstimateFpscr = new SoftFloat64FPRecipEstimateFpscr(SoftFloat64.FPRecipEstimateFpscr); // A32 only. + var dlgSoftFloat64FPRecipStep = new SoftFloat64FPRecipStep(SoftFloat64.FPRecipStep); // A32 only. + var dlgSoftFloat64FPRecipStepFused = new SoftFloat64FPRecipStepFused(SoftFloat64.FPRecipStepFused); + var dlgSoftFloat64FPRecpX = new SoftFloat64FPRecpX(SoftFloat64.FPRecpX); + var dlgSoftFloat64FPRSqrtEstimate = new SoftFloat64FPRSqrtEstimate(SoftFloat64.FPRSqrtEstimate); + var dlgSoftFloat64FPRSqrtEstimateFpscr = new SoftFloat64FPRSqrtEstimateFpscr(SoftFloat64.FPRSqrtEstimateFpscr); // A32 only. + var dlgSoftFloat64FPRSqrtStep = new SoftFloat64FPRSqrtStep(SoftFloat64.FPRSqrtStep); // A32 only. + var dlgSoftFloat64FPRSqrtStepFused = new SoftFloat64FPRSqrtStepFused(SoftFloat64.FPRSqrtStepFused); + var dlgSoftFloat64FPSqrt = new SoftFloat64FPSqrt(SoftFloat64.FPSqrt); + var dlgSoftFloat64FPSub = new SoftFloat64FPSub(SoftFloat64.FPSub); + + var dlgSoftFloat64_16FPConvert = new SoftFloat64_16FPConvert(SoftFloat64_16.FPConvert); + + SetDelegateInfo(dlgMathAbs, Marshal.GetFunctionPointerForDelegate(dlgMathAbs)); + SetDelegateInfo(dlgMathCeiling, Marshal.GetFunctionPointerForDelegate(dlgMathCeiling)); + SetDelegateInfo(dlgMathFloor, Marshal.GetFunctionPointerForDelegate(dlgMathFloor)); + SetDelegateInfo(dlgMathRound, Marshal.GetFunctionPointerForDelegate(dlgMathRound)); + SetDelegateInfo(dlgMathTruncate, Marshal.GetFunctionPointerForDelegate(dlgMathTruncate)); + + SetDelegateInfo(dlgMathFAbs, Marshal.GetFunctionPointerForDelegate(dlgMathFAbs)); + SetDelegateInfo(dlgMathFCeiling, Marshal.GetFunctionPointerForDelegate(dlgMathFCeiling)); + SetDelegateInfo(dlgMathFFloor, Marshal.GetFunctionPointerForDelegate(dlgMathFFloor)); + SetDelegateInfo(dlgMathFRound, Marshal.GetFunctionPointerForDelegate(dlgMathFRound)); + SetDelegateInfo(dlgMathFTruncate, Marshal.GetFunctionPointerForDelegate(dlgMathFTruncate)); + + SetDelegateInfo(dlgNativeInterfaceBreak, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceBreak)); + SetDelegateInfo(dlgNativeInterfaceCheckSynchronization, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceCheckSynchronization)); + SetDelegateInfo(dlgNativeInterfaceEnqueueForRejit, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceEnqueueForRejit)); + SetDelegateInfo(dlgNativeInterfaceGetCntfrqEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetCntfrqEl0)); + SetDelegateInfo(dlgNativeInterfaceGetCntpctEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetCntpctEl0)); + SetDelegateInfo(dlgNativeInterfaceGetCntvctEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetCntvctEl0)); + SetDelegateInfo(dlgNativeInterfaceGetCtrEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetCtrEl0)); + SetDelegateInfo(dlgNativeInterfaceGetDczidEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetDczidEl0)); + SetDelegateInfo(dlgNativeInterfaceGetFunctionAddress, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetFunctionAddress)); + SetDelegateInfo(dlgNativeInterfaceInvalidateCacheLine, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceInvalidateCacheLine)); + SetDelegateInfo(dlgNativeInterfaceReadByte, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadByte)); + SetDelegateInfo(dlgNativeInterfaceReadUInt16, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadUInt16)); + SetDelegateInfo(dlgNativeInterfaceReadUInt32, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadUInt32)); + SetDelegateInfo(dlgNativeInterfaceReadUInt64, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadUInt64)); + SetDelegateInfo(dlgNativeInterfaceReadVector128, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadVector128)); + SetDelegateInfo(dlgNativeInterfaceSignalMemoryTracking, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceSignalMemoryTracking)); + SetDelegateInfo(dlgNativeInterfaceSupervisorCall, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceSupervisorCall)); + SetDelegateInfo(dlgNativeInterfaceThrowInvalidMemoryAccess, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceThrowInvalidMemoryAccess)); + SetDelegateInfo(dlgNativeInterfaceUndefined, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceUndefined)); + SetDelegateInfo(dlgNativeInterfaceWriteByte, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteByte)); + SetDelegateInfo(dlgNativeInterfaceWriteUInt16, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteUInt16)); + SetDelegateInfo(dlgNativeInterfaceWriteUInt32, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteUInt32)); + SetDelegateInfo(dlgNativeInterfaceWriteUInt64, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteUInt64)); + SetDelegateInfo(dlgNativeInterfaceWriteVector128, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteVector128)); + + SetDelegateInfo(dlgSoftFallbackCountLeadingSigns, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCountLeadingSigns)); + SetDelegateInfo(dlgSoftFallbackCountLeadingZeros, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCountLeadingZeros)); + SetDelegateInfo(dlgSoftFallbackCrc32b, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32b)); + SetDelegateInfo(dlgSoftFallbackCrc32cb, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32cb)); + SetDelegateInfo(dlgSoftFallbackCrc32ch, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32ch)); + SetDelegateInfo(dlgSoftFallbackCrc32cw, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32cw)); + SetDelegateInfo(dlgSoftFallbackCrc32cx, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32cx)); + SetDelegateInfo(dlgSoftFallbackCrc32h, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32h)); + SetDelegateInfo(dlgSoftFallbackCrc32w, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32w)); + SetDelegateInfo(dlgSoftFallbackCrc32x, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32x)); + SetDelegateInfo(dlgSoftFallbackDecrypt, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackDecrypt)); + SetDelegateInfo(dlgSoftFallbackEncrypt, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackEncrypt)); + SetDelegateInfo(dlgSoftFallbackFixedRotate, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackFixedRotate)); + SetDelegateInfo(dlgSoftFallbackHashChoose, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashChoose)); + SetDelegateInfo(dlgSoftFallbackHashLower, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashLower)); + SetDelegateInfo(dlgSoftFallbackHashMajority, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashMajority)); + SetDelegateInfo(dlgSoftFallbackHashParity, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashParity)); + SetDelegateInfo(dlgSoftFallbackHashUpper, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashUpper)); + SetDelegateInfo(dlgSoftFallbackInverseMixColumns, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackInverseMixColumns)); + SetDelegateInfo(dlgSoftFallbackMixColumns, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackMixColumns)); + SetDelegateInfo(dlgSoftFallbackPolynomialMult64_128, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackPolynomialMult64_128)); + SetDelegateInfo(dlgSoftFallbackSatF32ToS32, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF32ToS32)); + SetDelegateInfo(dlgSoftFallbackSatF32ToS64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF32ToS64)); + SetDelegateInfo(dlgSoftFallbackSatF32ToU32, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF32ToU32)); + SetDelegateInfo(dlgSoftFallbackSatF32ToU64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF32ToU64)); + SetDelegateInfo(dlgSoftFallbackSatF64ToS32, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF64ToS32)); + SetDelegateInfo(dlgSoftFallbackSatF64ToS64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF64ToS64)); + SetDelegateInfo(dlgSoftFallbackSatF64ToU32, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF64ToU32)); + SetDelegateInfo(dlgSoftFallbackSatF64ToU64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF64ToU64)); + SetDelegateInfo(dlgSoftFallbackSha1SchedulePart1, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSha1SchedulePart1)); + SetDelegateInfo(dlgSoftFallbackSha1SchedulePart2, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSha1SchedulePart2)); + SetDelegateInfo(dlgSoftFallbackSha256SchedulePart1, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSha256SchedulePart1)); + SetDelegateInfo(dlgSoftFallbackSha256SchedulePart2, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSha256SchedulePart2)); + SetDelegateInfo(dlgSoftFallbackSignedShrImm64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSignedShrImm64)); + SetDelegateInfo(dlgSoftFallbackTbl1, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbl1)); + SetDelegateInfo(dlgSoftFallbackTbl2, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbl2)); + SetDelegateInfo(dlgSoftFallbackTbl3, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbl3)); + SetDelegateInfo(dlgSoftFallbackTbl4, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbl4)); + SetDelegateInfo(dlgSoftFallbackTbx1, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbx1)); + SetDelegateInfo(dlgSoftFallbackTbx2, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbx2)); + SetDelegateInfo(dlgSoftFallbackTbx3, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbx3)); + SetDelegateInfo(dlgSoftFallbackTbx4, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbx4)); + SetDelegateInfo(dlgSoftFallbackUnsignedShrImm64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackUnsignedShrImm64)); + + SetDelegateInfo(dlgSoftFloat16_32FPConvert, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat16_32FPConvert)); + SetDelegateInfo(dlgSoftFloat16_64FPConvert, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat16_64FPConvert)); + + SetDelegateInfo(dlgSoftFloat32FPAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPAdd)); + SetDelegateInfo(dlgSoftFloat32FPAddFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPAddFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompare, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompare)); + SetDelegateInfo(dlgSoftFloat32FPCompareEQ, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareEQ)); + SetDelegateInfo(dlgSoftFloat32FPCompareEQFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareEQFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompareGE, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareGE)); + SetDelegateInfo(dlgSoftFloat32FPCompareGEFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareGEFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompareGT, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareGT)); + SetDelegateInfo(dlgSoftFloat32FPCompareGTFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareGTFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompareLE, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareLE)); + SetDelegateInfo(dlgSoftFloat32FPCompareLEFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareLEFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompareLT, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareLT)); + SetDelegateInfo(dlgSoftFloat32FPCompareLTFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareLTFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPDiv, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPDiv)); + SetDelegateInfo(dlgSoftFloat32FPMax, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMax)); + SetDelegateInfo(dlgSoftFloat32FPMaxFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMaxFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMaxNum, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMaxNum)); + SetDelegateInfo(dlgSoftFloat32FPMaxNumFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMaxNumFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMin, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMin)); + SetDelegateInfo(dlgSoftFloat32FPMinFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMinFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMinNum, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMinNum)); + SetDelegateInfo(dlgSoftFloat32FPMinNumFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMinNumFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMul, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMul)); + SetDelegateInfo(dlgSoftFloat32FPMulFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMulAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulAdd)); + SetDelegateInfo(dlgSoftFloat32FPMulAddFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulAddFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMulSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulSub)); + SetDelegateInfo(dlgSoftFloat32FPMulSubFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulSubFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMulX, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulX)); + SetDelegateInfo(dlgSoftFloat32FPNegMulAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPNegMulAdd)); + SetDelegateInfo(dlgSoftFloat32FPNegMulSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPNegMulSub)); + SetDelegateInfo(dlgSoftFloat32FPRecipEstimate, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecipEstimate)); + SetDelegateInfo(dlgSoftFloat32FPRecipEstimateFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecipEstimateFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPRecipStep, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecipStep)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPRecipStepFused, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecipStepFused)); + SetDelegateInfo(dlgSoftFloat32FPRecpX, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecpX)); + SetDelegateInfo(dlgSoftFloat32FPRSqrtEstimate, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRSqrtEstimate)); + SetDelegateInfo(dlgSoftFloat32FPRSqrtEstimateFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRSqrtEstimateFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPRSqrtStep, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRSqrtStep)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPRSqrtStepFused, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRSqrtStepFused)); + SetDelegateInfo(dlgSoftFloat32FPSqrt, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPSqrt)); + SetDelegateInfo(dlgSoftFloat32FPSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPSub)); + + SetDelegateInfo(dlgSoftFloat32_16FPConvert, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32_16FPConvert)); + + SetDelegateInfo(dlgSoftFloat64FPAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPAdd)); + SetDelegateInfo(dlgSoftFloat64FPAddFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPAddFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompare, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompare)); + SetDelegateInfo(dlgSoftFloat64FPCompareEQ, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareEQ)); + SetDelegateInfo(dlgSoftFloat64FPCompareEQFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareEQFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompareGE, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareGE)); + SetDelegateInfo(dlgSoftFloat64FPCompareGEFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareGEFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompareGT, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareGT)); + SetDelegateInfo(dlgSoftFloat64FPCompareGTFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareGTFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompareLE, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareLE)); + SetDelegateInfo(dlgSoftFloat64FPCompareLEFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareLEFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompareLT, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareLT)); + SetDelegateInfo(dlgSoftFloat64FPCompareLTFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareLTFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPDiv, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPDiv)); + SetDelegateInfo(dlgSoftFloat64FPMax, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMax)); + SetDelegateInfo(dlgSoftFloat64FPMaxFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMaxFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMaxNum, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMaxNum)); + SetDelegateInfo(dlgSoftFloat64FPMaxNumFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMaxNumFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMin, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMin)); + SetDelegateInfo(dlgSoftFloat64FPMinFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMinFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMinNum, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMinNum)); + SetDelegateInfo(dlgSoftFloat64FPMinNumFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMinNumFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMul, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMul)); + SetDelegateInfo(dlgSoftFloat64FPMulFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMulAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulAdd)); + SetDelegateInfo(dlgSoftFloat64FPMulAddFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulAddFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMulSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulSub)); + SetDelegateInfo(dlgSoftFloat64FPMulSubFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulSubFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMulX, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulX)); + SetDelegateInfo(dlgSoftFloat64FPNegMulAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPNegMulAdd)); + SetDelegateInfo(dlgSoftFloat64FPNegMulSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPNegMulSub)); + SetDelegateInfo(dlgSoftFloat64FPRecipEstimate, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecipEstimate)); + SetDelegateInfo(dlgSoftFloat64FPRecipEstimateFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecipEstimateFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPRecipStep, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecipStep)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPRecipStepFused, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecipStepFused)); + SetDelegateInfo(dlgSoftFloat64FPRecpX, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecpX)); + SetDelegateInfo(dlgSoftFloat64FPRSqrtEstimate, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRSqrtEstimate)); + SetDelegateInfo(dlgSoftFloat64FPRSqrtEstimateFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRSqrtEstimateFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPRSqrtStep, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRSqrtStep)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPRSqrtStepFused, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRSqrtStepFused)); + SetDelegateInfo(dlgSoftFloat64FPSqrt, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPSqrt)); + SetDelegateInfo(dlgSoftFloat64FPSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPSub)); + + SetDelegateInfo(dlgSoftFloat64_16FPConvert, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64_16FPConvert)); + } + + private delegate double MathAbs(double value); + private delegate double MathCeiling(double a); + private delegate double MathFloor(double d); + private delegate double MathRound(double value, MidpointRounding mode); + private delegate double MathTruncate(double d); + + private delegate float MathFAbs(float x); + private delegate float MathFCeiling(float x); + private delegate float MathFFloor(float x); + private delegate float MathFRound(float x, MidpointRounding mode); + private delegate float MathFTruncate(float x); + + private delegate void NativeInterfaceBreak(ulong address, int imm); + private delegate bool NativeInterfaceCheckSynchronization(); + private delegate void NativeInterfaceEnqueueForRejit(ulong address); + private delegate ulong NativeInterfaceGetCntfrqEl0(); + private delegate ulong NativeInterfaceGetCntpctEl0(); + private delegate ulong NativeInterfaceGetCntvctEl0(); + private delegate ulong NativeInterfaceGetCtrEl0(); + private delegate ulong NativeInterfaceGetDczidEl0(); + private delegate ulong NativeInterfaceGetFunctionAddress(ulong address); + private delegate void NativeInterfaceInvalidateCacheLine(ulong address); + private delegate byte NativeInterfaceReadByte(ulong address); + private delegate ushort NativeInterfaceReadUInt16(ulong address); + private delegate uint NativeInterfaceReadUInt32(ulong address); + private delegate ulong NativeInterfaceReadUInt64(ulong address); + private delegate V128 NativeInterfaceReadVector128(ulong address); + private delegate void NativeInterfaceSignalMemoryTracking(ulong address, ulong size, bool write); + private delegate void NativeInterfaceSupervisorCall(ulong address, int imm); + private delegate void NativeInterfaceThrowInvalidMemoryAccess(ulong address); + private delegate void NativeInterfaceUndefined(ulong address, int opCode); + private delegate void NativeInterfaceWriteByte(ulong address, byte value); + private delegate void NativeInterfaceWriteUInt16(ulong address, ushort value); + private delegate void NativeInterfaceWriteUInt32(ulong address, uint value); + private delegate void NativeInterfaceWriteUInt64(ulong address, ulong value); + private delegate void NativeInterfaceWriteVector128(ulong address, V128 value); + + private delegate ulong SoftFallbackCountLeadingSigns(ulong value, int size); + private delegate ulong SoftFallbackCountLeadingZeros(ulong value, int size); + private delegate uint SoftFallbackCrc32b(uint crc, byte value); + private delegate uint SoftFallbackCrc32cb(uint crc, byte value); + private delegate uint SoftFallbackCrc32ch(uint crc, ushort value); + private delegate uint SoftFallbackCrc32cw(uint crc, uint value); + private delegate uint SoftFallbackCrc32cx(uint crc, ulong value); + private delegate uint SoftFallbackCrc32h(uint crc, ushort value); + private delegate uint SoftFallbackCrc32w(uint crc, uint value); + private delegate uint SoftFallbackCrc32x(uint crc, ulong value); + private delegate V128 SoftFallbackDecrypt(V128 value, V128 roundKey); + private delegate V128 SoftFallbackEncrypt(V128 value, V128 roundKey); + private delegate uint SoftFallbackFixedRotate(uint hash_e); + private delegate V128 SoftFallbackHashChoose(V128 hash_abcd, uint hash_e, V128 wk); + private delegate V128 SoftFallbackHashLower(V128 hash_abcd, V128 hash_efgh, V128 wk); + private delegate V128 SoftFallbackHashMajority(V128 hash_abcd, uint hash_e, V128 wk); + private delegate V128 SoftFallbackHashParity(V128 hash_abcd, uint hash_e, V128 wk); + private delegate V128 SoftFallbackHashUpper(V128 hash_abcd, V128 hash_efgh, V128 wk); + private delegate V128 SoftFallbackInverseMixColumns(V128 value); + private delegate V128 SoftFallbackMixColumns(V128 value); + private delegate V128 SoftFallbackPolynomialMult64_128(ulong op1, ulong op2); + private delegate int SoftFallbackSatF32ToS32(float value); + private delegate long SoftFallbackSatF32ToS64(float value); + private delegate uint SoftFallbackSatF32ToU32(float value); + private delegate ulong SoftFallbackSatF32ToU64(float value); + private delegate int SoftFallbackSatF64ToS32(double value); + private delegate long SoftFallbackSatF64ToS64(double value); + private delegate uint SoftFallbackSatF64ToU32(double value); + private delegate ulong SoftFallbackSatF64ToU64(double value); + private delegate V128 SoftFallbackSha1SchedulePart1(V128 w0_3, V128 w4_7, V128 w8_11); + private delegate V128 SoftFallbackSha1SchedulePart2(V128 tw0_3, V128 w12_15); + private delegate V128 SoftFallbackSha256SchedulePart1(V128 w0_3, V128 w4_7); + private delegate V128 SoftFallbackSha256SchedulePart2(V128 w0_3, V128 w8_11, V128 w12_15); + private delegate long SoftFallbackSignedShrImm64(long value, long roundConst, int shift); + private delegate V128 SoftFallbackTbl1(V128 vector, int bytes, V128 tb0); + private delegate V128 SoftFallbackTbl2(V128 vector, int bytes, V128 tb0, V128 tb1); + private delegate V128 SoftFallbackTbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2); + private delegate V128 SoftFallbackTbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3); + private delegate V128 SoftFallbackTbx1(V128 dest, V128 vector, int bytes, V128 tb0); + private delegate V128 SoftFallbackTbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1); + private delegate V128 SoftFallbackTbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2); + private delegate V128 SoftFallbackTbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3); + private delegate ulong SoftFallbackUnsignedShrImm64(ulong value, long roundConst, int shift); + + private delegate float SoftFloat16_32FPConvert(ushort valueBits); + + private delegate double SoftFloat16_64FPConvert(ushort valueBits); + + private delegate float SoftFloat32FPAdd(float value1, float value2); + private delegate float SoftFloat32FPAddFpscr(float value1, float value2, bool standardFpscr); + private delegate int SoftFloat32FPCompare(float value1, float value2, bool signalNaNs); + private delegate float SoftFloat32FPCompareEQ(float value1, float value2); + private delegate float SoftFloat32FPCompareEQFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPCompareGE(float value1, float value2); + private delegate float SoftFloat32FPCompareGEFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPCompareGT(float value1, float value2); + private delegate float SoftFloat32FPCompareGTFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPCompareLE(float value1, float value2); + private delegate float SoftFloat32FPCompareLEFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPCompareLT(float value1, float value2); + private delegate float SoftFloat32FPCompareLTFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPDiv(float value1, float value2); + private delegate float SoftFloat32FPMax(float value1, float value2); + private delegate float SoftFloat32FPMaxFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMaxNum(float value1, float value2); + private delegate float SoftFloat32FPMaxNumFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMin(float value1, float value2); + private delegate float SoftFloat32FPMinFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMinNum(float value1, float value2); + private delegate float SoftFloat32FPMinNumFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMul(float value1, float value2); + private delegate float SoftFloat32FPMulFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMulAdd(float valueA, float value1, float value2); + private delegate float SoftFloat32FPMulAddFpscr(float valueA, float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMulSub(float valueA, float value1, float value2); + private delegate float SoftFloat32FPMulSubFpscr(float valueA, float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMulX(float value1, float value2); + private delegate float SoftFloat32FPNegMulAdd(float valueA, float value1, float value2); + private delegate float SoftFloat32FPNegMulSub(float valueA, float value1, float value2); + private delegate float SoftFloat32FPRecipEstimate(float value); + private delegate float SoftFloat32FPRecipEstimateFpscr(float value, bool standardFpscr); + private delegate float SoftFloat32FPRecipStep(float value1, float value2); + private delegate float SoftFloat32FPRecipStepFused(float value1, float value2); + private delegate float SoftFloat32FPRecpX(float value); + private delegate float SoftFloat32FPRSqrtEstimate(float value); + private delegate float SoftFloat32FPRSqrtEstimateFpscr(float value, bool standardFpscr); + private delegate float SoftFloat32FPRSqrtStep(float value1, float value2); + private delegate float SoftFloat32FPRSqrtStepFused(float value1, float value2); + private delegate float SoftFloat32FPSqrt(float value); + private delegate float SoftFloat32FPSub(float value1, float value2); + + private delegate ushort SoftFloat32_16FPConvert(float value); + + private delegate double SoftFloat64FPAdd(double value1, double value2); + private delegate double SoftFloat64FPAddFpscr(double value1, double value2, bool standardFpscr); + private delegate int SoftFloat64FPCompare(double value1, double value2, bool signalNaNs); + private delegate double SoftFloat64FPCompareEQ(double value1, double value2); + private delegate double SoftFloat64FPCompareEQFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPCompareGE(double value1, double value2); + private delegate double SoftFloat64FPCompareGEFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPCompareGT(double value1, double value2); + private delegate double SoftFloat64FPCompareGTFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPCompareLE(double value1, double value2); + private delegate double SoftFloat64FPCompareLEFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPCompareLT(double value1, double value2); + private delegate double SoftFloat64FPCompareLTFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPDiv(double value1, double value2); + private delegate double SoftFloat64FPMax(double value1, double value2); + private delegate double SoftFloat64FPMaxFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMaxNum(double value1, double value2); + private delegate double SoftFloat64FPMaxNumFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMin(double value1, double value2); + private delegate double SoftFloat64FPMinFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMinNum(double value1, double value2); + private delegate double SoftFloat64FPMinNumFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMul(double value1, double value2); + private delegate double SoftFloat64FPMulFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMulAdd(double valueA, double value1, double value2); + private delegate double SoftFloat64FPMulAddFpscr(double valueA, double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMulSub(double valueA, double value1, double value2); + private delegate double SoftFloat64FPMulSubFpscr(double valueA, double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMulX(double value1, double value2); + private delegate double SoftFloat64FPNegMulAdd(double valueA, double value1, double value2); + private delegate double SoftFloat64FPNegMulSub(double valueA, double value1, double value2); + private delegate double SoftFloat64FPRecipEstimate(double value); + private delegate double SoftFloat64FPRecipEstimateFpscr(double value, bool standardFpscr); + private delegate double SoftFloat64FPRecipStep(double value1, double value2); + private delegate double SoftFloat64FPRecipStepFused(double value1, double value2); + private delegate double SoftFloat64FPRecpX(double value); + private delegate double SoftFloat64FPRSqrtEstimate(double value); + private delegate double SoftFloat64FPRSqrtEstimateFpscr(double value, bool standardFpscr); + private delegate double SoftFloat64FPRSqrtStep(double value1, double value2); + private delegate double SoftFloat64FPRSqrtStepFused(double value1, double value2); + private delegate double SoftFloat64FPSqrt(double value); + private delegate double SoftFloat64FPSub(double value1, double value2); + + private delegate ushort SoftFloat64_16FPConvert(double value); + } +} diff --git a/src/ARMeilleure/Translation/DispatcherFunction.cs b/src/ARMeilleure/Translation/DispatcherFunction.cs new file mode 100644 index 000000000..649fa0f50 --- /dev/null +++ b/src/ARMeilleure/Translation/DispatcherFunction.cs @@ -0,0 +1,7 @@ +using System; + +namespace ARMeilleure.Translation +{ + delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress); + delegate ulong WrapperFunction(IntPtr nativeContext, ulong startAddress); +} diff --git a/src/ARMeilleure/Translation/Dominance.cs b/src/ARMeilleure/Translation/Dominance.cs new file mode 100644 index 000000000..e2185bd85 --- /dev/null +++ b/src/ARMeilleure/Translation/Dominance.cs @@ -0,0 +1,95 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Diagnostics; + +namespace ARMeilleure.Translation +{ + static class Dominance + { + // Those methods are an implementation of the algorithms on "A Simple, Fast Dominance Algorithm". + // https://www.cs.rice.edu/~keith/EMBED/dom.pdf + public static void FindDominators(ControlFlowGraph cfg) + { + BasicBlock Intersect(BasicBlock block1, BasicBlock block2) + { + while (block1 != block2) + { + while (cfg.PostOrderMap[block1.Index] < cfg.PostOrderMap[block2.Index]) + { + block1 = block1.ImmediateDominator; + } + + while (cfg.PostOrderMap[block2.Index] < cfg.PostOrderMap[block1.Index]) + { + block2 = block2.ImmediateDominator; + } + } + + return block1; + } + + cfg.Entry.ImmediateDominator = cfg.Entry; + + Debug.Assert(cfg.Entry == cfg.PostOrderBlocks[^1]); + + bool modified; + + do + { + modified = false; + + for (int blkIndex = cfg.PostOrderBlocks.Length - 2; blkIndex >= 0; blkIndex--) + { + BasicBlock block = cfg.PostOrderBlocks[blkIndex]; + + BasicBlock newIDom = null; + + foreach (BasicBlock predecessor in block.Predecessors) + { + if (predecessor.ImmediateDominator != null) + { + if (newIDom != null) + { + newIDom = Intersect(predecessor, newIDom); + } + else + { + newIDom = predecessor; + } + } + } + + if (block.ImmediateDominator != newIDom) + { + block.ImmediateDominator = newIDom; + + modified = true; + } + } + } + while (modified); + } + + public static void FindDominanceFrontiers(ControlFlowGraph cfg) + { + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + if (block.Predecessors.Count < 2) + { + continue; + } + + for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++) + { + BasicBlock current = block.Predecessors[pBlkIndex]; + + while (current != block.ImmediateDominator) + { + current.DominanceFrontiers.Add(block); + + current = current.ImmediateDominator; + } + } + } + } + } +} diff --git a/src/ARMeilleure/Translation/EmitterContext.cs b/src/ARMeilleure/Translation/EmitterContext.cs new file mode 100644 index 000000000..88bfe1335 --- /dev/null +++ b/src/ARMeilleure/Translation/EmitterContext.cs @@ -0,0 +1,680 @@ +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + class EmitterContext + { + private int _localsCount; + + private readonly Dictionary _irLabels; + private readonly IntrusiveList _irBlocks; + + private BasicBlock _irBlock; + private BasicBlock _ifBlock; + + private bool _needsNewBlock; + private BasicBlockFrequency _nextBlockFreq; + + public EmitterContext() + { + _localsCount = 0; + + _irLabels = new Dictionary(); + _irBlocks = new IntrusiveList(); + + _needsNewBlock = true; + _nextBlockFreq = BasicBlockFrequency.Default; + } + + public Operand AllocateLocal(OperandType type) + { + Operand local = Local(type); + + local.NumberLocal(++_localsCount); + + return local; + } + + public Operand Add(Operand op1, Operand op2) + { + return Add(Instruction.Add, Local(op1.Type), op1, op2); + } + + public Operand BitwiseAnd(Operand op1, Operand op2) + { + return Add(Instruction.BitwiseAnd, Local(op1.Type), op1, op2); + } + + public Operand BitwiseExclusiveOr(Operand op1, Operand op2) + { + return Add(Instruction.BitwiseExclusiveOr, Local(op1.Type), op1, op2); + } + + public Operand BitwiseNot(Operand op1) + { + return Add(Instruction.BitwiseNot, Local(op1.Type), op1); + } + + public Operand BitwiseOr(Operand op1, Operand op2) + { + return Add(Instruction.BitwiseOr, Local(op1.Type), op1, op2); + } + + public void Branch(Operand label) + { + NewNextBlockIfNeeded(); + + BranchToLabel(label, uncond: true, BasicBlockFrequency.Default); + } + + public void BranchIf(Operand label, Operand op1, Operand op2, Comparison comp, BasicBlockFrequency falseFreq = default) + { + Add(Instruction.BranchIf, default, op1, op2, Const((int)comp)); + + BranchToLabel(label, uncond: false, falseFreq); + } + + public void BranchIfFalse(Operand label, Operand op1, BasicBlockFrequency falseFreq = default) + { + BranchIf(label, op1, Const(op1.Type, 0), Comparison.Equal, falseFreq); + } + + public void BranchIfTrue(Operand label, Operand op1, BasicBlockFrequency falseFreq = default) + { + BranchIf(label, op1, Const(op1.Type, 0), Comparison.NotEqual, falseFreq); + } + + public Operand ByteSwap(Operand op1) + { + return Add(Instruction.ByteSwap, Local(op1.Type), op1); + } + + public virtual Operand Call(MethodInfo info, params Operand[] callArgs) + { + IntPtr funcPtr = Delegates.GetDelegateFuncPtr(info); + + OperandType returnType = GetOperandType(info.ReturnType); + + Symbols.Add((ulong)funcPtr.ToInt64(), info.Name); + + return Call(Const(funcPtr.ToInt64()), returnType, callArgs); + } + + protected static OperandType GetOperandType(Type type) + { + if (type == typeof(bool) || type == typeof(byte) || + type == typeof(char) || type == typeof(short) || + type == typeof(int) || type == typeof(sbyte) || + type == typeof(ushort) || type == typeof(uint)) + { + return OperandType.I32; + } + else if (type == typeof(long) || type == typeof(ulong)) + { + return OperandType.I64; + } + else if (type == typeof(double)) + { + return OperandType.FP64; + } + else if (type == typeof(float)) + { + return OperandType.FP32; + } + else if (type == typeof(V128)) + { + return OperandType.V128; + } + else if (type == typeof(void)) + { + return OperandType.None; + } + else + { + throw new ArgumentException($"Invalid type \"{type.Name}\"."); + } + } + + public Operand Call(Operand address, OperandType returnType, params Operand[] callArgs) + { + Operand[] args = new Operand[callArgs.Length + 1]; + + args[0] = address; + + Array.Copy(callArgs, 0, args, 1, callArgs.Length); + + if (returnType != OperandType.None) + { + return Add(Instruction.Call, Local(returnType), args); + } + else + { + return Add(Instruction.Call, default, args); + } + } + + public void Tailcall(Operand address, params Operand[] callArgs) + { + Operand[] args = new Operand[callArgs.Length + 1]; + + args[0] = address; + + Array.Copy(callArgs, 0, args, 1, callArgs.Length); + + Add(Instruction.Tailcall, default, args); + + _needsNewBlock = true; + } + + public Operand CompareAndSwap(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap, Local(desired.Type), address, expected, desired); + } + + public Operand CompareAndSwap16(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap16, Local(OperandType.I32), address, expected, desired); + } + + public Operand CompareAndSwap8(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap8, Local(OperandType.I32), address, expected, desired); + } + + public Operand ConditionalSelect(Operand op1, Operand op2, Operand op3) + { + return Add(Instruction.ConditionalSelect, Local(op2.Type), op1, op2, op3); + } + + public Operand ConvertI64ToI32(Operand op1) + { + if (op1.Type != OperandType.I64) + { + throw new ArgumentException($"Invalid operand type \"{op1.Type}\"."); + } + + return Add(Instruction.ConvertI64ToI32, Local(OperandType.I32), op1); + } + + public Operand ConvertToFP(OperandType type, Operand op1) + { + return Add(Instruction.ConvertToFP, Local(type), op1); + } + + public Operand ConvertToFPUI(OperandType type, Operand op1) + { + return Add(Instruction.ConvertToFPUI, Local(type), op1); + } + + public Operand Copy(Operand op1) + { + return Add(Instruction.Copy, Local(op1.Type), op1); + } + + public Operand Copy(Operand dest, Operand op1) + { + if (dest.Kind != OperandKind.Register && + (dest.Kind != OperandKind.LocalVariable || dest.GetLocalNumber() == 0)) + { + throw new ArgumentException($"Destination operand must be a Register or a numbered LocalVariable."); + } + + return Add(Instruction.Copy, dest, op1); + } + + public Operand CountLeadingZeros(Operand op1) + { + return Add(Instruction.CountLeadingZeros, Local(op1.Type), op1); + } + + public Operand Divide(Operand op1, Operand op2) + { + return Add(Instruction.Divide, Local(op1.Type), op1, op2); + } + + public Operand DivideUI(Operand op1, Operand op2) + { + return Add(Instruction.DivideUI, Local(op1.Type), op1, op2); + } + + public Operand ICompare(Operand op1, Operand op2, Comparison comp) + { + return Add(Instruction.Compare, Local(OperandType.I32), op1, op2, Const((int)comp)); + } + + public Operand ICompareEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.Equal); + } + + public Operand ICompareGreater(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.Greater); + } + + public Operand ICompareGreaterOrEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.GreaterOrEqual); + } + + public Operand ICompareGreaterOrEqualUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.GreaterOrEqualUI); + } + + public Operand ICompareGreaterUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.GreaterUI); + } + + public Operand ICompareLess(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.Less); + } + + public Operand ICompareLessOrEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.LessOrEqual); + } + + public Operand ICompareLessOrEqualUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.LessOrEqualUI); + } + + public Operand ICompareLessUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.LessUI); + } + + public Operand ICompareNotEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.NotEqual); + } + + public Operand Load(OperandType type, Operand address) + { + return Add(Instruction.Load, Local(type), address); + } + + public Operand Load16(Operand address) + { + return Add(Instruction.Load16, Local(OperandType.I32), address); + } + + public Operand Load8(Operand address) + { + return Add(Instruction.Load8, Local(OperandType.I32), address); + } + + public Operand LoadArgument(OperandType type, int index) + { + return Add(Instruction.LoadArgument, Local(type), Const(index)); + } + + public void LoadFromContext() + { + _needsNewBlock = true; + + Add(Instruction.LoadFromContext); + } + + public void MemoryBarrier() + { + Add(Instruction.MemoryBarrier); + } + + public Operand Multiply(Operand op1, Operand op2) + { + return Add(Instruction.Multiply, Local(op1.Type), op1, op2); + } + + public Operand Multiply64HighSI(Operand op1, Operand op2) + { + return Add(Instruction.Multiply64HighSI, Local(OperandType.I64), op1, op2); + } + + public Operand Multiply64HighUI(Operand op1, Operand op2) + { + return Add(Instruction.Multiply64HighUI, Local(OperandType.I64), op1, op2); + } + + public Operand Negate(Operand op1) + { + return Add(Instruction.Negate, Local(op1.Type), op1); + } + + public void Return() + { + Add(Instruction.Return); + + _needsNewBlock = true; + } + + public void Return(Operand op1) + { + Add(Instruction.Return, default, op1); + + _needsNewBlock = true; + } + + public Operand RotateRight(Operand op1, Operand op2) + { + return Add(Instruction.RotateRight, Local(op1.Type), op1, op2); + } + + public Operand ShiftLeft(Operand op1, Operand op2) + { + return Add(Instruction.ShiftLeft, Local(op1.Type), op1, op2); + } + + public Operand ShiftRightSI(Operand op1, Operand op2) + { + return Add(Instruction.ShiftRightSI, Local(op1.Type), op1, op2); + } + + public Operand ShiftRightUI(Operand op1, Operand op2) + { + return Add(Instruction.ShiftRightUI, Local(op1.Type), op1, op2); + } + + public Operand SignExtend16(OperandType type, Operand op1) + { + return Add(Instruction.SignExtend16, Local(type), op1); + } + + public Operand SignExtend32(OperandType type, Operand op1) + { + return Add(Instruction.SignExtend32, Local(type), op1); + } + + public Operand SignExtend8(OperandType type, Operand op1) + { + return Add(Instruction.SignExtend8, Local(type), op1); + } + + public void Store(Operand address, Operand value) + { + Add(Instruction.Store, default, address, value); + } + + public void Store16(Operand address, Operand value) + { + Add(Instruction.Store16, default, address, value); + } + + public void Store8(Operand address, Operand value) + { + Add(Instruction.Store8, default, address, value); + } + + public void StoreToContext() + { + Add(Instruction.StoreToContext); + + _needsNewBlock = true; + } + + public Operand Subtract(Operand op1, Operand op2) + { + return Add(Instruction.Subtract, Local(op1.Type), op1, op2); + } + + public Operand VectorCreateScalar(Operand value) + { + return Add(Instruction.VectorCreateScalar, Local(OperandType.V128), value); + } + + public Operand VectorExtract(OperandType type, Operand vector, int index) + { + return Add(Instruction.VectorExtract, Local(type), vector, Const(index)); + } + + public Operand VectorExtract16(Operand vector, int index) + { + return Add(Instruction.VectorExtract16, Local(OperandType.I32), vector, Const(index)); + } + + public Operand VectorExtract8(Operand vector, int index) + { + return Add(Instruction.VectorExtract8, Local(OperandType.I32), vector, Const(index)); + } + + public Operand VectorInsert(Operand vector, Operand value, int index) + { + return Add(Instruction.VectorInsert, Local(OperandType.V128), vector, value, Const(index)); + } + + public Operand VectorInsert16(Operand vector, Operand value, int index) + { + return Add(Instruction.VectorInsert16, Local(OperandType.V128), vector, value, Const(index)); + } + + public Operand VectorInsert8(Operand vector, Operand value, int index) + { + return Add(Instruction.VectorInsert8, Local(OperandType.V128), vector, value, Const(index)); + } + + public Operand VectorOne() + { + return Add(Instruction.VectorOne, Local(OperandType.V128)); + } + + public Operand VectorZero() + { + return Add(Instruction.VectorZero, Local(OperandType.V128)); + } + + public Operand VectorZeroUpper64(Operand vector) + { + return Add(Instruction.VectorZeroUpper64, Local(OperandType.V128), vector); + } + + public Operand VectorZeroUpper96(Operand vector) + { + return Add(Instruction.VectorZeroUpper96, Local(OperandType.V128), vector); + } + + public Operand ZeroExtend16(OperandType type, Operand op1) + { + return Add(Instruction.ZeroExtend16, Local(type), op1); + } + + public Operand ZeroExtend32(OperandType type, Operand op1) + { + return Add(Instruction.ZeroExtend32, Local(type), op1); + } + + public Operand ZeroExtend8(OperandType type, Operand op1) + { + return Add(Instruction.ZeroExtend8, Local(type), op1); + } + + private void NewNextBlockIfNeeded() + { + if (_needsNewBlock) + { + NewNextBlock(); + } + } + + private Operand Add(Instruction inst, Operand dest = default) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand[] sources) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, sources); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand source0) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, source0); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand source0, Operand source1) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, source0, source1); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand source0, Operand source1, Operand source2) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, source0, source1, source2); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + public Operand AddIntrinsic(Intrinsic intrin, params Operand[] args) + { + return Add(intrin, Local(OperandType.V128), args); + } + + public Operand AddIntrinsicInt(Intrinsic intrin, params Operand[] args) + { + return Add(intrin, Local(OperandType.I32), args); + } + + public Operand AddIntrinsicLong(Intrinsic intrin, params Operand[] args) + { + return Add(intrin, Local(OperandType.I64), args); + } + + public void AddIntrinsicNoRet(Intrinsic intrin, params Operand[] args) + { + Add(intrin, default, args); + } + + private Operand Add(Intrinsic intrin, Operand dest, params Operand[] sources) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(intrin, dest, sources); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private void BranchToLabel(Operand label, bool uncond, BasicBlockFrequency nextFreq) + { + if (!_irLabels.TryGetValue(label, out BasicBlock branchBlock)) + { + branchBlock = new BasicBlock(); + + _irLabels.Add(label, branchBlock); + } + + if (uncond) + { + _irBlock.AddSuccessor(branchBlock); + } + else + { + // Defer registration of successor to _irBlock so that the order of successors is correct. + _ifBlock = branchBlock; + } + + _needsNewBlock = true; + _nextBlockFreq = nextFreq; + } + + public void MarkLabel(Operand label, BasicBlockFrequency nextFreq = default) + { + _nextBlockFreq = nextFreq; + + if (_irLabels.TryGetValue(label, out BasicBlock nextBlock)) + { + nextBlock.Index = _irBlocks.Count; + + _irBlocks.AddLast(nextBlock); + + NextBlock(nextBlock); + } + else + { + NewNextBlock(); + + _irLabels.Add(label, _irBlock); + } + } + + private void NewNextBlock() + { + BasicBlock block = new(_irBlocks.Count); + + _irBlocks.AddLast(block); + + NextBlock(block); + } + + private void NextBlock(BasicBlock nextBlock) + { + if (_irBlock?.SuccessorsCount == 0 && !EndsWithUnconditional(_irBlock)) + { + _irBlock.AddSuccessor(nextBlock); + + if (_ifBlock != null) + { + _irBlock.AddSuccessor(_ifBlock); + + _ifBlock = null; + } + } + + _irBlock = nextBlock; + _irBlock.Frequency = _nextBlockFreq; + + _needsNewBlock = false; + _nextBlockFreq = BasicBlockFrequency.Default; + } + + private static bool EndsWithUnconditional(BasicBlock block) + { + Operation last = block.Operations.Last; + + return last != default && + (last.Instruction == Instruction.Return || + last.Instruction == Instruction.Tailcall); + } + + public ControlFlowGraph GetControlFlowGraph() + { + return new ControlFlowGraph(_irBlocks.First, _irBlocks, _localsCount); + } + } +} diff --git a/src/ARMeilleure/Translation/GuestFunction.cs b/src/ARMeilleure/Translation/GuestFunction.cs new file mode 100644 index 000000000..6414d6bd0 --- /dev/null +++ b/src/ARMeilleure/Translation/GuestFunction.cs @@ -0,0 +1,6 @@ +using System; + +namespace ARMeilleure.Translation +{ + delegate ulong GuestFunction(IntPtr nativeContextPtr); +} diff --git a/src/ARMeilleure/Translation/IntervalTree.cs b/src/ARMeilleure/Translation/IntervalTree.cs new file mode 100644 index 000000000..da29d6a68 --- /dev/null +++ b/src/ARMeilleure/Translation/IntervalTree.cs @@ -0,0 +1,745 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Translation +{ + /// + /// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges. + /// + /// Key + /// Value + class IntervalTree where TK : IComparable + { + private const int ArrayGrowthSize = 32; + + private const bool Black = true; + private const bool Red = false; + private IntervalTreeNode _root = null; + private int _count = 0; + + public int Count => _count; + + #region Public Methods + + /// + /// Gets the values of the interval whose key is . + /// + /// Key of the node value to get + /// Value with the given + /// True if the key is on the dictionary, false otherwise + public bool TryGet(TK key, out TV value) + { + IntervalTreeNode node = GetNode(key); + + if (node == null) + { + value = default; + return false; + } + + value = node.Value; + return true; + } + + /// + /// Returns the start addresses of the intervals whose start and end keys overlap the given range. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Index to start writing results into the array. Defaults to 0 + /// Number of intervals found + public int Get(TK start, TK end, ref TK[] overlaps, int overlapCount = 0) + { + GetKeys(_root, start, end, ref overlaps, ref overlapCount); + + return overlapCount; + } + + /// + /// Adds a new interval into the tree whose start is , end is and value is . + /// + /// Start of the range to add + /// End of the range to insert + /// Value to add + /// Optional factory used to create a new value if is already on the tree + /// is null + /// True if the value was added, false if the start key was already in the dictionary + public bool AddOrUpdate(TK start, TK end, TV value, Func updateFactoryCallback) + { + ArgumentNullException.ThrowIfNull(value); + + return BSTInsert(start, end, value, updateFactoryCallback, out _); + } + + /// + /// Gets an existing or adds a new interval into the tree whose start is , end is and value is . + /// + /// Start of the range to add + /// End of the range to insert + /// Value to add + /// is null + /// if is not yet on the tree, or the existing value otherwise + public TV GetOrAdd(TK start, TK end, TV value) + { + ArgumentNullException.ThrowIfNull(value); + + BSTInsert(start, end, value, null, out IntervalTreeNode node); + return node.Value; + } + + /// + /// Removes a value from the tree, searching for it with . + /// + /// Key of the node to remove + /// Number of deleted values + public int Remove(TK key) + { + int removed = Delete(key); + + _count -= removed; + + return removed; + } + + /// + /// Adds all the nodes in the dictionary into . + /// + /// A list of all values sorted by Key Order + public List AsList() + { + List list = new(); + + AddToList(_root, list); + + return list; + } + + #endregion + + #region Private Methods (BST) + + /// + /// Adds all values that are children of or contained within into , in Key Order. + /// + /// The node to search for values within + /// The list to add values to + private void AddToList(IntervalTreeNode node, List list) + { + if (node == null) + { + return; + } + + AddToList(node.Left, list); + + list.Add(node.Value); + + AddToList(node.Right, list); + } + + /// + /// Retrieve the node reference whose key is , or null if no such node exists. + /// + /// Key of the node to get + /// is null + /// Node reference in the tree + private IntervalTreeNode GetNode(TK key) + { + ArgumentNullException.ThrowIfNull(key); + + IntervalTreeNode node = _root; + while (node != null) + { + int cmp = key.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + + /// + /// Retrieve all keys that overlap the given start and end keys. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Overlaps count to update + private void GetKeys(IntervalTreeNode node, TK start, TK end, ref TK[] overlaps, ref int overlapCount) + { + if (node == null || start.CompareTo(node.Max) >= 0) + { + return; + } + + GetKeys(node.Left, start, end, ref overlaps, ref overlapCount); + + bool endsOnRight = end.CompareTo(node.Start) > 0; + if (endsOnRight) + { + if (start.CompareTo(node.End) < 0) + { + if (overlaps.Length <= overlapCount) + { + Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize); + } + + overlaps[overlapCount++] = node.Start; + } + + GetKeys(node.Right, start, end, ref overlaps, ref overlapCount); + } + } + + /// + /// Propagate an increase in max value starting at the given node, heading up the tree. + /// This should only be called if the max increases - not for rebalancing or removals. + /// + /// The node to start propagating from + private static void PropagateIncrease(IntervalTreeNode node) + { + TK max = node.Max; + IntervalTreeNode ptr = node; + + while ((ptr = ptr.Parent) != null) + { + if (max.CompareTo(ptr.Max) > 0) + { + ptr.Max = max; + } + else + { + break; + } + } + } + + /// + /// Propagate recalculating max value starting at the given node, heading up the tree. + /// This fully recalculates the max value from all children when there is potential for it to decrease. + /// + /// The node to start propagating from + private static void PropagateFull(IntervalTreeNode node) + { + IntervalTreeNode ptr = node; + + do + { + TK max = ptr.End; + + if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0) + { + max = ptr.Left.Max; + } + + if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0) + { + max = ptr.Right.Max; + } + + ptr.Max = max; + } while ((ptr = ptr.Parent) != null); + } + + /// + /// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key. + /// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than , and all children in the right subtree are greater than . + /// Each node can contain multiple values, and has an end address which is the maximum of all those values. + /// Post insertion, the "max" value of the node and all parents are updated. + /// + /// Start of the range to insert + /// End of the range to insert + /// Value to insert + /// Optional factory used to create a new value if is already on the tree + /// Node that was inserted or modified + /// True if was not yet on the tree, false otherwise + private bool BSTInsert(TK start, TK end, TV value, Func updateFactoryCallback, out IntervalTreeNode outNode) + { + IntervalTreeNode parent = null; + IntervalTreeNode node = _root; + + while (node != null) + { + parent = node; + int cmp = start.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + outNode = node; + + if (updateFactoryCallback != null) + { + // Replace + node.Value = updateFactoryCallback(start, node.Value); + + int endCmp = end.CompareTo(node.End); + + if (endCmp > 0) + { + node.End = end; + if (end.CompareTo(node.Max) > 0) + { + node.Max = end; + PropagateIncrease(node); + RestoreBalanceAfterInsertion(node); + } + } + else if (endCmp < 0) + { + node.End = end; + PropagateFull(node); + } + } + + return false; + } + } + IntervalTreeNode newNode = new(start, end, value, parent); + if (newNode.Parent == null) + { + _root = newNode; + } + else if (start.CompareTo(parent.Start) < 0) + { + parent.Left = newNode; + } + else + { + parent.Right = newNode; + } + + PropagateIncrease(newNode); + _count++; + RestoreBalanceAfterInsertion(newNode); + outNode = newNode; + return true; + } + + /// + /// Removes the value from the dictionary after searching for it with . + /// + /// Key to search for + /// Number of deleted values + private int Delete(TK key) + { + IntervalTreeNode nodeToDelete = GetNode(key); + + if (nodeToDelete == null) + { + return 0; + } + + IntervalTreeNode replacementNode; + + if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null) + { + replacementNode = nodeToDelete; + } + else + { + replacementNode = PredecessorOf(nodeToDelete); + } + + IntervalTreeNode tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); + + if (tmp != null) + { + tmp.Parent = ParentOf(replacementNode); + } + + if (ParentOf(replacementNode) == null) + { + _root = tmp; + } + else if (replacementNode == LeftOf(ParentOf(replacementNode))) + { + ParentOf(replacementNode).Left = tmp; + } + else + { + ParentOf(replacementNode).Right = tmp; + } + + if (replacementNode != nodeToDelete) + { + nodeToDelete.Start = replacementNode.Start; + nodeToDelete.Value = replacementNode.Value; + nodeToDelete.End = replacementNode.End; + nodeToDelete.Max = replacementNode.Max; + } + + PropagateFull(replacementNode); + + if (tmp != null && ColorOf(replacementNode) == Black) + { + RestoreBalanceAfterRemoval(tmp); + } + + return 1; + } + + /// + /// Returns the node with the largest key where is considered the root node. + /// + /// Root Node + /// Node with the maximum key in the tree of + private static IntervalTreeNode Maximum(IntervalTreeNode node) + { + IntervalTreeNode tmp = node; + while (tmp.Right != null) + { + tmp = tmp.Right; + } + + return tmp; + } + + /// + /// Finds the node whose key is immediately less than . + /// + /// Node to find the predecessor of + /// Predecessor of + private static IntervalTreeNode PredecessorOf(IntervalTreeNode node) + { + if (node.Left != null) + { + return Maximum(node.Left); + } + IntervalTreeNode parent = node.Parent; + while (parent != null && node == parent.Left) + { + node = parent; + parent = parent.Parent; + } + return parent; + } + + #endregion + + #region Private Methods (RBL) + + private void RestoreBalanceAfterRemoval(IntervalTreeNode balanceNode) + { + IntervalTreeNode ptr = balanceNode; + + while (ptr != _root && ColorOf(ptr) == Black) + { + if (ptr == LeftOf(ParentOf(ptr))) + { + IntervalTreeNode sibling = RightOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateLeft(ParentOf(ptr)); + sibling = RightOf(ParentOf(ptr)); + } + if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(RightOf(sibling)) == Black) + { + SetColor(LeftOf(sibling), Black); + SetColor(sibling, Red); + RotateRight(sibling); + sibling = RightOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(RightOf(sibling), Black); + RotateLeft(ParentOf(ptr)); + ptr = _root; + } + } + else + { + IntervalTreeNode sibling = LeftOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateRight(ParentOf(ptr)); + sibling = LeftOf(ParentOf(ptr)); + } + if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(LeftOf(sibling)) == Black) + { + SetColor(RightOf(sibling), Black); + SetColor(sibling, Red); + RotateLeft(sibling); + sibling = LeftOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(LeftOf(sibling), Black); + RotateRight(ParentOf(ptr)); + ptr = _root; + } + } + } + SetColor(ptr, Black); + } + + private void RestoreBalanceAfterInsertion(IntervalTreeNode balanceNode) + { + SetColor(balanceNode, Red); + while (balanceNode != null && balanceNode != _root && ColorOf(ParentOf(balanceNode)) == Red) + { + if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode)))) + { + IntervalTreeNode sibling = RightOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == RightOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateLeft(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateRight(ParentOf(ParentOf(balanceNode))); + } + } + else + { + IntervalTreeNode sibling = LeftOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == LeftOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateRight(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateLeft(ParentOf(ParentOf(balanceNode))); + } + } + } + SetColor(_root, Black); + } + + private void RotateLeft(IntervalTreeNode node) + { + if (node != null) + { + IntervalTreeNode right = RightOf(node); + node.Right = LeftOf(right); + if (node.Right != null) + { + node.Right.Parent = node; + } + IntervalTreeNode nodeParent = ParentOf(node); + right.Parent = nodeParent; + if (nodeParent == null) + { + _root = right; + } + else if (node == LeftOf(nodeParent)) + { + nodeParent.Left = right; + } + else + { + nodeParent.Right = right; + } + right.Left = node; + node.Parent = right; + + PropagateFull(node); + } + } + + private void RotateRight(IntervalTreeNode node) + { + if (node != null) + { + IntervalTreeNode left = LeftOf(node); + node.Left = RightOf(left); + if (node.Left != null) + { + node.Left.Parent = node; + } + IntervalTreeNode nodeParent = ParentOf(node); + left.Parent = nodeParent; + if (nodeParent == null) + { + _root = left; + } + else if (node == RightOf(nodeParent)) + { + nodeParent.Right = left; + } + else + { + nodeParent.Left = left; + } + left.Right = node; + node.Parent = left; + + PropagateFull(node); + } + } + + #endregion + + #region Safety-Methods + + // These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions. + + /// + /// Returns the color of , or Black if it is null. + /// + /// Node + /// The boolean color of , or black if null + private static bool ColorOf(IntervalTreeNode node) + { + return node == null || node.Color; + } + + /// + /// Sets the color of node to . + ///

+ /// This method does nothing if is null. + ///
+ /// Node to set the color of + /// Color (Boolean) + private static void SetColor(IntervalTreeNode node, bool color) + { + if (node != null) + { + node.Color = color; + } + } + + /// + /// This method returns the left node of , or null if is null. + /// + /// Node to retrieve the left child from + /// Left child of + private static IntervalTreeNode LeftOf(IntervalTreeNode node) + { + return node?.Left; + } + + /// + /// This method returns the right node of , or null if is null. + /// + /// Node to retrieve the right child from + /// Right child of + private static IntervalTreeNode RightOf(IntervalTreeNode node) + { + return node?.Right; + } + + /// + /// Returns the parent node of , or null if is null. + /// + /// Node to retrieve the parent from + /// Parent of + private static IntervalTreeNode ParentOf(IntervalTreeNode node) + { + return node?.Parent; + } + + #endregion + + public bool ContainsKey(TK key) + { + return GetNode(key) != null; + } + + public void Clear() + { + _root = null; + _count = 0; + } + } + + /// + /// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V. + /// + /// Key type of the node + /// Value type of the node + class IntervalTreeNode + { + public bool Color = true; + public IntervalTreeNode Left = null; + public IntervalTreeNode Right = null; + public IntervalTreeNode Parent = null; + + /// + /// The start of the range. + /// + public TK Start; + + /// + /// The end of the range. + /// + public TK End; + + /// + /// The maximum end value of this node and all its children. + /// + public TK Max; + + /// + /// Value stored on this node. + /// + public TV Value; + + public IntervalTreeNode(TK start, TK end, TV value, IntervalTreeNode parent) + { + Start = start; + End = end; + Max = end; + Value = value; + Parent = parent; + } + } +} diff --git a/src/ARMeilleure/Translation/PTC/EncodingCache.cs b/src/ARMeilleure/Translation/PTC/EncodingCache.cs new file mode 100644 index 000000000..d9b38ace7 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/EncodingCache.cs @@ -0,0 +1,9 @@ +using System.Text; + +namespace ARMeilleure.Translation.PTC +{ + static class EncodingCache + { + public static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + } +} diff --git a/src/ARMeilleure/Translation/PTC/IPtcLoadState.cs b/src/ARMeilleure/Translation/PTC/IPtcLoadState.cs new file mode 100644 index 000000000..efff45a9f --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/IPtcLoadState.cs @@ -0,0 +1,10 @@ +using System; + +namespace ARMeilleure.Translation.PTC +{ + public interface IPtcLoadState + { + event Action PtcStateChanged; + void Continue(); + } +} diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs new file mode 100644 index 000000000..e1de6d8f1 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -0,0 +1,1133 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Common; +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using static ARMeilleure.Translation.PTC.PtcFormatter; + +namespace ARMeilleure.Translation.PTC +{ + using Arm64HardwareCapabilities = CodeGen.Arm64.HardwareCapabilities; + using X86HardwareCapabilities = CodeGen.X86.HardwareCapabilities; + + class Ptc : IPtcLoadState + { + private const string OuterHeaderMagicString = "PTCohd\0\0"; + private const string InnerHeaderMagicString = "PTCihd\0\0"; + + private const uint InternalVersion = 5518; //! To be incremented manually for each change to the ARMeilleure project. + + private const string ActualDir = "0"; + private const string BackupDir = "1"; + + private const string TitleIdTextDefault = "0000000000000000"; + private const string DisplayVersionDefault = "0"; + + public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1); + public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2); + public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3); + + private const byte FillingByte = 0x00; + private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; + + public PtcProfiler Profiler { get; } + + // Carriers. + private MemoryStream _infosStream; + private List _codesList; + private MemoryStream _relocsStream; + private MemoryStream _unwindInfosStream; + + private readonly ulong _outerHeaderMagic; + private readonly ulong _innerHeaderMagic; + + private readonly ManualResetEvent _waitEvent; + + private readonly object _lock; + + private bool _disposed; + + public string TitleIdText { get; private set; } + public string DisplayVersion { get; private set; } + + private MemoryManagerType _memoryMode; + + public string CachePathActual { get; private set; } + public string CachePathBackup { get; private set; } + + public PtcState State { get; private set; } + + // Progress reporting helpers. + private volatile int _translateCount; + private volatile int _translateTotalCount; + public event Action PtcStateChanged; + + public Ptc() + { + Profiler = new PtcProfiler(this); + + InitializeCarriers(); + + _outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan()); + _innerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(InnerHeaderMagicString).AsSpan()); + + _waitEvent = new ManualResetEvent(true); + + _lock = new object(); + + _disposed = false; + + TitleIdText = TitleIdTextDefault; + DisplayVersion = DisplayVersionDefault; + + CachePathActual = string.Empty; + CachePathBackup = string.Empty; + + Disable(); + } + + public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode) + { + Wait(); + + Profiler.Wait(); + Profiler.ClearEntries(); + + Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled})."); + + if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault) + { + TitleIdText = TitleIdTextDefault; + DisplayVersion = DisplayVersionDefault; + + CachePathActual = string.Empty; + CachePathBackup = string.Empty; + + Disable(); + + return; + } + + TitleIdText = titleIdText; + DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault; + _memoryMode = memoryMode; + + string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir); + string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir); + + if (!Directory.Exists(workPathActual)) + { + Directory.CreateDirectory(workPathActual); + } + + if (!Directory.Exists(workPathBackup)) + { + Directory.CreateDirectory(workPathBackup); + } + + CachePathActual = Path.Combine(workPathActual, DisplayVersion); + CachePathBackup = Path.Combine(workPathBackup, DisplayVersion); + + PreLoad(); + Profiler.PreLoad(); + + Enable(); + } + + private void InitializeCarriers() + { + _infosStream = MemoryStreamManager.Shared.GetStream(); + _codesList = new List(); + _relocsStream = MemoryStreamManager.Shared.GetStream(); + _unwindInfosStream = MemoryStreamManager.Shared.GetStream(); + } + + private void DisposeCarriers() + { + _infosStream.Dispose(); + _codesList.Clear(); + _relocsStream.Dispose(); + _unwindInfosStream.Dispose(); + } + + private bool AreCarriersEmpty() + { + return _infosStream.Length == 0L && _codesList.Count == 0 && _relocsStream.Length == 0L && _unwindInfosStream.Length == 0L; + } + + private void ResetCarriersIfNeeded() + { + if (AreCarriersEmpty()) + { + return; + } + + DisposeCarriers(); + + InitializeCarriers(); + } + + private void PreLoad() + { + string fileNameActual = $"{CachePathActual}.cache"; + string fileNameBackup = $"{CachePathBackup}.cache"; + + FileInfo fileInfoActual = new(fileNameActual); + FileInfo fileInfoBackup = new(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 unsafe bool Load(string fileName, bool isBackup) + { + using (FileStream compressedStream = new(fileName, FileMode.Open)) + using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true)) + { + OuterHeader outerHeader = DeserializeStructure(compressedStream); + + if (!outerHeader.IsHeaderValid()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Magic != _outerHeaderMagic) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.CacheFileVersion != InternalVersion) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Endianness != GetEndianness()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.FeatureInfo != GetFeatureInfo()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.MemoryManagerMode != GetMemoryManagerMode()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.OSPlatform != GetOSPlatform()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Architecture != (uint)RuntimeInformation.ProcessArchitecture) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + IntPtr intPtr = IntPtr.Zero; + + try + { + intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize)); + + using UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite); + try + { + deflateStream.CopyTo(stream); + } + catch + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek(0L, SeekOrigin.Begin); + + InnerHeader innerHeader = DeserializeStructure(stream); + + if (!innerHeader.IsHeaderValid()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (innerHeader.Magic != _innerHeaderMagic) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan infosBytes = new(stream.PositionPointer, innerHeader.InfosLength); + stream.Seek(innerHeader.InfosLength, SeekOrigin.Current); + + Hash128 infosHash = XXHash128.ComputeHash(infosBytes); + + if (innerHeader.InfosHash != infosHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan.Empty; + stream.Seek(innerHeader.CodesLength, SeekOrigin.Current); + + Hash128 codesHash = XXHash128.ComputeHash(codesBytes); + + if (innerHeader.CodesHash != codesHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength); + stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current); + + Hash128 relocsHash = XXHash128.ComputeHash(relocsBytes); + + if (innerHeader.RelocsHash != relocsHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength); + stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current); + + Hash128 unwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes); + + if (innerHeader.UnwindInfosHash != unwindInfosHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek((long)Unsafe.SizeOf(), SeekOrigin.Begin); + + _infosStream.Write(infosBytes); + stream.Seek(innerHeader.InfosLength, SeekOrigin.Current); + + _codesList.ReadFrom(stream); + + _relocsStream.Write(relocsBytes); + stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current); + + _unwindInfosStream.Write(unwindInfosBytes); + stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current); + + Debug.Assert(stream.Position == stream.Length); + } + finally + { + if (intPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr); + } + } + } + + long fileSize = new FileInfo(fileName).Length; + + Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} (size: {fileSize} bytes, translated functions: {GetEntriesCount()})."); + + return true; + } + + private static void InvalidateCompressedStream(FileStream compressedStream) + { + compressedStream.SetLength(0L); + } + + private void PreSave() + { + _waitEvent.Reset(); + + try + { + string fileNameActual = $"{CachePathActual}.cache"; + string fileNameBackup = $"{CachePathBackup}.cache"; + + FileInfo fileInfoActual = new(fileNameActual); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + File.Copy(fileNameActual, fileNameBackup, true); + } + + Save(fileNameActual); + } + finally + { + ResetCarriersIfNeeded(); + } + + _waitEvent.Set(); + } + + private unsafe void Save(string fileName) + { + int translatedFuncsCount; + + InnerHeader innerHeader = new() + { + Magic = _innerHeaderMagic, + + InfosLength = (int)_infosStream.Length, + CodesLength = _codesList.Length(), + RelocsLength = (int)_relocsStream.Length, + UnwindInfosLength = (int)_unwindInfosStream.Length, + }; + + OuterHeader outerHeader = new() + { + Magic = _outerHeaderMagic, + + CacheFileVersion = InternalVersion, + Endianness = GetEndianness(), + FeatureInfo = GetFeatureInfo(), + MemoryManagerMode = GetMemoryManagerMode(), + OSPlatform = GetOSPlatform(), + Architecture = (uint)RuntimeInformation.ProcessArchitecture, + + UncompressedStreamSize = + (long)Unsafe.SizeOf() + + innerHeader.InfosLength + + innerHeader.CodesLength + + innerHeader.RelocsLength + + innerHeader.UnwindInfosLength, + }; + + outerHeader.SetHeaderHash(); + + IntPtr intPtr = IntPtr.Zero; + + try + { + intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize)); + + using UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite); + stream.Seek((long)Unsafe.SizeOf(), SeekOrigin.Begin); + + ReadOnlySpan infosBytes = new(stream.PositionPointer, innerHeader.InfosLength); + _infosStream.WriteTo(stream); + + ReadOnlySpan codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan.Empty; + _codesList.WriteTo(stream); + + ReadOnlySpan relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength); + _relocsStream.WriteTo(stream); + + ReadOnlySpan unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength); + _unwindInfosStream.WriteTo(stream); + + Debug.Assert(stream.Position == stream.Length); + + innerHeader.InfosHash = XXHash128.ComputeHash(infosBytes); + innerHeader.CodesHash = XXHash128.ComputeHash(codesBytes); + innerHeader.RelocsHash = XXHash128.ComputeHash(relocsBytes); + innerHeader.UnwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes); + + innerHeader.SetHeaderHash(); + + stream.Seek(0L, SeekOrigin.Begin); + SerializeStructure(stream, innerHeader); + + translatedFuncsCount = GetEntriesCount(); + + ResetCarriersIfNeeded(); + + using FileStream compressedStream = new(fileName, FileMode.OpenOrCreate); + using DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true); + try + { + SerializeStructure(compressedStream, outerHeader); + + stream.Seek(0L, SeekOrigin.Begin); + stream.CopyTo(deflateStream); + } + catch + { + compressedStream.Position = 0L; + } + + if (compressedStream.Position < compressedStream.Length) + { + compressedStream.SetLength(compressedStream.Position); + } + } + finally + { + if (intPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr); + } + } + + long fileSize = new FileInfo(fileName).Length; + + if (fileSize != 0L) + { + Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {translatedFuncsCount})."); + } + } + + public void LoadTranslations(Translator translator) + { + if (AreCarriersEmpty()) + { + return; + } + + long infosStreamLength = _infosStream.Length; + long relocsStreamLength = _relocsStream.Length; + long unwindInfosStreamLength = _unwindInfosStream.Length; + + _infosStream.Seek(0L, SeekOrigin.Begin); + _relocsStream.Seek(0L, SeekOrigin.Begin); + _unwindInfosStream.Seek(0L, SeekOrigin.Begin); + + using (BinaryReader relocsReader = new(_relocsStream, EncodingCache.UTF8NoBOM, true)) + using (BinaryReader unwindInfosReader = new(_unwindInfosStream, EncodingCache.UTF8NoBOM, true)) + { + for (int index = 0; index < GetEntriesCount(); index++) + { + InfoEntry infoEntry = DeserializeStructure(_infosStream); + + if (infoEntry.Stubbed) + { + SkipCode(index, infoEntry.CodeLength); + SkipReloc(infoEntry.RelocEntriesCount); + SkipUnwindInfo(unwindInfosReader); + + continue; + } + + bool isEntryChanged = infoEntry.Hash != ComputeHash(translator.Memory, infoEntry.Address, infoEntry.GuestSize); + + if (isEntryChanged || (!infoEntry.HighCq && Profiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) && value.HighCq)) + { + infoEntry.Stubbed = true; + infoEntry.CodeLength = 0; + UpdateInfo(infoEntry); + + StubCode(index); + StubReloc(infoEntry.RelocEntriesCount); + StubUnwindInfo(unwindInfosReader); + + if (isEntryChanged) + { + Logger.Info?.Print(LogClass.Ptc, $"Invalidated translated function (address: 0x{infoEntry.Address:X16})"); + } + + continue; + } + + byte[] code = ReadCode(index, infoEntry.CodeLength); + + Counter callCounter = null; + + if (infoEntry.RelocEntriesCount != 0) + { + RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount); + + PatchCode(translator, code, relocEntries, out callCounter); + } + + UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader); + + TranslatedFunction func = FastTranslate(code, callCounter, infoEntry.GuestSize, unwindInfo, infoEntry.HighCq); + + translator.RegisterFunction(infoEntry.Address, func); + + bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, infoEntry.GuestSize, func); + + Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique."); + } + } + + if (_infosStream.Length != infosStreamLength || _infosStream.Position != infosStreamLength || + _relocsStream.Length != relocsStreamLength || _relocsStream.Position != relocsStreamLength || + _unwindInfosStream.Length != unwindInfosStreamLength || _unwindInfosStream.Position != unwindInfosStreamLength) + { + throw new Exception("The length of a memory stream has changed, or its position has not reached or has exceeded its end."); + } + + Logger.Info?.Print(LogClass.Ptc, $"{translator.Functions.Count} translated functions loaded"); + } + + private int GetEntriesCount() + { + return _codesList.Count; + } + + [Conditional("DEBUG")] + private void SkipCode(int index, int codeLength) + { + Debug.Assert(_codesList[index].Length == 0); + Debug.Assert(codeLength == 0); + } + + private void SkipReloc(int relocEntriesCount) + { + _relocsStream.Seek(relocEntriesCount * RelocEntry.Stride, SeekOrigin.Current); + } + + private void SkipUnwindInfo(BinaryReader unwindInfosReader) + { + int pushEntriesLength = unwindInfosReader.ReadInt32(); + + _unwindInfosStream.Seek(pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride, SeekOrigin.Current); + } + + private byte[] ReadCode(int index, int codeLength) + { + Debug.Assert(_codesList[index].Length == codeLength); + + return _codesList[index]; + } + + private static RelocEntry[] GetRelocEntries(BinaryReader relocsReader, int relocEntriesCount) + { + RelocEntry[] relocEntries = new RelocEntry[relocEntriesCount]; + + for (int i = 0; i < relocEntriesCount; i++) + { + int position = relocsReader.ReadInt32(); + SymbolType type = (SymbolType)relocsReader.ReadByte(); + ulong value = relocsReader.ReadUInt64(); + + relocEntries[i] = new RelocEntry(position, new Symbol(type, value)); + } + + return relocEntries; + } + + private static void PatchCode(Translator translator, Span code, RelocEntry[] relocEntries, out Counter callCounter) + { + callCounter = null; + + foreach (RelocEntry relocEntry in relocEntries) + { + IntPtr? imm = null; + Symbol symbol = relocEntry.Symbol; + + if (symbol.Type == SymbolType.FunctionTable) + { + ulong guestAddress = symbol.Value; + + if (translator.FunctionTable.IsValid(guestAddress)) + { + unsafe + { + imm = (IntPtr)Unsafe.AsPointer(ref translator.FunctionTable.GetValue(guestAddress)); + } + } + } + else if (symbol.Type == SymbolType.DelegateTable) + { + int index = (int)symbol.Value; + + if (Delegates.TryGetDelegateFuncPtrByIndex(index, out IntPtr funcPtr)) + { + imm = funcPtr; + } + } + else if (symbol == PageTableSymbol) + { + imm = translator.Memory.PageTablePointer; + } + else if (symbol == CountTableSymbol) + { + callCounter ??= new Counter(translator.CountTable); + + unsafe + { + imm = (IntPtr)Unsafe.AsPointer(ref callCounter.Value); + } + } + else if (symbol == DispatchStubSymbol) + { + imm = translator.Stubs.DispatchStub; + } + + if (imm == null) + { + throw new Exception($"Unexpected reloc entry {relocEntry}."); + } + + BinaryPrimitives.WriteUInt64LittleEndian(code.Slice(relocEntry.Position, 8), (ulong)imm.Value); + } + } + + private static UnwindInfo ReadUnwindInfo(BinaryReader unwindInfosReader) + { + int pushEntriesLength = unwindInfosReader.ReadInt32(); + + UnwindPushEntry[] pushEntries = new UnwindPushEntry[pushEntriesLength]; + + for (int i = 0; i < pushEntriesLength; i++) + { + int pseudoOp = unwindInfosReader.ReadInt32(); + int prologOffset = unwindInfosReader.ReadInt32(); + int regIndex = unwindInfosReader.ReadInt32(); + int stackOffsetOrAllocSize = unwindInfosReader.ReadInt32(); + + pushEntries[i] = new UnwindPushEntry((UnwindPseudoOp)pseudoOp, prologOffset, regIndex, stackOffsetOrAllocSize); + } + + int prologueSize = unwindInfosReader.ReadInt32(); + + return new UnwindInfo(pushEntries, prologueSize); + } + + private static TranslatedFunction FastTranslate( + byte[] code, + Counter callCounter, + ulong guestSize, + UnwindInfo unwindInfo, + bool highCq) + { + var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty); + var gFunc = cFunc.MapWithPointer(out IntPtr gFuncPointer); + + return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq); + } + + private void UpdateInfo(InfoEntry infoEntry) + { + _infosStream.Seek(-Unsafe.SizeOf(), SeekOrigin.Current); + + SerializeStructure(_infosStream, infoEntry); + } + + private void StubCode(int index) + { + _codesList[index] = Array.Empty(); + } + + private void StubReloc(int relocEntriesCount) + { + for (int i = 0; i < relocEntriesCount * RelocEntry.Stride; i++) + { + _relocsStream.WriteByte(FillingByte); + } + } + + private void StubUnwindInfo(BinaryReader unwindInfosReader) + { + int pushEntriesLength = unwindInfosReader.ReadInt32(); + + for (int i = 0; i < pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride; i++) + { + _unwindInfosStream.WriteByte(FillingByte); + } + } + + public void MakeAndSaveTranslations(Translator translator) + { + var profiledFuncsToTranslate = Profiler.GetProfiledFuncsToTranslate(translator.Functions); + + _translateCount = 0; + _translateTotalCount = profiledFuncsToTranslate.Count; + + if (_translateTotalCount == 0) + { + ResetCarriersIfNeeded(); + + return; + } + + int degreeOfParallelism = Environment.ProcessorCount; + + // If there are enough cores lying around, we leave one alone for other tasks. + if (degreeOfParallelism > 4) + { + degreeOfParallelism--; + } + + Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism}"); + + PtcStateChanged?.Invoke(PtcLoadingState.Start, _translateCount, _translateTotalCount); + + using AutoResetEvent progressReportEvent = new(false); + + Thread progressReportThread = new(ReportProgress) + { + Name = "Ptc.ProgressReporter", + Priority = ThreadPriority.Lowest, + IsBackground = true, + }; + + progressReportThread.Start(progressReportEvent); + + void TranslateFuncs() + { + while (profiledFuncsToTranslate.TryDequeue(out var item)) + { + ulong address = item.address; + + Debug.Assert(Profiler.IsAddressInStaticCodeRange(address)); + + TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq); + + bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func); + + Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique."); + + Interlocked.Increment(ref _translateCount); + + translator.RegisterFunction(address, func); + + if (State != PtcState.Enabled) + { + break; + } + } + } + + List threads = new(); + + for (int i = 0; i < degreeOfParallelism; i++) + { + Thread thread = new(TranslateFuncs) + { + IsBackground = true, + }; + + threads.Add(thread); + } + + Stopwatch sw = Stopwatch.StartNew(); + + threads.ForEach((thread) => thread.Start()); + threads.ForEach((thread) => thread.Join()); + + threads.Clear(); + + progressReportEvent.Set(); + progressReportThread.Join(); + + sw.Stop(); + + PtcStateChanged?.Invoke(PtcLoadingState.Loaded, _translateCount, _translateTotalCount); + + Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism} in {sw.Elapsed.TotalSeconds} s"); + + Thread preSaveThread = new(PreSave) + { + IsBackground = true, + }; + preSaveThread.Start(); + } + + private void ReportProgress(object state) + { + const int RefreshRate = 50; // ms. + + AutoResetEvent endEvent = (AutoResetEvent)state; + + int count = 0; + + do + { + int newCount = _translateCount; + + if (count != newCount) + { + PtcStateChanged?.Invoke(PtcLoadingState.Loading, newCount, _translateTotalCount); + count = newCount; + } + } + while (!endEvent.WaitOne(RefreshRate)); + } + + public static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize) + { + return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize)))); + } + + public void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc) + { + lock (_lock) + { + byte[] code = compiledFunc.Code; + RelocInfo relocInfo = compiledFunc.RelocInfo; + UnwindInfo unwindInfo = compiledFunc.UnwindInfo; + + InfoEntry infoEntry = new() + { + Address = address, + GuestSize = guestSize, + Hash = hash, + HighCq = highCq, + Stubbed = false, + CodeLength = code.Length, + RelocEntriesCount = relocInfo.Entries.Length, + }; + + SerializeStructure(_infosStream, infoEntry); + + WriteCode(code.AsSpan()); + + // WriteReloc. + using var relocInfoWriter = new BinaryWriter(_relocsStream, EncodingCache.UTF8NoBOM, true); + + foreach (RelocEntry entry in relocInfo.Entries) + { + relocInfoWriter.Write(entry.Position); + relocInfoWriter.Write((byte)entry.Symbol.Type); + relocInfoWriter.Write(entry.Symbol.Value); + } + + // WriteUnwindInfo. + using var unwindInfoWriter = new BinaryWriter(_unwindInfosStream, EncodingCache.UTF8NoBOM, true); + + unwindInfoWriter.Write(unwindInfo.PushEntries.Length); + + foreach (UnwindPushEntry unwindPushEntry in unwindInfo.PushEntries) + { + unwindInfoWriter.Write((int)unwindPushEntry.PseudoOp); + unwindInfoWriter.Write(unwindPushEntry.PrologOffset); + unwindInfoWriter.Write(unwindPushEntry.RegIndex); + unwindInfoWriter.Write(unwindPushEntry.StackOffsetOrAllocSize); + } + + unwindInfoWriter.Write(unwindInfo.PrologSize); + } + } + + private void WriteCode(ReadOnlySpan code) + { + _codesList.Add(code.ToArray()); + } + + public static bool GetEndianness() + { + return BitConverter.IsLittleEndian; + } + + private static FeatureInfo GetFeatureInfo() + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + return new FeatureInfo( + (ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap, + (ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2, + (ulong)Arm64HardwareCapabilities.MacOsFeatureInfo, + 0, + 0); + } + else if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + return new FeatureInfo( + (ulong)X86HardwareCapabilities.FeatureInfo1Ecx, + (ulong)X86HardwareCapabilities.FeatureInfo1Edx, + (ulong)X86HardwareCapabilities.FeatureInfo7Ebx, + (ulong)X86HardwareCapabilities.FeatureInfo7Ecx, + (ulong)X86HardwareCapabilities.Xcr0InfoEax); + } + else + { + return new FeatureInfo(0, 0, 0, 0, 0); + } + } + + private byte GetMemoryManagerMode() + { + return (byte)_memoryMode; + } + + private static uint GetOSPlatform() + { + uint osPlatform = 0u; + +#pragma warning disable IDE0055 // Disable formatting + osPlatform |= (OperatingSystem.IsFreeBSD() ? 1u : 0u) << 0; + osPlatform |= (OperatingSystem.IsLinux() ? 1u : 0u) << 1; + osPlatform |= (OperatingSystem.IsMacOS() ? 1u : 0u) << 2; + osPlatform |= (OperatingSystem.IsWindows() ? 1u : 0u) << 3; + osPlatform |= (Ryujinx.Common.PlatformInfo.IsBionic ? 1u : 0u) << 4; +#pragma warning restore IDE0055 + + return osPlatform; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)] + private struct OuterHeader + { + public ulong Magic; + + public uint CacheFileVersion; + + public bool Endianness; + public FeatureInfo FeatureInfo; + public byte MemoryManagerMode; + public uint OSPlatform; + public uint Architecture; + + public long UncompressedStreamSize; + + public Hash128 HeaderHash; + + public void SetHeaderHash() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]); + } + + public bool IsHeaderValid() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]) == HeaderHash; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)] + private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4); + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)] + private struct InnerHeader + { + public ulong Magic; + + public int InfosLength; + public long CodesLength; + public int RelocsLength; + public int UnwindInfosLength; + + public Hash128 InfosHash; + public Hash128 CodesHash; + public Hash128 RelocsHash; + public Hash128 UnwindInfosHash; + + public Hash128 HeaderHash; + + public void SetHeaderHash() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]); + } + + public bool IsHeaderValid() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]) == HeaderHash; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 42*/)] + private struct InfoEntry + { + public ulong Address; + public ulong GuestSize; + public Hash128 Hash; + public bool HighCq; + public bool Stubbed; + public int CodeLength; + public int RelocEntriesCount; + } + + private void Enable() + { + State = PtcState.Enabled; + } + + public void Continue() + { + if (State == PtcState.Enabled) + { + State = PtcState.Continuing; + } + } + + public void Close() + { + if (State == PtcState.Enabled || + State == PtcState.Continuing) + { + State = PtcState.Closing; + } + } + + public void Disable() + { + State = PtcState.Disabled; + } + + private void Wait() + { + _waitEvent.WaitOne(); + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + + Wait(); + _waitEvent.Dispose(); + + DisposeCarriers(); + } + } + } +} diff --git a/src/ARMeilleure/Translation/PTC/PtcFormatter.cs b/src/ARMeilleure/Translation/PTC/PtcFormatter.cs new file mode 100644 index 000000000..60953dcd9 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcFormatter.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.PTC +{ + static class PtcFormatter + { + #region "Deserialize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary DeserializeDictionary(Stream stream, Func valueFunc) where TKey : struct + { + Dictionary dictionary = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + TKey key = DeserializeStructure(stream); + TValue value = valueFunc(stream); + + dictionary.Add(key, value); + } + + return dictionary; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary DeserializeAndUpdateDictionary(Stream stream, Func valueFunc, Func updateFunc) where TKey : struct + { + Dictionary dictionary = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + TKey key = DeserializeStructure(stream); + TValue value = valueFunc(stream); + + (key, value) = updateFunc(key, value); + + dictionary.Add(key, value); + } + + return dictionary; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List DeserializeList(Stream stream) where T : struct + { + List list = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + T item = DeserializeStructure(stream); + + list.Add(item); + } + + return list; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T DeserializeStructure(Stream stream) where T : struct + { + T structure = default; + + Span spanT = MemoryMarshal.CreateSpan(ref structure, 1); + int bytesCount = stream.Read(MemoryMarshal.AsBytes(spanT)); + + if (bytesCount != Unsafe.SizeOf()) + { + throw new EndOfStreamException(); + } + + return structure; + } + #endregion + + #region "GetSerializeSize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetSerializeSizeDictionary(Dictionary dictionary, Func valueFunc) where TKey : struct + { + int size = 0; + + size += Unsafe.SizeOf(); + + foreach ((_, TValue value) in dictionary) + { + size += Unsafe.SizeOf(); + size += valueFunc(value); + } + + return size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetSerializeSizeList(List list) where T : struct + { + int size = 0; + + size += Unsafe.SizeOf(); + + size += list.Count * Unsafe.SizeOf(); + + return size; + } + #endregion + + #region "Serialize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeDictionary(Stream stream, Dictionary dictionary, Action valueAction) where TKey : struct + { + SerializeStructure(stream, dictionary.Count); + + foreach ((TKey key, TValue value) in dictionary) + { + SerializeStructure(stream, key); + valueAction(stream, value); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeList(Stream stream, List list) where T : struct + { + SerializeStructure(stream, list.Count); + + foreach (T item in list) + { + SerializeStructure(stream, item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeStructure(Stream stream, T structure) where T : struct + { + Span spanT = MemoryMarshal.CreateSpan(ref structure, 1); + stream.Write(MemoryMarshal.AsBytes(spanT)); + } + #endregion + + #region "Extension methods" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadFrom(this List list, Stream stream) where T : struct + { + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + int itemLength = DeserializeStructure(stream); + + T[] item = new T[itemLength]; + + int bytesCount = stream.Read(MemoryMarshal.AsBytes(item.AsSpan())); + + if (bytesCount != itemLength) + { + throw new EndOfStreamException(); + } + + list.Add(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long Length(this List list) where T : struct + { + long size = 0L; + + size += Unsafe.SizeOf(); + + foreach (T[] item in list) + { + size += Unsafe.SizeOf(); + size += item.Length; + } + + return size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteTo(this List list, Stream stream) where T : struct + { + SerializeStructure(stream, list.Count); + + foreach (T[] item in list) + { + SerializeStructure(stream, item.Length); + + stream.Write(MemoryMarshal.AsBytes(item.AsSpan())); + } + } + #endregion + } +} diff --git a/src/ARMeilleure/Translation/PTC/PtcLoadingState.cs b/src/ARMeilleure/Translation/PTC/PtcLoadingState.cs new file mode 100644 index 000000000..587be7939 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcLoadingState.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Translation.PTC +{ + public enum PtcLoadingState + { + Start, + Loading, + Loaded, + } +} diff --git a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs new file mode 100644 index 000000000..0fe78edab --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs @@ -0,0 +1,441 @@ +using ARMeilleure.State; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +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.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Timers; +using static ARMeilleure.Translation.PTC.PtcFormatter; +using Timer = System.Timers.Timer; + +namespace ARMeilleure.Translation.PTC +{ + class PtcProfiler + { + private const string OuterHeaderMagicString = "Pohd\0\0\0\0"; + + private const uint InternalVersion = 5518; //! Not to be incremented manually for each change to the ARMeilleure project. + + private static readonly uint[] _migrateInternalVersions = { + 1866, + }; + + private const int SaveInterval = 30; // Seconds. + + private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; + + private readonly Ptc _ptc; + + private readonly Timer _timer; + + private readonly ulong _outerHeaderMagic; + + private readonly ManualResetEvent _waitEvent; + + private readonly object _lock; + + private bool _disposed; + + private Hash128 _lastHash; + + public Dictionary 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 Timer(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(); + + 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 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 = $"{_ptc.CachePathActual}.info"; + string fileNameBackup = $"{_ptc.CachePathBackup}.info"; + + FileInfo fileInfoActual = new(fileNameActual); + FileInfo fileInfoBackup = new(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(compressedStream); + + if (!outerHeader.IsHeaderValid()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Magic != _outerHeaderMagic) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.InfoFileVersion != InternalVersion && !_migrateInternalVersions.Contains(outerHeader.InfoFileVersion)) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Endianness != Ptc.GetEndianness()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + 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(stream); + + Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream)); + + if (actualHash != expectedHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + switch (outerHeader.InfoFileVersion) + { + case InternalVersion: + ProfiledFuncs = Deserialize(stream); + break; + case 1866: + ProfiledFuncs = Deserialize(stream, (address, profile) => (address + 0x500000UL, profile)); + break; + default: + Logger.Error?.Print(LogClass.Ptc, $"No migration path for {nameof(outerHeader.InfoFileVersion)} '{outerHeader.InfoFileVersion}'. Discarding cache."); + InvalidateCompressedStream(compressedStream); + return false; + } + + 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 Deserialize(Stream stream, Func migrateEntryFunc = null) + { + if (migrateEntryFunc != null) + { + return DeserializeAndUpdateDictionary(stream, DeserializeStructure, migrateEntryFunc); + } + + return DeserializeDictionary(stream, DeserializeStructure); + } + + private static ReadOnlySpan GetReadOnlySpan(MemoryStream memoryStream) + { + return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position); + } + + private static void InvalidateCompressedStream(FileStream compressedStream) + { + compressedStream.SetLength(0L); + } + + private void PreSave(object source, ElapsedEventArgs e) + { + _waitEvent.Reset(); + + string fileNameActual = $"{_ptc.CachePathActual}.info"; + string fileNameBackup = $"{_ptc.CachePathBackup}.info"; + + FileInfo fileInfoActual = new(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() + { + Magic = _outerHeaderMagic, + + InfoFileVersion = InternalVersion, + Endianness = Ptc.GetEndianness(), + }; + + outerHeader.SetHeaderHash(); + + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) + { + Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L); + + stream.Seek(Unsafe.SizeOf(), SeekOrigin.Begin); + + lock (_lock) + { + Serialize(stream, ProfiledFuncs); + + profiledFuncsCount = ProfiledFuncs.Count; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek(Unsafe.SizeOf(), 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 static void Serialize(Stream stream, Dictionary profiledFuncs) + { + SerializeDictionary(stream, profiledFuncs, SerializeStructure); + } + + [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 spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]); + } + + public bool IsHeaderValid() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]) == 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(); + } + } + } +} diff --git a/src/ARMeilleure/Translation/PTC/PtcState.cs b/src/ARMeilleure/Translation/PTC/PtcState.cs new file mode 100644 index 000000000..f6692e870 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcState.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Translation.PTC +{ + enum PtcState + { + Enabled, + Continuing, + Closing, + Disabled, + } +} diff --git a/src/ARMeilleure/Translation/RegisterToLocal.cs b/src/ARMeilleure/Translation/RegisterToLocal.cs new file mode 100644 index 000000000..91372eb00 --- /dev/null +++ b/src/ARMeilleure/Translation/RegisterToLocal.cs @@ -0,0 +1,52 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Collections.Generic; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + static class RegisterToLocal + { + public static void Rename(ControlFlowGraph cfg) + { + Dictionary registerToLocalMap = new(); + + Operand GetLocal(Operand op) + { + Register register = op.GetRegister(); + + if (!registerToLocalMap.TryGetValue(register, out Operand local)) + { + local = Local(op.Type); + + registerToLocalMap.Add(register, local); + } + + return local; + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + Operand dest = node.Destination; + + if (dest != default && dest.Kind == OperandKind.Register) + { + node.Destination = GetLocal(dest); + } + + for (int index = 0; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (source.Kind == OperandKind.Register) + { + node.SetSource(index, GetLocal(source)); + } + } + } + } + } + } +} diff --git a/src/ARMeilleure/Translation/RegisterUsage.cs b/src/ARMeilleure/Translation/RegisterUsage.cs new file mode 100644 index 000000000..c8c250626 --- /dev/null +++ b/src/ARMeilleure/Translation/RegisterUsage.cs @@ -0,0 +1,395 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Numerics; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.Translation +{ + static class RegisterUsage + { + private const int RegsCount = 32; + private const int RegsMask = RegsCount - 1; + + private readonly struct RegisterMask : IEquatable + { + public long IntMask => Mask.GetElement(0); + public long VecMask => Mask.GetElement(1); + + public Vector128 Mask { get; } + + public RegisterMask(Vector128 mask) + { + Mask = mask; + } + + public RegisterMask(long intMask, long vecMask) + { + Mask = Vector128.Create(intMask, vecMask); + } + + public static RegisterMask operator &(RegisterMask x, RegisterMask y) + { + if (Sse2.IsSupported) + { + return new RegisterMask(Sse2.And(x.Mask, y.Mask)); + } + + return new RegisterMask(x.IntMask & y.IntMask, x.VecMask & y.VecMask); + } + + public static RegisterMask operator |(RegisterMask x, RegisterMask y) + { + if (Sse2.IsSupported) + { + return new RegisterMask(Sse2.Or(x.Mask, y.Mask)); + } + + return new RegisterMask(x.IntMask | y.IntMask, x.VecMask | y.VecMask); + } + + public static RegisterMask operator ~(RegisterMask x) + { + if (Sse2.IsSupported) + { + return new RegisterMask(Sse2.AndNot(x.Mask, Vector128.AllBitsSet)); + } + + return new RegisterMask(~x.IntMask, ~x.VecMask); + } + + public static bool operator ==(RegisterMask x, RegisterMask y) + { + return x.Equals(y); + } + + public static bool operator !=(RegisterMask x, RegisterMask y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is RegisterMask regMask && Equals(regMask); + } + + public bool Equals(RegisterMask other) + { + return Mask.Equals(other.Mask); + } + + public override int GetHashCode() + { + return Mask.GetHashCode(); + } + } + + public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode) + { + // Compute local register inputs and outputs used inside blocks. + RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count]; + RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count]; + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (source.Kind == OperandKind.Register) + { + Register register = source.GetRegister(); + + localInputs[block.Index] |= GetMask(register) & ~localOutputs[block.Index]; + } + } + + if (node.Destination != default && node.Destination.Kind == OperandKind.Register) + { + localOutputs[block.Index] |= GetMask(node.Destination.GetRegister()); + } + } + } + + // Compute global register inputs and outputs used across blocks. + RegisterMask[] globalCmnOutputs = new RegisterMask[cfg.Blocks.Count]; + + RegisterMask[] globalInputs = new RegisterMask[cfg.Blocks.Count]; + RegisterMask[] globalOutputs = new RegisterMask[cfg.Blocks.Count]; + + bool modified; + bool firstPass = true; + + do + { + modified = false; + + // Compute register outputs. + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + if (block.Predecessors.Count != 0 && !HasContextLoad(block)) + { + BasicBlock predecessor = block.Predecessors[0]; + + RegisterMask cmnOutputs = localOutputs[predecessor.Index] | globalCmnOutputs[predecessor.Index]; + RegisterMask outputs = globalOutputs[predecessor.Index]; + + for (int pIndex = 1; pIndex < block.Predecessors.Count; pIndex++) + { + predecessor = block.Predecessors[pIndex]; + + cmnOutputs &= localOutputs[predecessor.Index] | globalCmnOutputs[predecessor.Index]; + outputs |= globalOutputs[predecessor.Index]; + } + + globalInputs[block.Index] |= outputs & ~cmnOutputs; + + if (!firstPass) + { + cmnOutputs &= globalCmnOutputs[block.Index]; + } + + modified |= Exchange(globalCmnOutputs, block.Index, cmnOutputs); + outputs |= localOutputs[block.Index]; + modified |= Exchange(globalOutputs, block.Index, globalOutputs[block.Index] | outputs); + } + else + { + modified |= Exchange(globalOutputs, block.Index, localOutputs[block.Index]); + } + } + + // Compute register inputs. + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + RegisterMask inputs = localInputs[block.Index]; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + inputs |= globalInputs[block.GetSuccessor(i).Index]; + } + + inputs &= ~globalCmnOutputs[block.Index]; + + modified |= Exchange(globalInputs, block.Index, globalInputs[block.Index] | inputs); + } + + firstPass = false; + } + while (modified); + + // Insert load and store context instructions where needed. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + bool hasContextLoad = HasContextLoad(block); + + if (hasContextLoad) + { + block.Operations.Remove(block.Operations.First); + } + + Operand arg = default; + + // The only block without any predecessor should be the entry block. + // It always needs a context load as it is the first block to run. + if (block.Predecessors.Count == 0 || hasContextLoad) + { + long vecMask = globalInputs[block.Index].VecMask; + long intMask = globalInputs[block.Index].IntMask; + + if (vecMask != 0 || intMask != 0) + { + arg = Local(OperandType.I64); + + Operation loadArg = block.Operations.AddFirst(Operation(Instruction.LoadArgument, arg, Const(0))); + + LoadLocals(block, vecMask, RegisterType.Vector, mode, loadArg, arg); + LoadLocals(block, intMask, RegisterType.Integer, mode, loadArg, arg); + } + } + + bool hasContextStore = HasContextStore(block); + + if (hasContextStore) + { + block.Operations.Remove(block.Operations.Last); + } + + if (EndsWithReturn(block) || hasContextStore) + { + long vecMask = globalOutputs[block.Index].VecMask; + long intMask = globalOutputs[block.Index].IntMask; + + if (vecMask != 0 || intMask != 0) + { + if (arg == default) + { + arg = Local(OperandType.I64); + + block.Append(Operation(Instruction.LoadArgument, arg, Const(0))); + } + + StoreLocals(block, intMask, RegisterType.Integer, mode, arg); + StoreLocals(block, vecMask, RegisterType.Vector, mode, arg); + } + } + } + } + + private static bool HasContextLoad(BasicBlock block) + { + return StartsWith(block, Instruction.LoadFromContext) && block.Operations.First.SourcesCount == 0; + } + + private static bool HasContextStore(BasicBlock block) + { + return EndsWith(block, Instruction.StoreToContext) && block.Operations.Last.SourcesCount == 0; + } + + private static bool StartsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count > 0) + { + Operation first = block.Operations.First; + + return first != default && first.Instruction == inst; + } + + return false; + } + + private static bool EndsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count > 0) + { + Operation last = block.Operations.Last; + + return last != default && last.Instruction == inst; + } + + return false; + } + + private static RegisterMask GetMask(Register register) + { + long intMask = 0; + long vecMask = 0; + + switch (register.Type) + { +#pragma warning disable IDE0055 // Disable formatting + case RegisterType.Flag: intMask = (1L << RegsCount) << register.Index; break; + case RegisterType.Integer: intMask = 1L << register.Index; break; + case RegisterType.FpFlag: vecMask = (1L << RegsCount) << register.Index; break; + case RegisterType.Vector: vecMask = 1L << register.Index; break; +#pragma warning restore IDE0055 + } + + return new RegisterMask(intMask, vecMask); + } + + private static bool Exchange(RegisterMask[] masks, int blkIndex, RegisterMask value) + { + ref RegisterMask curValue = ref masks[blkIndex]; + + bool changed = curValue != value; + + curValue = value; + + return changed; + } + + private static void LoadLocals( + BasicBlock block, + long inputs, + RegisterType baseType, + ExecutionMode mode, + Operation loadArg, + Operand arg) + { + while (inputs != 0) + { + int bit = 63 - BitOperations.LeadingZeroCount((ulong)inputs); + + Operand dest = GetRegFromBit(bit, baseType, mode); + Operand offset = Const((long)NativeContext.GetRegisterOffset(dest.GetRegister())); + Operand addr = Local(OperandType.I64); + + block.Operations.AddAfter(loadArg, Operation(Instruction.Load, dest, addr)); + block.Operations.AddAfter(loadArg, Operation(Instruction.Add, addr, arg, offset)); + + inputs &= ~(1L << bit); + } + } + + private static void StoreLocals( + BasicBlock block, + long outputs, + RegisterType baseType, + ExecutionMode mode, + Operand arg) + { + while (outputs != 0) + { + int bit = BitOperations.TrailingZeroCount(outputs); + + Operand source = GetRegFromBit(bit, baseType, mode); + Operand offset = Const((long)NativeContext.GetRegisterOffset(source.GetRegister())); + Operand addr = Local(OperandType.I64); + + block.Append(Operation(Instruction.Add, addr, arg, offset)); + block.Append(Operation(Instruction.Store, default, addr, source)); + + outputs &= ~(1L << bit); + } + } + + private static Operand GetRegFromBit(int bit, RegisterType baseType, ExecutionMode mode) + { + if (bit < RegsCount) + { + return Register(bit, baseType, GetOperandType(baseType, mode)); + } + else if (baseType == RegisterType.Integer) + { + return Register(bit & RegsMask, RegisterType.Flag, OperandType.I32); + } + else if (baseType == RegisterType.Vector) + { + return Register(bit & RegsMask, RegisterType.FpFlag, OperandType.I32); + } + else + { + throw new ArgumentOutOfRangeException(nameof(bit)); + } + } + + private static OperandType GetOperandType(RegisterType type, ExecutionMode mode) + { + return type switch + { + RegisterType.Flag => OperandType.I32, + RegisterType.FpFlag => OperandType.I32, + RegisterType.Integer => (mode == ExecutionMode.Aarch64) ? OperandType.I64 : OperandType.I32, + RegisterType.Vector => OperandType.V128, + _ => throw new ArgumentException($"Invalid register type \"{type}\"."), + }; + } + + private static bool EndsWithReturn(BasicBlock block) + { + Operation last = block.Operations.Last; + + return last != default && last.Instruction == Instruction.Return; + } + } +} diff --git a/src/ARMeilleure/Translation/RejitRequest.cs b/src/ARMeilleure/Translation/RejitRequest.cs new file mode 100644 index 000000000..1bed5c0ae --- /dev/null +++ b/src/ARMeilleure/Translation/RejitRequest.cs @@ -0,0 +1,16 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Translation +{ + struct RejitRequest + { + public ulong Address; + public ExecutionMode Mode; + + public RejitRequest(ulong address, ExecutionMode mode) + { + Address = address; + Mode = mode; + } + } +} diff --git a/src/ARMeilleure/Translation/SsaConstruction.cs b/src/ARMeilleure/Translation/SsaConstruction.cs new file mode 100644 index 000000000..cddcfcd4f --- /dev/null +++ b/src/ARMeilleure/Translation/SsaConstruction.cs @@ -0,0 +1,289 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + static partial class Ssa + { + private class DefMap + { + private readonly Dictionary _map; + private readonly BitMap _phiMasks; + + public DefMap() + { + _map = new Dictionary(); + _phiMasks = new BitMap(Allocators.Default, RegisterConsts.TotalCount); + } + + public bool TryAddOperand(int key, Operand operand) + { + return _map.TryAdd(key, operand); + } + + public bool TryGetOperand(int key, out Operand operand) + { + return _map.TryGetValue(key, out operand); + } + + public bool AddPhi(int key) + { + return _phiMasks.Set(key); + } + + public bool HasPhi(int key) + { + return _phiMasks.IsSet(key); + } + } + + public static void Construct(ControlFlowGraph cfg) + { + var globalDefs = new DefMap[cfg.Blocks.Count]; + var localDefs = new Operand[cfg.LocalsCount + RegisterConsts.TotalCount]; + + var dfPhiBlocks = new Queue(); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + globalDefs[block.Index] = new DefMap(); + } + + // First pass, get all defs and locals uses. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (TryGetId(src, out int srcKey)) + { + Operand local = localDefs[srcKey]; + + if (local == default) + { + local = src; + } + + node.SetSource(index, local); + } + } + + Operand dest = node.Destination; + + if (TryGetId(dest, out int destKey)) + { + Operand local = Local(dest.Type); + + localDefs[destKey] = local; + + node.Destination = local; + } + } + + for (int key = 0; key < localDefs.Length; key++) + { + Operand local = localDefs[key]; + + if (local == default) + { + continue; + } + + globalDefs[block.Index].TryAddOperand(key, local); + + dfPhiBlocks.Enqueue(block); + + while (dfPhiBlocks.TryDequeue(out BasicBlock dfPhiBlock)) + { + foreach (BasicBlock domFrontier in dfPhiBlock.DominanceFrontiers) + { + if (globalDefs[domFrontier.Index].AddPhi(key)) + { + dfPhiBlocks.Enqueue(domFrontier); + } + } + } + } + + Array.Clear(localDefs); + } + + // Second pass, rename variables with definitions on different blocks. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (TryGetId(src, out int key)) + { + Operand local = localDefs[key]; + + if (local == default) + { + local = FindDef(globalDefs, block, src); + localDefs[key] = local; + } + + node.SetSource(index, local); + } + } + } + + Array.Clear(localDefs); + } + } + + private static Operand FindDef(DefMap[] globalDefs, BasicBlock current, Operand operand) + { + if (globalDefs[current.Index].HasPhi(GetId(operand))) + { + return InsertPhi(globalDefs, current, operand); + } + + if (current != current.ImmediateDominator) + { + return FindDefOnPred(globalDefs, current.ImmediateDominator, operand); + } + + return Undef(); + } + + private static Operand FindDefOnPred(DefMap[] globalDefs, BasicBlock current, Operand operand) + { + BasicBlock previous; + + do + { + DefMap defMap = globalDefs[current.Index]; + + int key = GetId(operand); + + if (defMap.TryGetOperand(key, out Operand lastDef)) + { + return lastDef; + } + + if (defMap.HasPhi(key)) + { + return InsertPhi(globalDefs, current, operand); + } + + previous = current; + current = current.ImmediateDominator; + } + while (previous != current); + + return Undef(); + } + + private static Operand InsertPhi(DefMap[] globalDefs, BasicBlock block, Operand operand) + { + // This block has a Phi that has not been materialized yet, but that + // would define a new version of the variable we're looking for. We need + // to materialize the Phi, add all the block/operand pairs into the Phi, and + // then use the definition from that Phi. + Operand local = Local(operand.Type); + + Operation operation = Operation.Factory.PhiOperation(local, block.Predecessors.Count); + + AddPhi(block, operation); + + globalDefs[block.Index].TryAddOperand(GetId(operand), local); + + PhiOperation phi = operation.AsPhi(); + + for (int index = 0; index < block.Predecessors.Count; index++) + { + BasicBlock predecessor = block.Predecessors[index]; + + phi.SetBlock(index, predecessor); + phi.SetSource(index, FindDefOnPred(globalDefs, predecessor, operand)); + } + + return local; + } + + private static void AddPhi(BasicBlock block, Operation phi) + { + Operation node = block.Operations.First; + + if (node != default) + { + while (node.ListNext != default && node.ListNext.Instruction == Instruction.Phi) + { + node = node.ListNext; + } + } + + if (node != default && node.Instruction == Instruction.Phi) + { + block.Operations.AddAfter(node, phi); + } + else + { + block.Operations.AddFirst(phi); + } + } + + private static bool TryGetId(Operand operand, out int result) + { + if (operand != default) + { + if (operand.Kind == OperandKind.Register) + { + Register reg = operand.GetRegister(); + + if (reg.Type == RegisterType.Integer) + { + result = reg.Index; + } + else if (reg.Type == RegisterType.Vector) + { + result = RegisterConsts.IntRegsCount + reg.Index; + } + else if (reg.Type == RegisterType.Flag) + { + result = RegisterConsts.IntAndVecRegsCount + reg.Index; + } + else /* if (reg.Type == RegisterType.FpFlag) */ + { + result = RegisterConsts.FpFlagsOffset + reg.Index; + } + + return true; + } + else if (operand.Kind == OperandKind.LocalVariable && operand.GetLocalNumber() > 0) + { + result = RegisterConsts.TotalCount + operand.GetLocalNumber() - 1; + + return true; + } + } + + result = -1; + + return false; + } + + private static int GetId(Operand operand) + { + if (!TryGetId(operand, out int key)) + { + Debug.Fail("OperandKind must be Register or a numbered LocalVariable."); + } + + return key; + } + } +} diff --git a/src/ARMeilleure/Translation/SsaDeconstruction.cs b/src/ARMeilleure/Translation/SsaDeconstruction.cs new file mode 100644 index 000000000..68af54e5d --- /dev/null +++ b/src/ARMeilleure/Translation/SsaDeconstruction.cs @@ -0,0 +1,48 @@ +using ARMeilleure.IntermediateRepresentation; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.Translation +{ + static partial class Ssa + { + public static void Deconstruct(ControlFlowGraph cfg) + { + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + Operation operation = block.Operations.First; + + while (operation != default && operation.Instruction == Instruction.Phi) + { + Operation nextNode = operation.ListNext; + + Operand local = Local(operation.Destination.Type); + + PhiOperation phi = operation.AsPhi(); + + for (int index = 0; index < phi.SourcesCount; index++) + { + BasicBlock predecessor = phi.GetBlock(cfg, index); + + Operand source = phi.GetSource(index); + + predecessor.Append(Operation(Instruction.Copy, local, source)); + + phi.SetSource(index, default); + } + + Operation copyOp = Operation(Instruction.Copy, operation.Destination, local); + + block.Operations.AddBefore(operation, copyOp); + + operation.Destination = default; + + block.Operations.Remove(operation); + + operation = nextNode; + } + } + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatedFunction.cs b/src/ARMeilleure/Translation/TranslatedFunction.cs new file mode 100644 index 000000000..1446c254a --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatedFunction.cs @@ -0,0 +1,34 @@ +using ARMeilleure.Common; +using System; + +namespace ARMeilleure.Translation +{ + class TranslatedFunction + { + private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected. + + public IntPtr FuncPointer { get; } + public Counter CallCounter { get; } + public ulong GuestSize { get; } + public bool HighCq { get; } + + public TranslatedFunction(GuestFunction func, IntPtr funcPointer, Counter callCounter, ulong guestSize, bool highCq) + { + _func = func; + FuncPointer = funcPointer; + CallCounter = callCounter; + GuestSize = guestSize; + HighCq = highCq; + } + + public ulong Execute(State.ExecutionContext context) + { + return _func(context.NativeContextPtr); + } + + public ulong Execute(WrapperFunction dispatcher, State.ExecutionContext context) + { + return dispatcher(context.NativeContextPtr, (ulong)FuncPointer); + } + } +} diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs new file mode 100644 index 000000000..dc18038ba --- /dev/null +++ b/src/ARMeilleure/Translation/Translator.cs @@ -0,0 +1,592 @@ +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.Runtime.InteropServices; +using System.Threading; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + public class Translator + { + private static readonly AddressTable.Level[] _levels64Bit = + new AddressTable.Level[] + { + new(31, 17), + new(23, 8), + new(15, 8), + new( 7, 8), + new( 2, 5), + }; + + private static readonly AddressTable.Level[] _levels32Bit = + new AddressTable.Level[] + { + new(31, 17), + new(23, 8), + new(15, 8), + new( 7, 8), + new( 1, 6), + }; + + private readonly IJitMemoryAllocator _allocator; + private readonly ConcurrentQueue> _oldFuncs; + + private readonly Ptc _ptc; + + internal TranslatorCache Functions { get; } + internal AddressTable FunctionTable { get; } + internal EntryTable CountTable { get; } + internal TranslatorStubs Stubs { get; } + internal TranslatorQueue Queue { get; } + internal IMemoryManager Memory { get; } + + private Thread[] _backgroundTranslationThreads; + 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>(); + + _ptc = new Ptc(); + + Queue = new TranslatorQueue(); + + JitCache.Initialize(allocator); + + CountTable = new EntryTable(); + Functions = new TranslatorCache(); + FunctionTable = new AddressTable(for64Bits ? _levels64Bit : _levels32Bit); + Stubs = new TranslatorStubs(this); + + FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; + + if (memory.Type.IsHostMapped()) + { + NativeSignalHandler.InitializeSignalHandler(allocator.GetPageSize()); + } + } + + 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); + + Thread[] backgroundTranslationThreads = new Thread[threadCount]; + + for (int i = 0; i < threadCount; i++) + { + bool last = i != 0 && i == unboundedThreadCount - 1; + + backgroundTranslationThreads[i] = new(BackgroundTranslate) + { + Name = "CPU.BackgroundTranslatorThread." + i, + Priority = last ? ThreadPriority.Lowest : ThreadPriority.Normal, + }; + + backgroundTranslationThreads[i].Start(); + } + + Interlocked.Exchange(ref _backgroundTranslationThreads, backgroundTranslationThreads); + } + + 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) + { + Queue.Dispose(); + + Thread[] backgroundTranslationThreads = Interlocked.Exchange(ref _backgroundTranslationThreads, null); + + if (backgroundTranslationThreads != null) + { + foreach (Thread thread in backgroundTranslationThreads) + { + thread.Join(); + } + } + + ClearJitCache(); + + 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(Stubs.ContextWrapper, 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(Stubs.ContextWrapper, 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.FuncPointer); + 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.FuncPointer); + } + } + + 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 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, RuntimeInformation.ProcessArchitecture); + + if (context.HasPtc && !singleStep) + { + Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize); + + _ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc); + } + + GuestFunction func = compiledFunc.MapWithPointer(out IntPtr funcPointer); + + Allocators.ResetAll(); + + return new TranslatedFunction(func, funcPointer, 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 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) + { + if (!context.HighCq) + { + EmitRejitCheck(context, out counter); + } + + context.ClearQcFlag(); + } + + 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) + { + context.SyncQcFlag(); + + if (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 counter) + { + const int MinsCallForRejit = 100; + + counter = new Counter(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(); + + 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 functions = Functions.AsList(); + + foreach (var func in functions) + { + JitCache.Unmap(func.FuncPointer); + + func.CallCounter?.Dispose(); + } + + Functions.Clear(); + + while (_oldFuncs.TryDequeue(out var kv)) + { + JitCache.Unmap(kv.Value.FuncPointer); + + 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); + } + } + } + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorCache.cs b/src/ARMeilleure/Translation/TranslatorCache.cs new file mode 100644 index 000000000..99ca58dc6 --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorCache.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace ARMeilleure.Translation +{ + internal class TranslatorCache + { + private readonly IntervalTree _tree; + private readonly ReaderWriterLockSlim _treeLock; + + public int Count => _tree.Count; + + public TranslatorCache() + { + _tree = new IntervalTree(); + _treeLock = new ReaderWriterLockSlim(); + } + + public bool TryAdd(ulong address, ulong size, T value) + { + return AddOrUpdate(address, size, value, null); + } + + public bool AddOrUpdate(ulong address, ulong size, T value, Func updateFactoryCallback) + { + _treeLock.EnterWriteLock(); + bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback); + _treeLock.ExitWriteLock(); + + return result; + } + + public T GetOrAdd(ulong address, ulong size, T value) + { + _treeLock.EnterWriteLock(); + value = _tree.GetOrAdd(address, address + size, value); + _treeLock.ExitWriteLock(); + + return value; + } + + public bool Remove(ulong address) + { + _treeLock.EnterWriteLock(); + bool removed = _tree.Remove(address) != 0; + _treeLock.ExitWriteLock(); + + return removed; + } + + public void Clear() + { + _treeLock.EnterWriteLock(); + _tree.Clear(); + _treeLock.ExitWriteLock(); + } + + public bool ContainsKey(ulong address) + { + _treeLock.EnterReadLock(); + bool result = _tree.ContainsKey(address); + _treeLock.ExitReadLock(); + + return result; + } + + public bool TryGetValue(ulong address, out T value) + { + _treeLock.EnterReadLock(); + bool result = _tree.TryGet(address, out value); + _treeLock.ExitReadLock(); + + return result; + } + + public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps) + { + _treeLock.EnterReadLock(); + int count = _tree.Get(address, address + size, ref overlaps); + _treeLock.ExitReadLock(); + + return count; + } + + public List AsList() + { + _treeLock.EnterReadLock(); + List list = _tree.AsList(); + _treeLock.ExitReadLock(); + + return list; + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorQueue.cs b/src/ARMeilleure/Translation/TranslatorQueue.cs new file mode 100644 index 000000000..cee2f9080 --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorQueue.cs @@ -0,0 +1,121 @@ +using ARMeilleure.Diagnostics; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace ARMeilleure.Translation +{ + /// + /// Represents a queue of . + /// + /// + /// This does not necessarily behave like a queue, i.e: a FIFO collection. + /// + sealed class TranslatorQueue : IDisposable + { + private bool _disposed; + private readonly Stack _requests; + private readonly HashSet _requestAddresses; + + /// + /// Gets the object used to synchronize access to the . + /// + public object Sync { get; } + + /// + /// Gets the number of requests in the . + /// + public int Count => _requests.Count; + + /// + /// Initializes a new instance of the class. + /// + public TranslatorQueue() + { + Sync = new object(); + + _requests = new Stack(); + _requestAddresses = new HashSet(); + } + + /// + /// Enqueues a request with the specified and . + /// + /// Address of request + /// of request + public void Enqueue(ulong address, ExecutionMode mode) + { + lock (Sync) + { + if (_requestAddresses.Add(address)) + { + _requests.Push(new RejitRequest(address, mode)); + + TranslatorEventSource.Log.RejitQueueAdd(1); + + Monitor.Pulse(Sync); + } + } + } + + /// + /// Tries to dequeue a . This will block the thread until a + /// is enqueued or the is disposed. + /// + /// dequeued + /// on success; otherwise + public bool TryDequeue(out RejitRequest result) + { + while (!_disposed) + { + lock (Sync) + { + if (_requests.TryPop(out result)) + { + _requestAddresses.Remove(result.Address); + + TranslatorEventSource.Log.RejitQueueAdd(-1); + + return true; + } + + Monitor.Wait(Sync); + } + } + + result = default; + + return false; + } + + /// + /// Clears the . + /// + public void Clear() + { + lock (Sync) + { + TranslatorEventSource.Log.RejitQueueAdd(-_requests.Count); + + _requests.Clear(); + _requestAddresses.Clear(); + + Monitor.PulseAll(Sync); + } + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + + Clear(); + } + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorStubs.cs b/src/ARMeilleure/Translation/TranslatorStubs.cs new file mode 100644 index 000000000..bbe48c165 --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorStubs.cs @@ -0,0 +1,312 @@ +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 +{ + /// + /// Represents a stub manager. + /// + class TranslatorStubs : IDisposable + { + private static readonly Lazy _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true); + + private bool _disposed; + + private readonly Translator _translator; + private readonly Lazy _dispatchStub; + private readonly Lazy _dispatchLoop; + private readonly Lazy _contextWrapper; + + /// + /// Gets the dispatch stub. + /// + /// instance was disposed + public IntPtr DispatchStub + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _dispatchStub.Value; + } + } + + /// + /// Gets the slow dispatch stub. + /// + /// instance was disposed + public IntPtr SlowDispatchStub + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _slowDispatchStub.Value; + } + } + + /// + /// Gets the dispatch loop function. + /// + /// instance was disposed + public DispatcherFunction DispatchLoop + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _dispatchLoop.Value; + } + } + + /// + /// Gets the context wrapper function. + /// + /// instance was disposed + public WrapperFunction ContextWrapper + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _contextWrapper.Value; + } + } + + /// + /// Initializes a new instance of the class with the specified + /// instance. + /// + /// instance to use + /// is null + public TranslatorStubs(Translator translator) + { + ArgumentNullException.ThrowIfNull(translator); + + _translator = translator; + _dispatchStub = new(GenerateDispatchStub, isThreadSafe: true); + _dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true); + _contextWrapper = new(GenerateContextWrapper, isThreadSafe: true); + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + 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; + } + } + + /// + /// Frees resources used by the instance. + /// + ~TranslatorStubs() + { + Dispose(false); + } + + /// + /// Generates a . + /// + /// Generated + 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, RuntimeInformation.ProcessArchitecture).Map(); + + return Marshal.GetFunctionPointerForDelegate(func); + } + + /// + /// Generates a . + /// + /// Generated + 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, RuntimeInformation.ProcessArchitecture).Map(); + + return Marshal.GetFunctionPointerForDelegate(func); + } + + /// + /// Emits code that syncs FP state before executing guest code, or returns it to normal. + /// + /// Emitter context for the method + /// Pointer to the native context + /// True if entering guest code, false otherwise + private static void EmitSyncFpContext(EmitterContext context, Operand nativeContext, bool enter) + { + if (enter) + { + InstEmitSimdHelper.EnterArmFpMode(context, (flag) => + { + Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag)))); + return context.Load(OperandType.I32, flagAddress); + }); + } + else + { + InstEmitSimdHelper.ExitArmFpMode(context, (flag, value) => + { + Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag)))); + context.Store(flagAddress, value); + }); + } + } + + /// + /// Generates a function. + /// + /// function + 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())); + + EmitSyncFpContext(context, nativeContext, true); + + 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); + + EmitSyncFpContext(context, nativeContext, false); + + 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, RuntimeInformation.ProcessArchitecture).Map(); + } + + /// + /// Generates a function. + /// + /// function + private WrapperFunction GenerateContextWrapper() + { + var context = new EmitterContext(); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestMethod = context.LoadArgument(OperandType.I64, 1); + + EmitSyncFpContext(context, nativeContext, true); + Operand returnValue = context.Call(guestMethod, OperandType.I64, nativeContext); + EmitSyncFpContext(context, nativeContext, false); + + context.Return(returnValue); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.I64; + var argTypes = new[] { OperandType.I64, OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorTestMethods.cs b/src/ARMeilleure/Translation/TranslatorTestMethods.cs new file mode 100644 index 000000000..8cc7a3cf8 --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorTestMethods.cs @@ -0,0 +1,147 @@ +using ARMeilleure.CodeGen.X86; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Runtime.InteropServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + public static class TranslatorTestMethods + { + public delegate int FpFlagsPInvokeTest(IntPtr managedMethod); + + private static bool SetPlatformFtz(EmitterContext context, bool ftz) + { + if (Optimizations.UseSse2) + { + Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr); + + if (ftz) + { + mxcsr = context.BitwiseOr(mxcsr, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm))); + } + else + { + mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)Mxcsr.Ftz)); + } + + context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr); + + return true; + } + else if (Optimizations.UseAdvSimd) + { + Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr); + + if (ftz) + { + fpcr = context.BitwiseOr(fpcr, Const((int)FPCR.Fz)); + } + else + { + fpcr = context.BitwiseAnd(fpcr, Const(~(int)FPCR.Fz)); + } + + context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr); + + return true; + } + else + { + return false; + } + } + + private static Operand FpBitsToInt(EmitterContext context, Operand fp) + { + Operand vec = context.VectorInsert(context.VectorZero(), fp, 0); + return context.VectorExtract(OperandType.I32, vec, 0); + } + + public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest() + { + EmitterContext context = new(); + + Operand methodAddress = context.Copy(context.LoadArgument(OperandType.I64, 0)); + + // Verify that default dotnet fp state does not flush to zero. + // This is required for SoftFloat to function. + + // Denormal + zero != 0 + + Operand denormal = ConstF(BitConverter.Int32BitsToSingle(1)); // 1.40129846432e-45 + Operand zeroF = ConstF(0f); + Operand zero = Const(0); + + Operand result = context.Add(zeroF, denormal); + + // Must not be zero. + + Operand correct1Label = Label(); + + context.BranchIfFalse(correct1Label, context.ICompareEqual(FpBitsToInt(context, result), zero)); + + context.Return(Const(1)); + + context.MarkLabel(correct1Label); + + // Set flush to zero flag. If unsupported by the backend, just return true. + + if (!SetPlatformFtz(context, true)) + { + context.Return(Const(0)); + } + + // Denormal + zero == 0 + + Operand resultFz = context.Add(zeroF, denormal); + + // Must equal zero. + + Operand correct2Label = Label(); + + context.BranchIfTrue(correct2Label, context.ICompareEqual(FpBitsToInt(context, resultFz), zero)); + + SetPlatformFtz(context, false); + + context.Return(Const(2)); + + context.MarkLabel(correct2Label); + + // Call a managed method. This method should not change Fz state. + + context.Call(methodAddress, OperandType.None); + + // Denormal + zero == 0 + + Operand resultFz2 = context.Add(zeroF, denormal); + + // Must equal zero. + + Operand correct3Label = Label(); + + context.BranchIfTrue(correct3Label, context.ICompareEqual(FpBitsToInt(context, resultFz2), zero)); + + SetPlatformFtz(context, false); + + context.Return(Const(3)); + + context.MarkLabel(correct3Label); + + // Success. + + SetPlatformFtz(context, false); + + context.Return(Const(0)); + + // Compile and return the function. + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] { OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + } +} diff --git a/src/LibRyujinx.NativeSample/LibRyujinx.NativeSample.csproj b/src/LibRyujinx.NativeSample/LibRyujinx.NativeSample.csproj new file mode 100644 index 000000000..249f81552 --- /dev/null +++ b/src/LibRyujinx.NativeSample/LibRyujinx.NativeSample.csproj @@ -0,0 +1,15 @@ + + + + Exe + net7.0 + enable + enable + true + + + + + + + diff --git a/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs b/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs new file mode 100644 index 000000000..e3d841fcc --- /dev/null +++ b/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace LibRyujinx.Sample +{ + internal static class LibRyujinxInterop + { + private const string dll = "LibRyujinx.dll"; + + [DllImport(dll, EntryPoint = "initialize")] + public extern static bool Initialize(IntPtr path); + + + [DllImport(dll, EntryPoint = "graphics_initialize")] + public extern static bool InitializeGraphics(GraphicsConfiguration graphicsConfiguration); + + [DllImport(dll, EntryPoint = "device_initialize")] + internal extern static bool InitializeDevice(); + + [DllImport(dll, EntryPoint = "graphics_initialize_renderer")] + internal extern static bool InitializeGraphicsRenderer(GraphicsBackend backend, NativeGraphicsInterop nativeGraphicsInterop); + + [DllImport(dll, EntryPoint = "device_load")] + internal extern static bool LoadApplication(IntPtr pathPtr); + + [DllImport(dll, EntryPoint = "graphics_renderer_run_loop")] + internal extern static void RunLoop(); + + [DllImport(dll, EntryPoint = "graphics_renderer_set_size")] + internal extern static void SetRendererSize(int width, int height); + + [DllImport(dll, EntryPoint = "graphics_renderer_set_swap_buffer_callback")] + internal extern static void SetSwapBuffersCallback(IntPtr swapBuffers); + + [DllImport(dll, EntryPoint = "graphics_renderer_set_vsync")] + internal extern static void SetVsyncState(bool enabled); + + [DllImport(dll, EntryPoint = "input_initialize")] + internal extern static void InitializeInput(int width, int height); + + [DllImport(dll, EntryPoint = "input_set_client_size")] + internal extern static void SetClientSize(int width, int height); + + [DllImport(dll, EntryPoint = "input_set_touch_point")] + internal extern static void SetTouchPoint(int x, int y); + + [DllImport(dll, EntryPoint = "input_release_touch_point")] + internal extern static void ReleaseTouchPoint(); + + [DllImport(dll, EntryPoint = "input_update")] + internal extern static void UpdateInput(); + + [DllImport(dll, EntryPoint = "input_set_button_pressed")] + public extern static void SetButtonPressed(GamepadButtonInputId button, IntPtr idPtr); + + [DllImport(dll, EntryPoint = "input_set_button_released")] + public extern static void SetButtonReleased(GamepadButtonInputId button, IntPtr idPtr); + + [DllImport(dll, EntryPoint = "input_set_stick_axis")] + public extern static void SetStickAxis(StickInputId stick, Vector2 axes, IntPtr idPtr); + + [DllImport(dll, EntryPoint = "input_connect_gamepad")] + public extern static IntPtr ConnectGamepad(int index); + } + + [StructLayout(LayoutKind.Sequential)] + public struct GraphicsConfiguration + { + public float ResScale = 1f; + public float MaxAnisotropy = -1; + public bool FastGpuTime = true; + public bool Fast2DCopy = true; + public bool EnableMacroJit = false; + public bool EnableMacroHLE = true; + public bool EnableShaderCache = true; + public bool EnableTextureRecompression = false; + public BackendThreading BackendThreading = BackendThreading.Auto; + public AspectRatio AspectRatio = AspectRatio.Fixed16x9; + + public GraphicsConfiguration() + { + } + } + + public enum GraphicsBackend + { + Vulkan, + OpenGl + } + public enum BackendThreading + { + Auto, + Off, + On + } + + public struct NativeGraphicsInterop + { + public IntPtr GlGetProcAddress; + public IntPtr VkNativeContextLoader; + public IntPtr VkCreateSurface; + public IntPtr VkRequiredExtensions; + public int VkRequiredExtensionsCount; + } + public enum AspectRatio + { + Fixed4x3, + Fixed16x9, + Fixed16x10, + Fixed21x9, + Fixed32x9, + Stretched + } + + /// + /// Represent a button from a gamepad. + /// + public enum GamepadButtonInputId : byte + { + Unbound, + A, + B, + X, + Y, + LeftStick, + RightStick, + LeftShoulder, + RightShoulder, + + // Likely axis + LeftTrigger, + // Likely axis + RightTrigger, + + DpadUp, + DpadDown, + DpadLeft, + DpadRight, + + // Special buttons + + Minus, + Plus, + + Back = Minus, + Start = Plus, + + Guide, + Misc1, + + // Xbox Elite paddle + Paddle1, + Paddle2, + Paddle3, + Paddle4, + + // PS5 touchpad button + Touchpad, + + // Virtual buttons for single joycon + SingleLeftTrigger0, + SingleRightTrigger0, + + SingleLeftTrigger1, + SingleRightTrigger1, + + Count + } + + public enum StickInputId : byte + { + Unbound, + Left, + Right, + + Count + } +} diff --git a/src/LibRyujinx.NativeSample/NativeWindow.cs b/src/LibRyujinx.NativeSample/NativeWindow.cs new file mode 100644 index 000000000..12ff7af24 --- /dev/null +++ b/src/LibRyujinx.NativeSample/NativeWindow.cs @@ -0,0 +1,249 @@ +using LibRyujinx.Sample; +using OpenTK.Graphics.OpenGL; +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using System.Runtime.InteropServices; + +namespace LibRyujinx.NativeSample +{ + internal class NativeWindow : OpenTK.Windowing.Desktop.NativeWindow + { + private nint del; + public delegate void SwapBuffersCallback(); + public delegate IntPtr GetProcAddress(string name); + public delegate IntPtr CreateSurface(IntPtr instance); + + private bool _run; + private bool _isVulkan; + private Vector2 _lastPosition; + private bool _mousePressed; + private nint _gamepadIdPtr; + private string? _gamepadId; + + public NativeWindow(NativeWindowSettings nativeWindowSettings) : base(nativeWindowSettings) + { + _isVulkan = true; + } + + internal unsafe void Start(string gamePath) + { + if (!_isVulkan) + { + MakeCurrent(); + } + + var getProcAddress = Marshal.GetFunctionPointerForDelegate(x => GLFW.GetProcAddress(x)); + var createSurface = Marshal.GetFunctionPointerForDelegate( x => + { + VkHandle surface; + GLFW.CreateWindowSurface(new VkHandle(x) ,this.WindowPtr, null, out surface); + + return surface.Handle; + }); + var vkExtensions = GLFW.GetRequiredInstanceExtensions(); + + + var pointers = new IntPtr[vkExtensions.Length]; + for (int i = 0; i < vkExtensions.Length; i++) + { + pointers[i] = Marshal.StringToHGlobalAnsi(vkExtensions[i]); + } + + fixed (IntPtr* ptr = pointers) + { + var nativeGraphicsInterop = new NativeGraphicsInterop() + { + GlGetProcAddress = getProcAddress, + VkRequiredExtensions = (nint)ptr, + VkRequiredExtensionsCount = pointers.Length, + VkCreateSurface = createSurface + }; + var success = LibRyujinxInterop.InitializeGraphicsRenderer(_isVulkan ? GraphicsBackend.Vulkan : GraphicsBackend.OpenGl, nativeGraphicsInterop); + success = LibRyujinxInterop.InitializeDevice(); + LibRyujinxInterop.InitializeInput(ClientSize.X, ClientSize.Y); + + var path = Marshal.StringToHGlobalAnsi(gamePath); + var loaded = LibRyujinxInterop.LoadApplication(path); + LibRyujinxInterop.SetRendererSize(Size.X, Size.Y); + Marshal.FreeHGlobal(path); + } + + _gamepadIdPtr = LibRyujinxInterop.ConnectGamepad(0); + _gamepadId = Marshal.PtrToStringAnsi(_gamepadIdPtr); + + if (!_isVulkan) + { + Context.MakeNoneCurrent(); + } + + _run = true; + var thread = new Thread(new ThreadStart(RunLoop)); + thread.Start(); + + UpdateLoop(); + + thread.Join(); + + foreach(var ptr in pointers) + { + Marshal.FreeHGlobal(ptr); + } + + Marshal.FreeHGlobal(_gamepadIdPtr); + } + + public void RunLoop() + { + del = Marshal.GetFunctionPointerForDelegate(SwapBuffers); + LibRyujinxInterop.SetSwapBuffersCallback(del); + + if (!_isVulkan) + { + MakeCurrent(); + + Context.SwapInterval = 0; + } + + /* Task.Run(async () => + { + await Task.Delay(1000); + + LibRyujinxInterop.SetVsyncState(true); + });*/ + + LibRyujinxInterop.RunLoop(); + + _run = false; + + if (!_isVulkan) + { + Context.MakeNoneCurrent(); + } + } + + private void SwapBuffers() + { + if (!_isVulkan) + { + this.Context.SwapBuffers(); + } + } + + protected override void OnMouseMove(MouseMoveEventArgs e) + { + base.OnMouseMove(e); + _lastPosition = e.Position; + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + if(e.Button == MouseButton.Left) + { + _mousePressed = true; + } + } + + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + + if (_run) + { + LibRyujinxInterop.SetRendererSize(e.Width, e.Height); + LibRyujinxInterop.SetClientSize(e.Width, e.Height); + } + } + + protected override void OnMouseUp(MouseButtonEventArgs e) + { + base.OnMouseUp(e); + if (e.Button == MouseButton.Left) + { + _mousePressed = false; + } + } + + protected override void OnKeyUp(KeyboardKeyEventArgs e) + { + base.OnKeyUp(e); + + if (_gamepadIdPtr != IntPtr.Zero) + { + var key = GetKeyMapping(e.Key); + + LibRyujinxInterop.SetButtonReleased(key, _gamepadIdPtr); + } + } + + protected override void OnKeyDown(KeyboardKeyEventArgs e) + { + base.OnKeyDown(e); + + if (_gamepadIdPtr != IntPtr.Zero) + { + var key = GetKeyMapping(e.Key); + + LibRyujinxInterop.SetButtonPressed(key, _gamepadIdPtr); + } + } + + public void UpdateLoop() + { + while(_run) + { + ProcessWindowEvents(true); + // ProcessInputEvents(); + ProcessWindowEvents(IsEventDriven); + if (_mousePressed) + { + LibRyujinxInterop.SetTouchPoint((int)_lastPosition.X, (int)_lastPosition.Y); + } + else + { + LibRyujinxInterop.ReleaseTouchPoint(); + } + + LibRyujinxInterop.UpdateInput(); + + Thread.Sleep(1); + } + } + + public GamepadButtonInputId GetKeyMapping(Keys key) + { + if(_keyMapping.TryGetValue(key, out var mapping)) + { + return mapping; + } + + return GamepadButtonInputId.Unbound; + } + + private Dictionary _keyMapping = new Dictionary() + { + {Keys.A, GamepadButtonInputId.A }, + {Keys.S, GamepadButtonInputId.B }, + {Keys.Z, GamepadButtonInputId.X }, + {Keys.X, GamepadButtonInputId.Y }, + {Keys.Equal, GamepadButtonInputId.Plus }, + {Keys.Minus, GamepadButtonInputId.Minus }, + {Keys.Q, GamepadButtonInputId.LeftShoulder }, + {Keys.D1, GamepadButtonInputId.LeftTrigger }, + {Keys.W, GamepadButtonInputId.RightShoulder }, + {Keys.D2, GamepadButtonInputId.RightTrigger }, + {Keys.E, GamepadButtonInputId.LeftStick }, + {Keys.R, GamepadButtonInputId.RightStick }, + {Keys.Up, GamepadButtonInputId.DpadUp }, + {Keys.Down, GamepadButtonInputId.DpadDown }, + {Keys.Left, GamepadButtonInputId.DpadLeft }, + {Keys.Right, GamepadButtonInputId.DpadRight }, + {Keys.U, GamepadButtonInputId.SingleLeftTrigger0 }, + {Keys.D7, GamepadButtonInputId.SingleLeftTrigger1 }, + {Keys.O, GamepadButtonInputId.SingleRightTrigger0 }, + {Keys.D9, GamepadButtonInputId.SingleRightTrigger1 } + }; + } +} diff --git a/src/LibRyujinx.NativeSample/Program.cs b/src/LibRyujinx.NativeSample/Program.cs new file mode 100644 index 000000000..d39a64a94 --- /dev/null +++ b/src/LibRyujinx.NativeSample/Program.cs @@ -0,0 +1,33 @@ +using LibRyujinx.Sample; +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; + +namespace LibRyujinx.NativeSample +{ + internal class Program + { + static void Main(string[] args) + { + if (args.Length > 0) + { + var success = LibRyujinxInterop.Initialize(IntPtr.Zero); + success = LibRyujinxInterop.InitializeGraphics(new GraphicsConfiguration()); + var nativeWindowSettings = new NativeWindowSettings() + { + Size = new Vector2i(800, 600), + Title = "Ryujinx", + API = ContextAPI.NoAPI, + IsEventDriven = false, + // This is needed to run on macos + Flags = ContextFlags.ForwardCompatible, + }; + + using var window = new NativeWindow(nativeWindowSettings); + + window.IsVisible = true; + window.Start(args[0]); + } + } + } +} \ No newline at end of file diff --git a/src/LibRyujinx/Android/AndroidLogTarget.cs b/src/LibRyujinx/Android/AndroidLogTarget.cs new file mode 100644 index 000000000..c32cc261a --- /dev/null +++ b/src/LibRyujinx/Android/AndroidLogTarget.cs @@ -0,0 +1,49 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging.Formatters; +using Ryujinx.Common.Logging.Targets; +using System; + +namespace LibRyujinx +{ + public class AndroidLogTarget : ILogTarget + { + private readonly string _name; + private readonly DefaultLogFormatter _formatter; + + string ILogTarget.Name { get => _name; } + + public AndroidLogTarget(string name) + { + _name = name; + _formatter = new DefaultLogFormatter(); + } + + public void Log(object sender, LogEventArgs args) + { + Logcat.AndroidLogPrint(GetLogLevel(args.Level), _name, _formatter.Format(args)); + } + + private static Logcat.LogLevel GetLogLevel(LogLevel logLevel) + { + return logLevel switch + { + LogLevel.Debug => Logcat.LogLevel.Debug, + LogLevel.Stub => Logcat.LogLevel.Info, + LogLevel.Info => Logcat.LogLevel.Info, + LogLevel.Warning => Logcat.LogLevel.Warn, + LogLevel.Error => Logcat.LogLevel.Error, + LogLevel.Guest => Logcat.LogLevel.Info, + LogLevel.AccessLog => Logcat.LogLevel.Info, + LogLevel.Notice => Logcat.LogLevel.Info, + LogLevel.Trace => Logcat.LogLevel.Verbose, + _ => throw new NotImplementedException(), + + }; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/LibRyujinx/Android/AndroidUiHandler.cs b/src/LibRyujinx/Android/AndroidUiHandler.cs new file mode 100644 index 000000000..945feb0f4 --- /dev/null +++ b/src/LibRyujinx/Android/AndroidUiHandler.cs @@ -0,0 +1,136 @@ +using LibHac.Tools.Fs; +using Ryujinx.Common.Logging; +using Ryujinx.HLE; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.HLE.Ui; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace LibRyujinx.Android +{ + internal class AndroidUiHandler : IHostUiHandler, IDisposable + { + public IHostUiTheme HostUiTheme => throw new NotImplementedException(); + + public ManualResetEvent _waitEvent; + public ManualResetEvent _responseEvent; + private bool _isDisposed; + private bool _isOkPressed; + private long _input; + + public AndroidUiHandler() + { + _waitEvent = new ManualResetEvent(false); + _responseEvent = new ManualResetEvent(false); + } + + public IDynamicTextInputHandler CreateDynamicTextInputHandler() + { + throw new NotImplementedException(); + } + + public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText) + { + LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString(title ?? "")); + LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(message ?? "")); + LibRyujinx.setUiHandlerType(1); + + _responseEvent.Reset(); + Set(); + _responseEvent.WaitOne(); + + return _isOkPressed; + } + + public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText) + { + LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString("Software Keyboard")); + LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(args.HeaderText ?? "")); + LibRyujinx.setUiHandlerWatermark(LibRyujinx.storeString(args.GuideText ?? "")); + LibRyujinx.setUiHandlerSubtitle(LibRyujinx.storeString(args.SubtitleText ?? "")); + LibRyujinx.setUiHandlerInitialText(LibRyujinx.storeString(args.InitialText ?? "")); + LibRyujinx.setUiHandlerMinLength(args.StringLengthMin); + LibRyujinx.setUiHandlerMaxLength(args.StringLengthMax); + LibRyujinx.setUiHandlerType(2); + LibRyujinx.setUiHandlerKeyboardMode((int)args.KeyboardMode); + + _responseEvent.Reset(); + Set(); + _responseEvent.WaitOne(); + + userText = _input != -1 ? LibRyujinx.GetStoredString(_input) : ""; + + return _isOkPressed; + } + + public bool DisplayMessageDialog(string title, string message) + { + LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString(title ?? "")); + LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(message ?? "")); + LibRyujinx.setUiHandlerType(1); + + _responseEvent.Reset(); + Set(); + _responseEvent.WaitOne(); + + return _isOkPressed; + } + + public bool DisplayMessageDialog(ControllerAppletUiArgs args) + { + string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; + + string message = $"Application requests **{playerCount}** player(s) with:\n\n" + + $"**TYPES:** {args.SupportedStyles}\n\n" + + $"**PLAYERS:** {string.Join(", ", args.SupportedPlayers)}\n\n" + + (args.IsDocked ? "Docked mode set. `Handheld` is also invalid.\n\n" : "") + + "_Please reconfigure Input now and then press OK._"; + + return DisplayMessageDialog("Controller Applet", message); + } + + public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value) + { + // throw new NotImplementedException(); + } + + internal void Wait() + { + if (_isDisposed) + return; + _waitEvent.Reset(); + _waitEvent.WaitOne(); + _waitEvent.Reset(); + } + + internal void Set() + { + if (_isDisposed) + return; + _waitEvent.Set(); + } + + internal void SetResponse(bool isOkPressed, long input) + { + if (_isDisposed) + return; + _isOkPressed = isOkPressed; + _input = input; + _responseEvent.Set(); + } + + public void Dispose() + { + _isDisposed = true; + _waitEvent.Set(); + _waitEvent.Set(); + _responseEvent.Dispose(); + _waitEvent.Dispose(); + } + } +} diff --git a/src/LibRyujinx/Android/Audio/Oboe/OboeAudioBuffer.cs b/src/LibRyujinx/Android/Audio/Oboe/OboeAudioBuffer.cs new file mode 100644 index 000000000..aad74115f --- /dev/null +++ b/src/LibRyujinx/Android/Audio/Oboe/OboeAudioBuffer.cs @@ -0,0 +1,18 @@ +namespace LibRyujinx.Shared.Audio.Oboe +{ + internal class OboeAudioBuffer + { + public readonly ulong DriverIdentifier; + public readonly ulong SampleCount; + public readonly byte[] Data; + public ulong SamplePlayed; + + public OboeAudioBuffer(ulong driverIdentifier, byte[] data, ulong sampleCount) + { + DriverIdentifier = driverIdentifier; + Data = data; + SampleCount = sampleCount; + SamplePlayed = 0; + } + } +} diff --git a/src/LibRyujinx/Android/Audio/Oboe/OboeHardwareDeviceDriver.cs b/src/LibRyujinx/Android/Audio/Oboe/OboeHardwareDeviceDriver.cs new file mode 100644 index 000000000..7377b171e --- /dev/null +++ b/src/LibRyujinx/Android/Audio/Oboe/OboeHardwareDeviceDriver.cs @@ -0,0 +1,105 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace LibRyujinx.Shared.Audio.Oboe +{ + internal class OboeHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + + public OboeHardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + } + + public static bool IsSupported => true; + + 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 Oboe backend!"); + } + + OboeHardwareDeviceSession session = new OboeHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + + _sessions.TryAdd(session, 0); + + return session; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (OboeHardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + _pauseEvent.Dispose(); + + _sessions.Clear(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return sampleFormat != SampleFormat.Adpcm; + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Output; + } + } +} \ No newline at end of file diff --git a/src/LibRyujinx/Android/Audio/Oboe/OboeHardwareDeviceSession.cs b/src/LibRyujinx/Android/Audio/Oboe/OboeHardwareDeviceSession.cs new file mode 100644 index 000000000..76b41d802 --- /dev/null +++ b/src/LibRyujinx/Android/Audio/Oboe/OboeHardwareDeviceSession.cs @@ -0,0 +1,189 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace LibRyujinx.Shared.Audio.Oboe +{ + internal class OboeHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private OboeHardwareDeviceDriver _driver; + private bool _isClosed; + private bool _isWorkerActive; + private Queue _queuedBuffers; + private bool _isActive; + private ulong _playedSampleCount; + private Thread _workerThread; + private ManualResetEvent _updateRequiredEvent; + private IntPtr _session; + private object _queueLock = new object(); + private object _trackLock = new object(); + + public OboeHardwareDeviceSession(OboeHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _isActive = false; + _playedSampleCount = 0; + _isWorkerActive = true; + _queuedBuffers = new Queue(); + _updateRequiredEvent = driver.GetUpdateRequiredEvent(); + + _session = OboeInterop.CreateSession((int)requestedSampleFormat, requestedSampleRate, requestedChannelCount); + + _workerThread = new Thread(Update); + _workerThread.Name = $"HardwareDeviceSession.Android.Track"; + _workerThread.Start(); + + SetVolume(requestedVolume); + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public unsafe void Update(object ignored) + { + while (_isWorkerActive) + { + bool needUpdate = false; + + bool hasBuffer; + + OboeAudioBuffer buffer; + + lock (_queueLock) + { + hasBuffer = _queuedBuffers.TryPeek(out buffer); + } + + while (hasBuffer) + { + StartIfNotPlaying(); + + if (_isClosed) + break; + + fixed(byte* ptr = buffer.Data) + OboeInterop.WriteToSession(_session, (ulong)ptr, buffer.SampleCount); + + lock (_queueLock) + { + _playedSampleCount += buffer.SampleCount; + + _queuedBuffers.TryDequeue(out _); + } + + needUpdate = true; + + lock (_queueLock) + { + hasBuffer = _queuedBuffers.TryPeek(out buffer); + } + } + + if (needUpdate) + { + _updateRequiredEvent.Set(); + } + + // No work + Thread.Sleep(5); + } + + } + + public override void Dispose() + { + if (_session == 0) + return; + + PrepareToClose(); + + OboeInterop.CloseSession(_session); + + _session = 0; + } + + public override void PrepareToClose() + { + _isClosed = true; + _isWorkerActive = false; + _workerThread?.Join(); + Stop(); + } + + private void StartIfNotPlaying() + { + lock (_trackLock) + { + if (_isClosed) + return; + + if (OboeInterop.IsPlaying(_session) == 0) + { + Start(); + } + } + } + + public override void QueueBuffer(AudioBuffer buffer) + { + lock (_queueLock) + { + OboeAudioBuffer driverBuffer = new OboeAudioBuffer(buffer.DataPointer, buffer.Data, GetSampleCount(buffer)); + + _queuedBuffers.Enqueue(driverBuffer); + + if (_isActive) + { + StartIfNotPlaying(); + } + } + } + + public override float GetVolume() + { + return OboeInterop.GetSessionVolume(_session); + } + + public override ulong GetPlayedSampleCount() + { + lock (_queueLock) + { + return _playedSampleCount; + } + } + + public override void SetVolume(float volume) + { + volume = 1; + OboeInterop.SetSessionVolume(_session, volume); + } + + public override void Start() + { + if (_isClosed) + return; + + OboeInterop.StartSession(_session); + } + + public override void Stop() + { + OboeInterop.StopSession(_session); + } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + lock (_queueLock) + { + if (!_queuedBuffers.TryPeek(out OboeAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + } + } +} diff --git a/src/LibRyujinx/Android/Audio/Oboe/OboeInterop.cs b/src/LibRyujinx/Android/Audio/Oboe/OboeInterop.cs new file mode 100644 index 000000000..79d9ca43e --- /dev/null +++ b/src/LibRyujinx/Android/Audio/Oboe/OboeInterop.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace LibRyujinx.Shared.Audio.Oboe +{ + internal static partial class OboeInterop + { + private const string InteropLib = "libryujinxjni"; + + [LibraryImport(InteropLib, EntryPoint = "create_session")] + public static partial IntPtr CreateSession(int sample_format, + uint sample_rate, + uint channel_count); + + + [LibraryImport(InteropLib, EntryPoint = "start_session")] + public static partial void StartSession(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "stop_session")] + public static partial void StopSession(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "close_session")] + public static partial void CloseSession(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "set_session_volume")] + public static partial void SetSessionVolume(IntPtr session, float volume); + + [LibraryImport(InteropLib, EntryPoint = "get_session_volume")] + public static partial float GetSessionVolume(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "is_playing")] + public static partial int IsPlaying(IntPtr session); + + [LibraryImport(InteropLib, EntryPoint = "write_to_session")] + public static partial void WriteToSession(IntPtr session, ulong data, ulong samples); + } +} diff --git a/src/LibRyujinx/Android/JniExportedMethods.cs b/src/LibRyujinx/Android/JniExportedMethods.cs new file mode 100644 index 000000000..618da6a41 --- /dev/null +++ b/src/LibRyujinx/Android/JniExportedMethods.cs @@ -0,0 +1,799 @@ +using LibRyujinx.Jni; +using LibRyujinx.Jni.Pointers; +using LibRyujinx.Jni.Primitives; +using LibRyujinx.Jni.References; +using LibRyujinx.Jni.Values; +using LibRyujinx.Shared.Audio.Oboe; +using Rxmxnx.PInvoke; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging.Targets; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Input; +using Silk.NET.Core.Loader; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.KHR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +namespace LibRyujinx +{ + public static partial class LibRyujinx + { + private static ManualResetEvent _surfaceEvent; + private static long _surfacePtr; + private static long _window = 0; + + public static VulkanLoader? VulkanLoader { get; private set; } + + [DllImport("libryujinxjni")] + private extern static IntPtr getStringPointer(JEnvRef jEnv, JStringLocalRef s); + + [DllImport("libryujinxjni")] + private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch); + + [DllImport("libryujinxjni")] + internal extern static long storeString(string ch); + + [DllImport("libryujinxjni")] + internal extern static IntPtr getString(long id); + + [DllImport("libryujinxjni")] + internal extern static long setUiHandlerTitle(long title); + + [DllImport("libryujinxjni")] + internal extern static long setUiHandlerMessage(long message); + [DllImport("libryujinxjni")] + internal extern static long setUiHandlerWatermark(long watermark); + [DllImport("libryujinxjni")] + internal extern static long setUiHandlerInitialText(long text); + [DllImport("libryujinxjni")] + internal extern static long setUiHandlerSubtitle(long text); + + [DllImport("libryujinxjni")] + internal extern static long setUiHandlerType(int type); + + [DllImport("libryujinxjni")] + internal extern static long setUiHandlerKeyboardMode(int mode); + + [DllImport("libryujinxjni")] + internal extern static long setUiHandlerMinLength(int lenght); + + [DllImport("libryujinxjni")] + internal extern static long setUiHandlerMaxLength(int lenght); + + internal static string GetStoredString(long id) + { + var pointer = getString(id); + if (pointer != IntPtr.Zero) + { + var str = Marshal.PtrToStringAnsi(pointer) ?? ""; + + Marshal.FreeHGlobal(pointer); + return str; + } + + return ""; + } + + [DllImport("libryujinxjni")] + internal extern static void setRenderingThread(); + + [DllImport("libryujinxjni")] + internal extern static void debug_break(int code); + + [DllImport("libryujinxjni")] + internal extern static void onFrameEnd(double time); + + [DllImport("libryujinxjni")] + internal extern static void setProgressInfo(IntPtr info, float progress); + + [DllImport("libryujinxjni")] + internal extern static void setCurrentTransform(long native_window, int transform); + + public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance); + + [UnmanagedCallersOnly(EntryPoint = "JNI_OnLoad")] + internal static int LoadLibrary(JavaVMRef vm, IntPtr unknown) + { + return 0x00010006; //JNI_VERSION_1_6 + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")] + public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JLong jpathId) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + PlatformInfo.IsBionic = true; + + Logger.AddTarget( + new AsyncLogTargetWrapper( + new AndroidLogTarget("RyujinxLog"), + 1000, + AsyncLogTargetOverflowAction.Block + )); + + var path = GetStoredString(jpathId); + + var init = Initialize(path); + + _surfaceEvent?.Set(); + + _surfaceEvent = new ManualResetEvent(false); + + return init; + } + + private static string? GetString(JEnvRef jEnv, JStringLocalRef jString) + { + var stringPtr = getStringPointer(jEnv, jString); + + var s = Marshal.PtrToStringAnsi(stringPtr); + Marshal.FreeHGlobal(stringPtr); + return s; + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceReloadFilesystem")] + public static void JniReloadFileSystem() + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + SwitchDevice?.ReloadFileSystem(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInitialize")] + public static JBoolean JniInitializeDeviceNative(JEnvRef jEnv, + JObjectLocalRef jObj, + JBoolean isHostMapped, + JBoolean useNce, + JInt systemLanguage, + JInt regionCode, + JBoolean enableVsync, + JBoolean enableDockedMode, + JBoolean enablePtc, + JBoolean enableInternetAccess, + JLong timeZoneId, + JBoolean ignoreMissingServices) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + AudioDriver = new OpenALHardwareDeviceDriver();//new OboeHardwareDeviceDriver(); + return InitializeDevice(isHostMapped, + useNce, + (SystemLanguage)(int)systemLanguage, + (RegionCode)(int)regionCode, + enableVsync, + enableDockedMode, + enablePtc, + enableInternetAccess, + GetStoredString(timeZoneId), + ignoreMissingServices); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFifo")] + public static JDouble JniGetGameFifo(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var stats = SwitchDevice.EmulationContext?.Statistics.GetFifoPercent() ?? 0; + + return stats; + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameTime")] + public static JDouble JniGetGameFrameTime(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameTime() ?? 0; + + return stats; + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameRate")] + public static JDouble JniGetGameFrameRate(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameRate() ?? 0; + + return stats; + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoad")] + public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + if (SwitchDevice?.EmulationContext == null) + { + return false; + } + + var path = GetString(jEnv, pathPtr); + + return LoadApplication(path); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLaunchMiiEditor")] + public static JBoolean JniLaunchMiiEditApplet(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + if (SwitchDevice?.EmulationContext == null) + { + return false; + } + + return LaunchMiiEditApplet(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")] + public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong titleId) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var list = GetDlcContentList(GetStoredString(pathPtr), (ulong)(long)titleId); + + debug_break(4); + + return CreateStringArray(jEnv, list); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcTitleId")] + public static JLong JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong ncaPath) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + return storeString(GetDlcTitleId(GetStoredString(pathPtr), GetStoredString(ncaPath))); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceSignalEmulationClose")] + public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + SignalEmulationClose(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceCloseEmulation")] + public static void JniCloseEmulationNative(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + CloseEmulation(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")] + public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JInt type, JInt updateDescriptor) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + if (SwitchDevice?.EmulationContext == null) + { + return false; + } + + var stream = OpenFile(descriptor); + var update = updateDescriptor == -1 ? null : OpenFile(updateDescriptor); + + return LoadApplication(stream, (FileType)(int)type, update); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceVerifyFirmware")] + public static JLong JniVerifyFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + + var stream = OpenFile(descriptor); + + long stringHandle = -1; + + try + { + var version = VerifyFirmware(stream, isXci); + + if (version != null) + { + stringHandle = storeString(version.VersionString); + } + } + catch(Exception _) + { + + } + + return stringHandle; + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInstallFirmware")] + public static void JniInstallFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + + var stream = OpenFile(descriptor); + + InstallFirmware(stream, isXci); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetInstalledFirmwareVersion")] + public static JLong JniGetInstalledFirmwareVersion(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + + var version = GetInstalledFirmwareVersion(); + long stringHandle = -1; + + if (version != String.Empty) + { + stringHandle = storeString(version); + } + + return stringHandle; + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")] + public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + JEnvValue value = jEnv.Environment; + ref JNativeInterface jInterface = ref value.Functions; + IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer; + IntPtr getFieldIdPtr = jInterface.GetFieldIdPointer; + IntPtr getIntFieldPtr = jInterface.GetIntFieldPointer; + IntPtr getLongFieldPtr = jInterface.GetLongFieldPointer; + IntPtr getFloatFieldPtr = jInterface.GetFloatFieldPointer; + IntPtr getBooleanFieldPtr = jInterface.GetBooleanFieldPointer; + + var getObjectClass = getObjectClassPtr.GetUnsafeDelegate(); + var getFieldId = getFieldIdPtr.GetUnsafeDelegate(); + var getLongField = getLongFieldPtr.GetUnsafeDelegate(); + var getIntField = getIntFieldPtr.GetUnsafeDelegate(); + var getBooleanField = getBooleanFieldPtr.GetUnsafeDelegate(); + var getFloatField = getFloatFieldPtr.GetUnsafeDelegate(); + + var jobject = getObjectClass(jEnv, graphicObject); + + GraphicsConfiguration graphicsConfiguration = new() + { + EnableShaderCache = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableShaderCache"), GetCCharSequence("Z"))), + EnableMacroHLE = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroHLE"), GetCCharSequence("Z"))), + EnableMacroJit = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroJit"), GetCCharSequence("Z"))), + EnableTextureRecompression = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableTextureRecompression"), GetCCharSequence("Z"))), + Fast2DCopy = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("Fast2DCopy"), GetCCharSequence("Z"))), + FastGpuTime = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("FastGpuTime"), GetCCharSequence("Z"))), + ResScale = getFloatField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("ResScale"), GetCCharSequence("F"))), + MaxAnisotropy = getFloatField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("MaxAnisotropy"), GetCCharSequence("F"))), + BackendThreading = (BackendThreading)(int)getIntField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("BackendThreading"), GetCCharSequence("I"))) + }; + SearchPathContainer.Platform = UnderlyingPlatform.Android; + return InitializeGraphics(graphicsConfiguration); + } + + private static CCharSequence GetCCharSequence(string s) + { + return Encoding.UTF8.GetBytes(s).AsSpan(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")] + public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr, JLong window) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + _surfacePtr = surfacePtr; + _window = window; + + _surfaceEvent.Set(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitializeRenderer")] + public unsafe static JBoolean JniInitializeGraphicsRendererNative(JEnvRef jEnv, + JObjectLocalRef jObj, + JArrayLocalRef extensionsArray, + JLong driverHandle) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + if (Renderer != null) + { + return false; + } + + JEnvValue value = jEnv.Environment; + ref JNativeInterface jInterface = ref value.Functions; + IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer; + IntPtr getFieldIdPtr = jInterface.GetFieldIdPointer; + IntPtr getLongFieldPtr = jInterface.GetLongFieldPointer; + IntPtr getArrayLengthPtr = jInterface.GetArrayLengthPointer; + IntPtr getObjectArrayElementPtr = jInterface.GetObjectArrayElementPointer; + IntPtr getObjectFieldPtr = jInterface.GetObjectFieldPointer; + + var getObjectClass = getObjectClassPtr.GetUnsafeDelegate(); + var getFieldId = getFieldIdPtr.GetUnsafeDelegate(); + var getArrayLength = getArrayLengthPtr.GetUnsafeDelegate(); + var getObjectArrayElement = getObjectArrayElementPtr.GetUnsafeDelegate(); + var getLongField = getLongFieldPtr.GetUnsafeDelegate(); + var getObjectField = getObjectFieldPtr.GetUnsafeDelegate(); + + List extensions = new(); + + var count = getArrayLength(jEnv, extensionsArray); + + for (int i = 0; i < count; i++) + { + var obj = getObjectArrayElement(jEnv, extensionsArray, i); + var ext = obj.Transform(); + + extensions.Add(GetString(jEnv, ext)); + } + + if ((long)driverHandle != 0) + { + VulkanLoader = new VulkanLoader((IntPtr)(long)driverHandle); + } + + CreateSurface createSurfaceFunc = instance => + { + _surfaceEvent.WaitOne(); + _surfaceEvent.Reset(); + + var api = VulkanLoader?.GetApi() ?? Vk.GetApi(); + if (api.TryGetInstanceExtension(new Instance(instance), out KhrAndroidSurface surfaceExtension)) + { + var createInfo = new AndroidSurfaceCreateInfoKHR + { + SType = StructureType.AndroidSurfaceCreateInfoKhr, + Window = (nint*)_surfacePtr, + }; + + var result = surfaceExtension.CreateAndroidSurface(new Instance(instance), createInfo, null, out var surface); + + return (nint)surface.Handle; + } + + return IntPtr.Zero; + }; + + return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray()); + } + + private static JArrayLocalRef CreateStringArray(JEnvRef jEnv, List strings) + { + JEnvValue value = jEnv.Environment; + ref JNativeInterface jInterface = ref value.Functions; + IntPtr newObjectArrayPtr = jInterface.NewObjectArrayPointer; + IntPtr findClassPtr = jInterface.FindClassPointer; + IntPtr setObjectArrayElementPtr = jInterface.SetObjectArrayElementPointer; + + var newObjectArray = newObjectArrayPtr.GetUnsafeDelegate(); + var findClass = findClassPtr.GetUnsafeDelegate(); + var setObjectArrayElement = setObjectArrayElementPtr.GetUnsafeDelegate(); + var array = newObjectArray(jEnv, strings.Count, findClass(jEnv, GetCCharSequence("java/lang/String")), CreateString(jEnv, "")._value); + + for (int i = 0; i < strings.Count; i++) + { + setObjectArrayElement(jEnv, array, i, CreateString(jEnv, strings[i])._value); + } + + return array; + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")] + public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + Renderer?.Window?.SetSize(width, height); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")] + public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + SetSwapBuffersCallback(() => + { + var time = SwitchDevice.EmulationContext.Statistics.GetGameFrameTime(); + onFrameEnd(time); + }); + RunLoop(); + } + + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_loggingSetEnabled")] + public static void JniSetLoggingEnabledNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt logLevel, JBoolean enabled) + { + Logger.SetEnable((LogLevel)(int)logLevel, enabled); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfoFromPath")] + public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var info = GetGameInfo(GetString(jEnv, path)); + return GetInfo(jEnv, info); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")] + public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JInt fileDescriptor, JLong extension) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + using var stream = OpenFile(fileDescriptor); + var ext = GetStoredString(extension); + var info = GetGameInfo(stream, ext.ToLower()); + return GetInfo(jEnv, info); + } + + private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo? info) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var javaClassName = GetCCharSequence("org/ryujinx/android/viewmodels/GameInfo"); + + JEnvValue value = jEnv.Environment; + ref JNativeInterface jInterface = ref value.Functions; + IntPtr findClassPtr = jInterface.FindClassPointer; + IntPtr newGlobalRefPtr = jInterface.NewGlobalRefPointer; + IntPtr getFieldIdPtr = jInterface.GetFieldIdPointer; + IntPtr getMethodPtr = jInterface.GetMethodIdPointer; + IntPtr newObjectPtr = jInterface.NewObjectPointer; + IntPtr setObjectFieldPtr = jInterface.SetObjectFieldPointer; + IntPtr setDoubleFieldPtr = jInterface.SetDoubleFieldPointer; + + + var findClass = findClassPtr.GetUnsafeDelegate(); + var newGlobalRef = newGlobalRefPtr.GetUnsafeDelegate(); + var getFieldId = getFieldIdPtr.GetUnsafeDelegate(); + var getMethod = getMethodPtr.GetUnsafeDelegate(); + var newObject = newObjectPtr.GetUnsafeDelegate(); + var setObjectField = setObjectFieldPtr.GetUnsafeDelegate(); + var setDoubleField = setDoubleFieldPtr.GetUnsafeDelegate(); + + var javaClass = findClass(jEnv, javaClassName); + var newGlobal = newGlobalRef(jEnv, javaClass._value); + var constructor = getMethod(jEnv, javaClass, GetCCharSequence(""), GetCCharSequence("()V")); + var newObj = newObject(jEnv, javaClass, constructor, 0); + + setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleName"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleName)._value); + setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleId"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleId)._value); + setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Developer"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Developer)._value); + setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Version"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Version)._value); + setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Icon"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, Convert.ToBase64String(info?.Icon ?? Array.Empty()))._value); + setDoubleField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("FileSize"), GetCCharSequence("D")), info?.FileSize ?? 0d); + + return newObj; + } + + private static JStringLocalRef CreateString(JEnvRef jEnv, string? s) + { + s ??= string.Empty; + + var ptr = Marshal.StringToHGlobalAnsi(s); + + var str = createString(jEnv, ptr); + + Marshal.FreeHGlobal(ptr); + + return str; + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetVsync")] + public static void JniSetVsyncStateNative(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean enabled) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + SetVsyncState(enabled); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSwapBufferCallback")] + public static void JniSetSwapBuffersCallbackNative(JEnvRef jEnv, JObjectLocalRef jObj, IntPtr swapBuffersCallback) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + _swapBuffersCallback = Marshal.GetDelegateForFunctionPointer(swapBuffersCallback); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputInitialize")] + public static void JniInitializeInput(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + InitializeInput(width, height); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetClientSize")] + public static void JniSetClientSize(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + SetClientSize(width, height); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetTouchPoint")] + public static void JniSetTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj, JInt x, JInt y) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + SetTouchPoint(x, y); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputReleaseTouchPoint")] + public static void JniReleaseTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + ReleaseTouchPoint(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputUpdate")] + public static void JniUpdateInput(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + UpdateInput(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")] + public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + SetButtonPressed((GamepadButtonInputId)(int)button, id); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonReleased")] + public static void JniSetButtonReleased(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + SetButtonReleased((GamepadButtonInputId)(int)button, id); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetAccelerometerData")] + public static void JniSetAccelerometerData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id) + { + var accel = new Vector3(x, y, z); + SetAccelerometerData(accel, id); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetGyroData")] + public static void JniSetGyroData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id) + { + var gryo = new Vector3(x, y, z); + SetGryoData(gryo, id); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")] + public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + SetStickAxis((StickInputId)(int)stick, new Vector2(float.IsNaN(x) ? 0 : x, float.IsNaN(y) ? 0 : y), id); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputConnectGamepad")] + public static JInt JniConnectGamepad(JEnvRef jEnv, JObjectLocalRef jObj, JInt index) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + return ConnectGamepad(index); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")] + public static JLong JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var userId = GetOpenedUser(); + + return storeString(userId); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")] + public static JLong JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var userId = GetStoredString(userIdPtr) ?? ""; + + return storeString(GetUserPicture(userId)); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserPicture")] + public static void JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef picturePtr) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var userId = GetString(jEnv, userIdPtr) ?? ""; + var picture = GetString(jEnv, picturePtr) ?? ""; + + SetUserPicture(userId, picture); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserName")] + public static JLong JniGetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var userId = GetStoredString(userIdPtr) ?? ""; + + return storeString(GetUserName(userId)); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserName")] + public static void JniSetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef userNamePtr) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var userId = GetString(jEnv, userIdPtr) ?? ""; + var userName = GetString(jEnv, userNamePtr) ?? ""; + + SetUserName(userId, userName); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetAllUsers")] + public static JArrayLocalRef JniGetAllUsers(JEnvRef jEnv, JObjectLocalRef jObj) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var users = GetAllUsers(); + + return CreateStringArray(jEnv, users.ToList()); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userAddUser")] + public static void JniAddUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userNamePtr, JStringLocalRef picturePtr) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var userName = GetString(jEnv, userNamePtr) ?? ""; + var picture = GetString(jEnv, picturePtr) ?? ""; + + AddUser(userName, picture); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userDeleteUser")] + public static void JniDeleteUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var userId = GetString(jEnv, userIdPtr) ?? ""; + + DeleteUser(userId); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetup")] + public static void JniSetupUiHandler(JEnvRef jEnv, JObjectLocalRef jObj) + { + SetupUiHandler(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerWait")] + public static void JniWaitUiHandler(JEnvRef jEnv, JObjectLocalRef jObj) + { + WaitUiHandler(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerStopWait")] + public static void JniStopUiHandlerWait(JEnvRef jEnv, JObjectLocalRef jObj) + { + StopUiHandlerWait(); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetResponse")] + public static void JniSetUiHandlerResponse(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean isOkPressed, JLong input) + { + SetUiHandlerResponse(isOkPressed, input); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")] + public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var userId = GetStoredString(userIdPtr) ?? ""; + + OpenUser(userId); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userCloseUser")] + public static void JniCloseUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr) + { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); + var userId = GetString(jEnv, userIdPtr) ?? ""; + + CloseUser(userId); + } + } + + internal static partial class Logcat + { + [LibraryImport("liblog", StringMarshalling = StringMarshalling.Utf8)] + private static partial void __android_log_print(LogLevel level, string? tag, string format, string args, IntPtr ptr); + + internal static void AndroidLogPrint(LogLevel level, string? tag, string message) => + __android_log_print(level, tag, "%s", message, IntPtr.Zero); + + internal enum LogLevel + { + Unknown = 0x00, + Default = 0x01, + Verbose = 0x02, + Debug = 0x03, + Info = 0x04, + Warn = 0x05, + Error = 0x06, + Fatal = 0x07, + Silent = 0x08, + } + } +} diff --git a/src/LibRyujinx/Jni/Delegates.cs b/src/LibRyujinx/Jni/Delegates.cs new file mode 100644 index 000000000..683109ade --- /dev/null +++ b/src/LibRyujinx/Jni/Delegates.cs @@ -0,0 +1,286 @@ +using LibRyujinx.Jni.Identifiers; +using LibRyujinx.Jni.Internal.Pointers; +using LibRyujinx.Jni.Pointers; +using LibRyujinx.Jni.Primitives; +using LibRyujinx.Jni.References; +using LibRyujinx.Jni.Values; +using System; + +namespace LibRyujinx.Jni +{ + internal delegate Int32 GetVersionDelegate(JEnvRef env); + + internal delegate JClassLocalRef DefineClassDelegate(JEnvRef env, CCharSequence name, JObjectLocalRef loader, IntPtr binaryData, Int32 len); + internal delegate JClassLocalRef FindClassDelegate(JEnvRef env, CCharSequence name); + + internal delegate JMethodId FromReflectedMethodDelegate(JEnvRef env, JObjectLocalRef method); + internal delegate JFieldId FromReflectedFieldIdDelegate(JEnvRef env, JObjectLocalRef field); + + internal delegate JObjectLocalRef ToReflectedMethodDelegate(JEnvRef env, JClassLocalRef jClass, JMethodId methodId, Boolean isStatic); + + internal delegate JClassLocalRef GetSuperclassDelegate(JEnvRef env, JClassLocalRef sub); + internal delegate Boolean IsAssignableFromDelegate(JEnvRef env, JClassLocalRef sub, JClassLocalRef sup); + + internal delegate JObjectLocalRef ToReflectedFieldIdDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId fieldId, Boolean isStatic); + + internal delegate JResult ThrowDelegate(JEnvRef env, JThrowableLocalRef obj); + internal delegate JResult ThrowNewDelegate(JEnvRef env, JClassLocalRef jClass, CCharSequence msg); + internal delegate JThrowableLocalRef ExceptionOccurredDelegate(JEnvRef env); + internal delegate void ExceptionDescribeDelegate(JEnvRef env); + internal delegate void ExceptionClearDelegate(JEnvRef env); + internal delegate void FatalErrorDelegate(JEnvRef env, CCharSequence msg); + + internal delegate JResult PushLocalFrameDelegate(JEnvRef env, Int32 capacity); + internal delegate JObjectLocalRef PopLocalFrameDelegate(JEnvRef env, JObjectLocalRef result); + + internal delegate JGlobalRef NewGlobalRefDelegate(JEnvRef env, JObjectLocalRef lref); + internal delegate void DeleteGlobalRefDelegate(JEnvRef env, JGlobalRef gref); + internal delegate void DeleteLocalRefDelegate(JEnvRef env, JObjectLocalRef lref); + internal delegate Boolean IsSameObjectDelegate(JEnvRef env, JObjectLocalRef obj1, JObjectLocalRef obj2); + internal delegate JObjectLocalRef NewLocalRefDelegate(JEnvRef env, JObjectLocalRef objRef); + internal delegate JResult EnsureLocalCapacityDelegate(JEnvRef env, Int32 capacity); + + internal delegate JObjectLocalRef AllocObjectDelegate(JEnvRef env, JClassLocalRef jClass); + internal delegate JObjectLocalRef NewObjectDelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod, IntPtr args); + internal delegate JObjectLocalRef NewObjectVDelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod, ArgIterator args); + internal delegate JObjectLocalRef NewObjectADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod, JValueSequence args); + + internal delegate JClassLocalRef GetObjectClassDelegate(JEnvRef env, JObjectLocalRef obj); + internal delegate Boolean IsInstanceOfDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass); + + internal delegate JMethodId GetMethodIdDelegate(JEnvRef env, JClassLocalRef jClass, CCharSequence name, CCharSequence signature); + + internal delegate JObjectLocalRef CallObjectMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate JObjectLocalRef CallObjectMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate JObjectLocalRef CallObjectMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + internal delegate JBoolean CallBooleanMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate JBoolean CallBooleanMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate JBoolean CallBooleanMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + internal delegate JByte CallByteMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate JByte CallByteMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate JByte CallByteMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + internal delegate JChar CallCharMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate JChar CallCharMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate JChar CallCharMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + internal delegate JShort CallShortMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate JShort CallShortMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate JShort CallShortMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + internal delegate JInt CallIntMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate JInt CallIntMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate JInt CallIntMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + internal delegate JLong CallLongMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate JLong CallLongMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate JLong CallLongMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + internal delegate JFloat CallFloatMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate JFloat CallFloatMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate JFloat CallFloatMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + internal delegate JDouble CallDoubleMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate JDouble CallDoubleMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate JDouble CallDoubleMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + internal delegate void CallVoidMethodDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, IntPtr args); + internal delegate void CallVoidMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, ArgIterator args); + internal delegate void CallVoidMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod, JValueSequence args); + + internal delegate JObjectLocalRef CallNonVirtualObjectMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JObjectLocalRef CallNonVirtualObjectMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JObjectLocalRef CallNonVirtualObjectMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JBoolean CallNonVirtualBooleanMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JBoolean CallNonVirtualBooleanMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JBoolean CallNonVirtualBooleanMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JByte CallNonVirtualByteMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JByte CallNonVirtualByteMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JByte CallNonVirtualByteMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JChar CallNonVirtualCharMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JChar CallNonVirtualCharMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JChar CallNonVirtualCharMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JShort CallNonVirtualShortMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JShort CallNonVirtualShortMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JShort CallNonVirtualShortMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JInt CallNonVirtualIntMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JInt CallNonVirtualIntMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JInt CallNonVirtualIntMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JLong CallNonVirtualLongMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JLong CallNonVirtualLongMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JLong CallNonVirtualLongMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JFloat CallNonVirtualFloatMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JFloat CallNonVirtualFloatMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JFloat CallNonVirtualFloatMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JDouble CallNonVirtualDoubleMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JDouble CallNonVirtualDoubleMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JDouble CallNonVirtualDoubleMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate void CallNonVirtualVoidMethodDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate void CallNonVirtualVoidMethodVDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate void CallNonVirtualVoidMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + + internal delegate JFieldId GetFieldIdDelegate(JEnvRef env, JClassLocalRef jclass, CCharSequence name, CCharSequence signature); + + internal delegate JObjectLocalRef GetObjectFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField); + internal delegate JBoolean GetBooleanFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField); + internal delegate JByte GetByteFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField); + internal delegate JChar GetCharFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField); + internal delegate JShort GetShortFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField); + internal delegate JInt GetIntFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField); + internal delegate JLong GetLongFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField); + internal delegate JFloat GetFloatFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField); + internal delegate JDouble GetDoubleFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField); + internal delegate void SetObjectFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JObjectLocalRef value); + internal delegate void SetBooleanFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JBoolean value); + internal delegate void SetByteFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JByte value); + internal delegate void SetCharFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JChar value); + internal delegate void SetShortFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JShort value); + internal delegate void SetIntFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JInt value); + internal delegate void SetLongFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JLong value); + internal delegate void SetFloatFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JFloat value); + internal delegate void SetDoubleFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JDouble value); + + internal delegate JMethodId GetStaticMethodIdDelegate(JEnvRef env, JClassLocalRef jClass, CCharSequence name, CCharSequence signature); + + internal delegate JObjectLocalRef CallStaticObjectMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JObjectLocalRef CallStaticObjectMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JObjectLocalRef CallStaticObjectMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JBoolean CallStaticBooleanMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JBoolean CallStaticBooleanMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JBoolean CallStaticBooleanMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JByte CallStaticByteMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JByte CallStaticByteMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JByte CallStaticByteMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JChar CallStaticCharMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JChar CallStaticCharMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JChar CallStaticCharMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JShort CallStaticShortMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JShort CallStaticShortMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JShort CallStaticShortMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JInt CallStaticIntMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JInt CallStaticIntMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JInt CallStaticIntMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JLong CallStaticLongMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JLong CallStaticLongMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JLong CallStaticLongMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JFloat CallStaticFloatMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JFloat CallStaticFloatMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JFloat CallStaticFloatMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate JDouble CallStaticDoubleMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate JDouble CallStaticDoubleMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate JDouble CallStaticDoubleMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + internal delegate void CallStaticVoidMethodDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, IntPtr args); + internal delegate void CallStaticVoidMethodVDelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, ArgIterator args); + internal delegate void CallStaticVoidMethodADelegate(JEnvRef env, JClassLocalRef jclass, JMethodId jMethod, JValueSequence args); + + internal delegate JFieldId GetStaticFieldIdDelegate(JEnvRef env, JClassLocalRef jclass, CCharSequence name, CCharSequence signature); + + internal delegate JObjectLocalRef GetStaticObjectFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField); + internal delegate JBoolean GetStaticBooleanFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField); + internal delegate JByte GetStaticByteFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField); + internal delegate JChar GetStaticCharFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField); + internal delegate JShort GetStaticShortFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField); + internal delegate JInt GetStaticIntFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField); + internal delegate JLong GetStaticLongFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField); + internal delegate JFloat GetStaticFloatFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField); + internal delegate JDouble GetStaticDoubleFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField); + internal delegate void SetStaticObjectFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField, JObjectLocalRef value); + internal delegate void SetStaticBooleanFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField, JBoolean value); + internal delegate void SetStaticByteFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField, JByte value); + internal delegate void SetStaticCharFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField, JChar value); + internal delegate void SetStaticShortFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField, JShort value); + internal delegate void SetStaticIntFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField, JInt value); + internal delegate void SetStaticLongFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField, JLong value); + internal delegate void SetStaticFloatFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField, JFloat value); + internal delegate void SetStaticDoubleFieldDelegate(JEnvRef env, JClassLocalRef jclass, JFieldId jField, JDouble value); + + internal delegate JStringLocalRef NewStringDelegate(JEnvRef env, JCharSequence unicode, Int32 length); + + internal delegate Int32 GetStringLengthDelegate(JEnvRef env, JStringLocalRef jString); + internal delegate JCharSequence GetStringCharsDelegate(JEnvRef env, JStringLocalRef jString, JBooleanRef isCopy); + internal delegate void ReleaseStringCharsDelegate(JEnvRef env, JStringLocalRef jString, JCharSequence chars); + + internal delegate JStringLocalRef NewStringUtfDelegate(JEnvRef env, CCharSequence unicode); + internal delegate Int32 GetStringUtfLengthDelegate(JEnvRef env, JStringLocalRef jString); + internal delegate CCharSequence GetStringUtfCharsDelegate(JEnvRef env, JStringLocalRef jString, JBooleanRef isCopy); + internal delegate void ReleaseStringUtfCharsDelegate(JEnvRef env, JStringLocalRef jString, CCharSequence chars); + + internal delegate Int32 GetArrayLengthDelegate(JEnvRef env, JArrayLocalRef array); + + internal delegate JArrayLocalRef NewObjectArrayDelegate(JEnvRef env, Int32 length, JClassLocalRef jClass, JObjectLocalRef init); + internal delegate JObjectLocalRef GetObjectArrayElementDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 index); + internal delegate void SetObjectArrayElementDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 index, JObjectLocalRef obj); + + internal delegate JArrayLocalRef NewBooleanArrayDelegate(JEnvRef env, Int32 length); + internal delegate JArrayLocalRef NewByteArrayDelegate(JEnvRef env, Int32 length); + internal delegate JArrayLocalRef NewCharArrayDelegate(JEnvRef env, Int32 length); + internal delegate JArrayLocalRef NewShortArrayDelegate(JEnvRef env, Int32 length); + internal delegate JArrayLocalRef NewIntArrayDelegate(JEnvRef env, Int32 length); + internal delegate JArrayLocalRef NewLongArrayDelegate(JEnvRef env, Int32 length); + internal delegate JArrayLocalRef NewFloatArrayDelegate(JEnvRef env, Int32 length); + internal delegate JArrayLocalRef NewDoubleArrayDelegate(JEnvRef env, Int32 length); + + internal delegate IntPtr GetBooleanArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy); + internal delegate IntPtr GetByteArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy); + internal delegate IntPtr GetCharArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy); + internal delegate IntPtr GetShortArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy); + internal delegate IntPtr GetIntArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy); + internal delegate IntPtr GetLongArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy); + internal delegate IntPtr GetFloatArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy); + internal delegate IntPtr GetDoubleArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy); + + internal delegate void ReleaseBooleanArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, IntPtr elements, JReleaseMode mode); + internal delegate void ReleaseByteArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, IntPtr elements, JReleaseMode mode); + internal delegate void ReleaseCharArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, IntPtr elements, JReleaseMode mode); + internal delegate void ReleaseShortArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, IntPtr elements, JReleaseMode mode); + internal delegate void ReleaseIntArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, IntPtr elements, JReleaseMode mode); + internal delegate void ReleaseLongArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, IntPtr elements, JReleaseMode mode); + internal delegate void ReleaseFloatArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, IntPtr elements, JReleaseMode mode); + internal delegate void ReleaseDoubleArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, IntPtr elements, JReleaseMode mode); + + internal delegate void GetBooleanArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void GetByteArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void GetCharArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void GetShortArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void GetIntArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void GetLongArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void GetFloatArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void GetDoubleArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void SetBooleanArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void SetByteArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void SetCharArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void SetShortArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void SetIntArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void SetLongArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void SetFloatArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + internal delegate void SetDoubleArrayRegionDelegate(JEnvRef env, JArrayLocalRef jArray, Int32 startIndex, Int32 length, IntPtr buffer); + + internal delegate JResult RegisterNativesDelegate(JEnvRef env, JClassLocalRef jClass, JNativeMethodSequence methods); + internal delegate JResult UnregisterNativesDelegate(JEnvRef env, JClassLocalRef jClass); + + internal delegate JResult MonitorEnterDelegate(JEnvRef env, JObjectLocalRef jClass); + internal delegate JResult MonitorExitDelegate(JEnvRef env, JObjectLocalRef jClass); + + internal delegate JResult GetJavaVMDelegate(JEnvRef env, ref JavaVMRef jvm); + + internal delegate void GetStringRegionDelegate(JEnvRef env, JStringLocalRef jString, Int32 startIndex, Int32 length, JCharSequence buffer); + internal delegate void GetStringUtfRegionDelegate(JEnvRef env, JStringLocalRef jString, Int32 startIndex, Int32 length, CCharSequence buffer); + + internal delegate IntPtr GetPrimitiveArrayCriticalDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy); + internal delegate void ReleasePrimitiveArrayCriticalDelegate(JEnvRef env, JArrayLocalRef jArray, IntPtr elements, JReleaseMode mode); + + internal delegate JCharSequence GetStringCriticalDelegate(JEnvRef env, JStringLocalRef jString, JBooleanRef isCopy); + internal delegate void ReleaseStringCriticalDelegate(JEnvRef env, JStringLocalRef jString, JCharSequence chars); + + internal delegate JWeakRef NewWeakGlobalRefDelegate(JEnvRef env, JObjectLocalRef obj); + internal delegate void DeleteWeakGlobalRefDelegate(JEnvRef env, JWeakRef jWeak); + + internal delegate Boolean ExceptionCheckDelegate(JEnvRef env); + + internal delegate JObjectLocalRef NewDirectByteBufferDelegate(JEnvRef env, IntPtr address, Int64 capacity); + internal delegate IntPtr GetDirectBufferAddressDelegate(JEnvRef env, JObjectLocalRef buffObj); + internal delegate Int64 GetDirectBufferCapacityDelegate(JEnvRef env, JObjectLocalRef buffObj); + + internal delegate JReferenceType GetObjectRefTypeDelegate(JEnvRef env, JObjectLocalRef obj); + + internal delegate JResult DestroyJavaVMDelegate(JavaVMRef vm); + internal delegate JResult AttachCurrentThreadDelegate(JavaVMRef vm, ref JEnvRef env, in JavaVMAttachArgs args); + internal delegate JResult DetachCurrentThreadDelegate(JavaVMRef vm); + + internal delegate JResult GetEnvDelegate(JavaVMRef vm, ref JEnvRef env, JInt version); + + internal delegate JResult AttachCurrentThreadAsDaemonDelegate(JavaVMRef vm, ref JEnvRef env, in JavaVMAttachArgs args); +} \ No newline at end of file diff --git a/src/LibRyujinx/Jni/Identifiers/JFieldId.cs b/src/LibRyujinx/Jni/Identifiers/JFieldId.cs new file mode 100644 index 000000000..aa35f9074 --- /dev/null +++ b/src/LibRyujinx/Jni/Identifiers/JFieldId.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.Identifiers +{ + public readonly struct JFieldId : IEquatable + { +#pragma warning disable 0649 + private readonly IntPtr _value; +#pragma warning restore 0649 + + #region Public Methods + public Boolean Equals(JFieldId other) + => this._value.Equals(other._value); + #endregion + + #region Override Methods + public override Boolean Equals([NotNullWhen(true)] Object obj) + => obj is JFieldId other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + + #region Operators + public static Boolean operator ==(JFieldId a, JFieldId b) => a.Equals(b); + public static Boolean operator !=(JFieldId a, JFieldId b) => !a.Equals(b); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Identifiers/JMethodId.cs b/src/LibRyujinx/Jni/Identifiers/JMethodId.cs new file mode 100644 index 000000000..b8fee13c3 --- /dev/null +++ b/src/LibRyujinx/Jni/Identifiers/JMethodId.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.Identifiers +{ + public readonly struct JMethodId : IEquatable + { +#pragma warning disable 0649 + private readonly IntPtr _value; +#pragma warning restore 0649 + + #region Public Methods + public Boolean Equals(JMethodId other) + => this._value.Equals(other._value); + #endregion + + #region Override Methods + public override Boolean Equals([NotNullWhen(true)] Object obj) + => obj is JMethodId other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + + #region Operators + public static Boolean operator ==(JMethodId a, JMethodId b) => a.Equals(b); + public static Boolean operator !=(JMethodId a, JMethodId b) => !a.Equals(b); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/JReferenceType.cs b/src/LibRyujinx/Jni/JReferenceType.cs new file mode 100644 index 000000000..47c9f2aef --- /dev/null +++ b/src/LibRyujinx/Jni/JReferenceType.cs @@ -0,0 +1,12 @@ +using System; + +namespace LibRyujinx.Jni +{ + public enum JReferenceType : Int32 + { + InvalidRefType = 0, + LocalRefType = 1, + GlobalRefType = 2, + WeakGlobalRefType = 3 + } +} diff --git a/src/LibRyujinx/Jni/JReleaseMode.cs b/src/LibRyujinx/Jni/JReleaseMode.cs new file mode 100644 index 000000000..4a86b5a0b --- /dev/null +++ b/src/LibRyujinx/Jni/JReleaseMode.cs @@ -0,0 +1,10 @@ +using System; +namespace LibRyujinx.Jni +{ + public enum JReleaseMode : Int32 + { + Free = 0, + Commit = 1, + Abort = 2, + } +} diff --git a/src/LibRyujinx/Jni/JResult.cs b/src/LibRyujinx/Jni/JResult.cs new file mode 100644 index 000000000..d3cdddff9 --- /dev/null +++ b/src/LibRyujinx/Jni/JResult.cs @@ -0,0 +1,14 @@ +using System; +namespace LibRyujinx.Jni +{ + public enum JResult : Int32 + { + Ok = 0, + Error = -1, + DetachedThreadError = -2, + VersionError = -3, + MemoryError = -4, + ExitingVMError = -5, + InvalidArgumentsError = -6, + } +} diff --git a/src/LibRyujinx/Jni/Pointers/CCharSequence.cs b/src/LibRyujinx/Jni/Pointers/CCharSequence.cs new file mode 100644 index 000000000..d52bcf6f4 --- /dev/null +++ b/src/LibRyujinx/Jni/Pointers/CCharSequence.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; +using System; + +using Rxmxnx.PInvoke; + +namespace LibRyujinx.Jni.Pointers +{ + public readonly struct CCharSequence : IEquatable + { + private readonly IntPtr _value; + + private CCharSequence(IntPtr value) => this._value = value; + + #region Operators + public static implicit operator CCharSequence(IntPtr value) => new(value); + public static implicit operator CCharSequence(Span span) => new(span.GetUnsafeIntPtr()); + public static implicit operator CCharSequence(ReadOnlySpan readonlySpan) => new(readonlySpan.GetUnsafeIntPtr()); + + public static CCharSequence operator ++(CCharSequence a) => new(a._value + sizeof(Byte)); + public static CCharSequence operator --(CCharSequence a) => new(a._value - sizeof(Byte)); + public static Boolean operator ==(CCharSequence a, CCharSequence b) => a._value.Equals(b._value); + public static Boolean operator !=(CCharSequence a, CCharSequence b) => !a._value.Equals(b._value); + #endregion + + #region Public Methods + public Boolean Equals(CCharSequence other) => this._value.Equals(other._value); + public String AsString(Int32 length = 0) + => length == 0 ? Marshal.PtrToStringUTF8(_value) : Marshal.PtrToStringUTF8(_value, length); + #endregion + + #region Overrided Methods + public override Boolean Equals(Object obj) => obj is CCharSequence other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Pointers/JBooleanRef.cs b/src/LibRyujinx/Jni/Pointers/JBooleanRef.cs new file mode 100644 index 000000000..82c6d1767 --- /dev/null +++ b/src/LibRyujinx/Jni/Pointers/JBooleanRef.cs @@ -0,0 +1,30 @@ +using System.Runtime.CompilerServices; +using System; + +using LibRyujinx.Jni.Primitives; + +using Rxmxnx.PInvoke; + +namespace LibRyujinx.Jni.Pointers +{ + public readonly struct JBooleanRef + { + private static readonly Int32 JBooleanResultFalse = 0; + private static readonly Int32 JBooleanResultTrue = 1; + +#pragma warning disable IDE0052 + private readonly IntPtr _value; +#pragma warning restore IDE0052 + + public JBooleanRef(JBoolean? jBoolean) + => this._value = jBoolean.HasValue ? GetJBooleanRef(jBoolean.Value) : IntPtr.Zero; + + private static IntPtr GetJBooleanRef(Boolean value) + { + // Probably gonna break stuff + var t = JBooleanResultTrue; + var f = JBooleanResultFalse; + return value ? Unsafe.AsRef(ref f).GetUnsafeIntPtr() : Unsafe.AsRef(ref t).GetUnsafeIntPtr(); + } + } +} diff --git a/src/LibRyujinx/Jni/Pointers/JCharSequence.cs b/src/LibRyujinx/Jni/Pointers/JCharSequence.cs new file mode 100644 index 000000000..461944fc5 --- /dev/null +++ b/src/LibRyujinx/Jni/Pointers/JCharSequence.cs @@ -0,0 +1,33 @@ +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Pointers +{ + public readonly struct JCharSequence : IEquatable + { + private readonly IntPtr _value; + + private JCharSequence(IntPtr value) => this._value = value; + + #region Operators + public static implicit operator JCharSequence(IntPtr value) => new(value); + public static implicit operator JCharSequence(Span span) => new(span.GetUnsafeIntPtr()); + public static implicit operator JCharSequence(ReadOnlySpan readonlySpan) => new(readonlySpan.GetUnsafeIntPtr()); + + public static JCharSequence operator ++(JCharSequence a) => new(a._value + sizeof(Char)); + public static JCharSequence operator --(JCharSequence a) => new(a._value - sizeof(Char)); + public static Boolean operator ==(JCharSequence a, JCharSequence b) => a._value.Equals(b._value); + public static Boolean operator !=(JCharSequence a, JCharSequence b) => !a._value.Equals(b._value); + #endregion + + #region Public Methods + public Boolean Equals(JCharSequence other) => this._value.Equals(other._value); + public String AsString(Int32 length = 0) => this._value.GetUnsafeString(length); + #endregion + + #region Overrided Methods + public override Boolean Equals(Object obj) => obj is JCharSequence other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Pointers/JEnvRef.cs b/src/LibRyujinx/Jni/Pointers/JEnvRef.cs new file mode 100644 index 000000000..f58c25d5e --- /dev/null +++ b/src/LibRyujinx/Jni/Pointers/JEnvRef.cs @@ -0,0 +1,32 @@ +using LibRyujinx.Jni.Values; + +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Pointers +{ + public readonly struct JEnvRef : IEquatable + { +#pragma warning disable 0649 + private readonly IntPtr _value; +#pragma warning restore 0649 + + #region Operators + public static Boolean operator ==(JEnvRef a, JEnvRef b) => a._value.Equals(b._value); + public static Boolean operator !=(JEnvRef a, JEnvRef b) => !a._value.Equals(b._value); + #endregion + + #region Public Properties + internal readonly ref JEnvValue Environment => ref this._value.GetUnsafeReference(); + #endregion + + #region Public Methods + public Boolean Equals(JEnvRef other) => this._value.Equals(other._value); + #endregion + + #region Overrided Methods + public override Boolean Equals(Object obj) => obj is JEnvRef other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Pointers/JNativeMethodSequence.cs b/src/LibRyujinx/Jni/Pointers/JNativeMethodSequence.cs new file mode 100644 index 000000000..d5508714d --- /dev/null +++ b/src/LibRyujinx/Jni/Pointers/JNativeMethodSequence.cs @@ -0,0 +1,33 @@ +using LibRyujinx.Jni.Values; + +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Pointers +{ + public readonly struct JNativeMethodSequence : IEquatable + { + private readonly IntPtr _value; + + private JNativeMethodSequence(IntPtr value) => this._value = value; + + #region Operators + public static implicit operator JNativeMethodSequence(IntPtr value) => new(value); + public static implicit operator JNativeMethodSequence(ReadOnlySpan readonlySpan) => new(readonlySpan.GetUnsafeIntPtr()); + + public static JNativeMethodSequence operator ++(JNativeMethodSequence a) => new(a._value + JValue.Size); + public static JNativeMethodSequence operator --(JNativeMethodSequence a) => new(a._value - JValue.Size); + public static Boolean operator ==(JNativeMethodSequence a, JNativeMethodSequence b) => a._value.Equals(b._value); + public static Boolean operator !=(JNativeMethodSequence a, JNativeMethodSequence b) => !a._value.Equals(b._value); + #endregion + + #region Public Methods + public Boolean Equals(JNativeMethodSequence other) => this._value.Equals(other._value); + #endregion + + #region Overrided Methods + public override Boolean Equals(Object obj) => obj is JNativeMethodSequence other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Pointers/JValueSequence.cs b/src/LibRyujinx/Jni/Pointers/JValueSequence.cs new file mode 100644 index 000000000..caa65ac9b --- /dev/null +++ b/src/LibRyujinx/Jni/Pointers/JValueSequence.cs @@ -0,0 +1,33 @@ +using LibRyujinx.Jni.Values; + +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Internal.Pointers +{ + internal readonly struct JValueSequence : IEquatable + { + private readonly IntPtr _value; + + private JValueSequence(IntPtr value) => this._value = value; + internal JValueSequence(ReadOnlySpan readonlySpan) : this(readonlySpan.GetUnsafeIntPtr()) { } + + #region Operators + public static implicit operator JValueSequence(IntPtr value) => new(value); + + public static JValueSequence operator ++(JValueSequence a) => new(a._value + JValue.Size); + public static JValueSequence operator --(JValueSequence a) => new(a._value - JValue.Size); + public static Boolean operator ==(JValueSequence a, JValueSequence b) => a._value.Equals(b._value); + public static Boolean operator !=(JValueSequence a, JValueSequence b) => !a._value.Equals(b._value); + #endregion + + #region Public Methods + public Boolean Equals(JValueSequence other) => this._value.Equals(other._value); + #endregion + + #region Overrided Methods + public override Boolean Equals(Object obj) => obj is JValueSequence other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Pointers/JavaVMRef.cs b/src/LibRyujinx/Jni/Pointers/JavaVMRef.cs new file mode 100644 index 000000000..27ba389f1 --- /dev/null +++ b/src/LibRyujinx/Jni/Pointers/JavaVMRef.cs @@ -0,0 +1,25 @@ +using System; + +namespace LibRyujinx.Jni.Pointers +{ + public readonly struct JavaVMRef : IEquatable + { +#pragma warning disable 0649 + private readonly IntPtr _value; +#pragma warning restore 0649 + + #region Operators + public static Boolean operator ==(JavaVMRef a, JavaVMRef b) => a._value.Equals(b._value); + public static Boolean operator !=(JavaVMRef a, JavaVMRef b) => !a._value.Equals(b._value); + #endregion + + #region Public Methods + public Boolean Equals(JavaVMRef other) => this._value.Equals(other._value); + #endregion + + #region Overrided Methods + public override Boolean Equals(Object obj) => obj is JavaVMRef other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Primitives/JBoolean.cs b/src/LibRyujinx/Jni/Primitives/JBoolean.cs new file mode 100644 index 000000000..88b4b7dba --- /dev/null +++ b/src/LibRyujinx/Jni/Primitives/JBoolean.cs @@ -0,0 +1,67 @@ +using LibRyujinx.Jni.Pointers; + +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Primitives +{ + public readonly struct JBoolean : IComparable, IEquatable + { + internal static readonly Type Type = typeof(JBoolean); + private static readonly Byte trueByte = 1; + private static readonly Byte falseByte = 2; + + public static readonly CString Signature = (CString)"Z"; + + private readonly Byte _value; + private Boolean Value => this._value == trueByte; + + private JBoolean(Boolean value) => this._value = value ? trueByte : falseByte; + + #region Operators + public static implicit operator JBoolean(Boolean value) => new(value); + public static implicit operator Boolean(JBoolean jValue) => jValue._value == 1; + public static implicit operator JBooleanRef(JBoolean? jValue) => new(jValue); + public static JBoolean operator !(JBoolean a) => new(!a.Value); + public static JBoolean operator |(JBoolean a, JBoolean b) => new(a.Value || b.Value); + public static JBoolean operator |(Boolean a, JBoolean b) => new(a || b.Value); + public static JBoolean operator |(JBoolean a, Boolean b) => new(a.Value || b); + public static JBoolean operator &(JBoolean a, JBoolean b) => new(a.Value && b.Value); + public static JBoolean operator &(Boolean a, JBoolean b) => new(a && b.Value); + public static JBoolean operator &(JBoolean a, Boolean b) => new(a.Value && b); + public static Boolean operator ==(JBoolean a, JBoolean b) => a._value.Equals(b._value); + public static Boolean operator ==(Boolean a, JBoolean b) => a.Equals(b._value); + public static Boolean operator ==(JBoolean a, Boolean b) => a._value.Equals(b); + public static Boolean operator !=(JBoolean a, JBoolean b) => !a._value.Equals(b._value); + public static Boolean operator !=(Boolean a, JBoolean b) => !a.Equals(b._value); + public static Boolean operator !=(JBoolean a, Boolean b) => !a._value.Equals(b); + public static Boolean operator >(JBoolean a, JBoolean b) => a._value.CompareTo(b._value) > 0; + public static Boolean operator >(Boolean a, JBoolean b) => a.CompareTo(b._value) > 0; + public static Boolean operator >(JBoolean a, Boolean b) => a._value.CompareTo(b) > 0; + public static Boolean operator <(JBoolean a, JBoolean b) => a._value.CompareTo(b._value) < 0; + public static Boolean operator <(Boolean a, JBoolean b) => a.CompareTo(b._value) < 0; + public static Boolean operator <(JBoolean a, Boolean b) => a._value.CompareTo(b) < 0; + public static Boolean operator >=(JBoolean a, JBoolean b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(Boolean a, JBoolean b) => a.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(JBoolean a, Boolean b) => a._value.CompareTo(b) > 0 || a._value.Equals(b); + public static Boolean operator <=(JBoolean a, JBoolean b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(Boolean a, JBoolean b) => a.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(JBoolean a, Boolean b) => a._value.CompareTo(b) < 0 || a._value.Equals(b); + #endregion + + #region Public Methods + public Int32 CompareTo(Boolean other) => this._value.CompareTo(other); + public Int32 CompareTo(JBoolean other) => this._value.CompareTo(other._value); + public Int32 CompareTo(Object obj) => obj is JBoolean jvalue ? this.CompareTo(jvalue) : obj is Boolean value ? this.CompareTo(value) : this._value.CompareTo(obj); + public Boolean Equals(Boolean other) => this._value.Equals(other); + public Boolean Equals(JBoolean other) => this._value.Equals(other._value); + public String ToString(IFormatProvider formatProvider) => this._value.ToString(formatProvider); + #endregion + + #region Overrided Methods + public override String ToString() => this._value.ToString(); + public override Boolean Equals(Object obj) => obj is JBoolean jvalue ? this.Equals(jvalue) : obj is Boolean value ? this.Equals(value) : this._value.Equals(obj); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Primitives/JByte.cs b/src/LibRyujinx/Jni/Primitives/JByte.cs new file mode 100644 index 000000000..a5ed5b0a4 --- /dev/null +++ b/src/LibRyujinx/Jni/Primitives/JByte.cs @@ -0,0 +1,74 @@ +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Primitives +{ + public readonly struct JByte : IComparable, IEquatable, IFormattable + { + internal static readonly Type Type = typeof(JByte); + + public static readonly CString Signature = (CString)"B"; + + private readonly SByte _value; + + private JByte(SByte value) => this._value = value; + private JByte(Int32 value) => this._value = Convert.ToSByte(value); + + #region Operators + public static implicit operator JByte(SByte value) => new(value); + public static implicit operator SByte(JByte jValue) => jValue._value; + public static JByte operator +(JByte a) => a; + public static JByte operator ++(JByte a) => new(a._value + 1); + public static JByte operator -(JByte a) => new(-a._value); + public static JByte operator --(JByte a) => new(a._value - 1); + public static JByte operator +(JByte a, JByte b) => new(a._value + b._value); + public static JByte operator +(SByte a, JByte b) => new(a + b._value); + public static JByte operator +(JByte a, SByte b) => new(a._value + b); + public static JByte operator -(JByte a, JByte b) => new(a._value - b._value); + public static JByte operator -(SByte a, JByte b) => new(a - b._value); + public static JByte operator -(JByte a, SByte b) => new(a._value - b); + public static JByte operator *(JByte a, JByte b) => new(a._value * b._value); + public static JByte operator *(SByte a, JByte b) => new(a * b._value); + public static JByte operator *(JByte a, SByte b) => new(a._value * b); + public static JByte operator /(JByte a, JByte b) => new(a._value / b._value); + public static JByte operator /(SByte a, JByte b) => new(a / b._value); + public static JByte operator /(JByte a, SByte b) => new(a._value / b); + public static JByte operator %(JByte a, JByte b) => new(a._value % b._value); + public static JByte operator %(SByte a, JByte b) => new(a % b._value); + public static JByte operator %(JByte a, SByte b) => new(a._value % b); + public static Boolean operator ==(JByte a, JByte b) => a._value.Equals(b._value); + public static Boolean operator ==(SByte a, JByte b) => a.Equals(b._value); + public static Boolean operator ==(JByte a, SByte b) => a._value.Equals(b); + public static Boolean operator !=(JByte a, JByte b) => !a._value.Equals(b._value); + public static Boolean operator !=(SByte a, JByte b) => !a.Equals(b._value); + public static Boolean operator !=(JByte a, SByte b) => !a._value.Equals(b); + public static Boolean operator >(JByte a, JByte b) => a._value.CompareTo(b._value) > 0; + public static Boolean operator >(SByte a, JByte b) => a.CompareTo(b._value) > 0; + public static Boolean operator >(JByte a, SByte b) => a._value.CompareTo(b) > 0; + public static Boolean operator <(JByte a, JByte b) => a._value.CompareTo(b._value) < 0; + public static Boolean operator <(SByte a, JByte b) => a.CompareTo(b._value) < 0; + public static Boolean operator <(JByte a, SByte b) => a._value.CompareTo(b) < 0; + public static Boolean operator >=(JByte a, JByte b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(SByte a, JByte b) => a.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(JByte a, SByte b) => a._value.CompareTo(b) > 0 || a._value.Equals(b); + public static Boolean operator <=(JByte a, JByte b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(SByte a, JByte b) => a.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(JByte a, SByte b) => a._value.CompareTo(b) < 0 || a._value.Equals(b); + #endregion + + #region Public Methods + public Int32 CompareTo(SByte other) => this._value.CompareTo(other); + public Int32 CompareTo(JByte other) => this._value.CompareTo(other._value); + public Int32 CompareTo(Object obj) => obj is JByte jValue ? this.CompareTo(jValue) : obj is SByte value ? this.CompareTo(value) : this._value.CompareTo(obj); + public Boolean Equals(SByte other) => this._value.Equals(other); + public Boolean Equals(JByte other) => this._value.Equals(other._value); + public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider); + #endregion + + #region Overrided Methods + public override String ToString() => this._value.ToString(); + public override Boolean Equals(Object obj) => obj is JByte jvalue ? this.Equals(jvalue) : obj is SByte value ? this.Equals(value) : this._value.Equals(obj); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Primitives/JChar.cs b/src/LibRyujinx/Jni/Primitives/JChar.cs new file mode 100644 index 000000000..b2ba3782d --- /dev/null +++ b/src/LibRyujinx/Jni/Primitives/JChar.cs @@ -0,0 +1,56 @@ +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Primitives +{ + public readonly struct JChar : IComparable, IEquatable + { + internal static readonly Type Type = typeof(JChar); + + public static readonly CString Signature = (CString)"C"; + + private readonly Char _value; + + private JChar(Char value) => this._value = value; + + #region Operators + public static implicit operator JChar(Char value) => new(value); + public static explicit operator JChar(Int16 value) => new((Char)value); + public static implicit operator Char(JChar jValue) => jValue._value; + public static explicit operator Int16(JChar jValue) => (Int16)jValue._value; + public static Boolean operator ==(JChar a, JChar b) => a._value.Equals(b._value); + public static Boolean operator ==(Char a, JChar b) => a.Equals(b._value); + public static Boolean operator ==(JChar a, Char b) => a._value.Equals(b); + public static Boolean operator !=(JChar a, JChar b) => !a._value.Equals(b._value); + public static Boolean operator !=(Char a, JChar b) => !a.Equals(b._value); + public static Boolean operator !=(JChar a, Char b) => !a._value.Equals(b); + public static Boolean operator >(JChar a, JChar b) => a._value.CompareTo(b._value) > 0; + public static Boolean operator >(Char a, JChar b) => a.CompareTo(b._value) > 0; + public static Boolean operator >(JChar a, Char b) => a._value.CompareTo(b) > 0; + public static Boolean operator <(JChar a, JChar b) => a._value.CompareTo(b._value) < 0; + public static Boolean operator <(Char a, JChar b) => a.CompareTo(b._value) < 0; + public static Boolean operator <(JChar a, Char b) => a._value.CompareTo(b) < 0; + public static Boolean operator >=(JChar a, JChar b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(Char a, JChar b) => a.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(JChar a, Char b) => a._value.CompareTo(b) > 0 || a._value.Equals(b); + public static Boolean operator <=(JChar a, JChar b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(Char a, JChar b) => a.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(JChar a, Char b) => a._value.CompareTo(b) < 0 || a._value.Equals(b); + #endregion + + #region Public Methods + public Int32 CompareTo(Char other) => this._value.CompareTo(other); + public Int32 CompareTo(JChar other) => this._value.CompareTo(other._value); + public Int32 CompareTo(Object obj) => obj is JChar jvalue ? this.CompareTo(jvalue) : obj is Char value ? this.CompareTo(value) : this._value.CompareTo(obj); + public Boolean Equals(Char other) => this._value.Equals(other); + public Boolean Equals(JChar other) => this._value.Equals(other._value); + public String ToString(IFormatProvider formatProvider) => this._value.ToString(formatProvider); + #endregion + + #region Overrided Methods + public override String ToString() => this._value.ToString(); + public override Boolean Equals(Object obj) => obj is JChar jvalue ? this.Equals(jvalue) : obj is Char value ? this.Equals(value) : this._value.Equals(obj); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Primitives/JDouble.cs b/src/LibRyujinx/Jni/Primitives/JDouble.cs new file mode 100644 index 000000000..94f05eb9a --- /dev/null +++ b/src/LibRyujinx/Jni/Primitives/JDouble.cs @@ -0,0 +1,70 @@ +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Primitives +{ + public readonly struct JDouble : IComparable, IEquatable, IFormattable + { + internal static readonly Type Type = typeof(JDouble); + + public static readonly CString Signature = (CString)"D"; + + private readonly Double _value; + + private JDouble(Double value) => this._value = value; + + #region Operators + public static implicit operator JDouble(Double value) => new(value); + public static implicit operator Double(JDouble jValue) => jValue._value; + public static JDouble operator +(JDouble a) => a; + public static JDouble operator ++(JDouble a) => new(a._value + 1); + public static JDouble operator -(JDouble a) => new(-a._value); + public static JDouble operator --(JDouble a) => new(a._value - 1); + public static JDouble operator +(JDouble a, JDouble b) => new(a._value + b._value); + public static JDouble operator +(Double a, JDouble b) => new(a + b._value); + public static JDouble operator +(JDouble a, Double b) => new(a._value + b); + public static JDouble operator -(JDouble a, JDouble b) => new(a._value - b._value); + public static JDouble operator -(Double a, JDouble b) => new(a - b._value); + public static JDouble operator -(JDouble a, Double b) => new(a._value - b); + public static JDouble operator *(JDouble a, JDouble b) => new(a._value * b._value); + public static JDouble operator *(Double a, JDouble b) => new(a * b._value); + public static JDouble operator *(JDouble a, Double b) => new(a._value * b); + public static JDouble operator /(JDouble a, JDouble b) => new(a._value / b._value); + public static JDouble operator /(Double a, JDouble b) => new(a / b._value); + public static JDouble operator /(JDouble a, Double b) => new(a._value / b); + public static Boolean operator ==(JDouble a, JDouble b) => a._value.Equals(b._value); + public static Boolean operator ==(Double a, JDouble b) => a.Equals(b._value); + public static Boolean operator ==(JDouble a, Double b) => a._value.Equals(b); + public static Boolean operator !=(JDouble a, JDouble b) => !a._value.Equals(b._value); + public static Boolean operator !=(Double a, JDouble b) => !a.Equals(b._value); + public static Boolean operator !=(JDouble a, Double b) => !a._value.Equals(b); + public static Boolean operator >(JDouble a, JDouble b) => a._value.CompareTo(b._value) > 0; + public static Boolean operator >(Double a, JDouble b) => a.CompareTo(b._value) > 0; + public static Boolean operator >(JDouble a, Double b) => a._value.CompareTo(b) > 0; + public static Boolean operator <(JDouble a, JDouble b) => a._value.CompareTo(b._value) < 0; + public static Boolean operator <(Double a, JDouble b) => a.CompareTo(b._value) < 0; + public static Boolean operator <(JDouble a, Double b) => a._value.CompareTo(b) < 0; + public static Boolean operator >=(JDouble a, JDouble b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(Double a, JDouble b) => a.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(JDouble a, Double b) => a._value.CompareTo(b) > 0 || a._value.Equals(b); + public static Boolean operator <=(JDouble a, JDouble b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(Double a, JDouble b) => a.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(JDouble a, Double b) => a._value.CompareTo(b) < 0 || a._value.Equals(b); + #endregion + + #region Public Methods + public Int32 CompareTo(Double other) => this._value.CompareTo(other); + public Int32 CompareTo(JDouble other) => this._value.CompareTo(other._value); + public Int32 CompareTo(Object obj) => obj is JDouble jvalue ? this.CompareTo(jvalue) : obj is Double value ? this.CompareTo(value) : this._value.CompareTo(obj); + public Boolean Equals(Double other) => this._value.Equals(other); + public Boolean Equals(JDouble other) => this._value.Equals(other._value); + public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider); + #endregion + + #region Overrided Methods + public override String ToString() => this._value.ToString(); + public override Boolean Equals(Object obj) => obj is JDouble jvalue ? this.Equals(jvalue) : obj is Double value ? this.Equals(value) : this._value.Equals(obj); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Primitives/JFloat.cs b/src/LibRyujinx/Jni/Primitives/JFloat.cs new file mode 100644 index 000000000..52f833241 --- /dev/null +++ b/src/LibRyujinx/Jni/Primitives/JFloat.cs @@ -0,0 +1,70 @@ +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Primitives +{ + public readonly struct JFloat : IComparable, IEquatable, IFormattable + { + internal static readonly Type Type = typeof(JFloat); + + public static readonly CString Signature = (CString)"F"; + + private readonly Single _value; + + private JFloat(Single value) => this._value = value; + + #region Operators + public static implicit operator JFloat(Single value) => new(value); + public static implicit operator Single(JFloat jValue) => jValue._value; + public static JFloat operator +(JFloat a) => a; + public static JFloat operator ++(JFloat a) => new(a._value + 1); + public static JFloat operator -(JFloat a) => new(-a._value); + public static JFloat operator --(JFloat a) => new(a._value - 1); + public static JFloat operator +(JFloat a, JFloat b) => new(a._value + b._value); + public static JFloat operator +(Single a, JFloat b) => new(a + b._value); + public static JFloat operator +(JFloat a, Single b) => new(a._value + b); + public static JFloat operator -(JFloat a, JFloat b) => new(a._value - b._value); + public static JFloat operator -(Single a, JFloat b) => new(a - b._value); + public static JFloat operator -(JFloat a, Single b) => new(a._value - b); + public static JFloat operator *(JFloat a, JFloat b) => new(a._value * b._value); + public static JFloat operator *(Single a, JFloat b) => new(a * b._value); + public static JFloat operator *(JFloat a, Single b) => new(a._value * b); + public static JFloat operator /(JFloat a, JFloat b) => new(a._value / b._value); + public static JFloat operator /(Single a, JFloat b) => new(a / b._value); + public static JFloat operator /(JFloat a, Single b) => new(a._value / b); + public static Boolean operator ==(JFloat a, JFloat b) => a._value.Equals(b._value); + public static Boolean operator ==(Single a, JFloat b) => a.Equals(b._value); + public static Boolean operator ==(JFloat a, Single b) => a._value.Equals(b); + public static Boolean operator !=(JFloat a, JFloat b) => !a._value.Equals(b._value); + public static Boolean operator !=(Single a, JFloat b) => !a.Equals(b._value); + public static Boolean operator !=(JFloat a, Single b) => !a._value.Equals(b); + public static Boolean operator >(JFloat a, JFloat b) => a._value.CompareTo(b._value) > 0; + public static Boolean operator >(Single a, JFloat b) => a.CompareTo(b._value) > 0; + public static Boolean operator >(JFloat a, Single b) => a._value.CompareTo(b) > 0; + public static Boolean operator <(JFloat a, JFloat b) => a._value.CompareTo(b._value) < 0; + public static Boolean operator <(Single a, JFloat b) => a.CompareTo(b._value) < 0; + public static Boolean operator <(JFloat a, Single b) => a._value.CompareTo(b) < 0; + public static Boolean operator >=(JFloat a, JFloat b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(Single a, JFloat b) => a.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(JFloat a, Single b) => a._value.CompareTo(b) > 0 || a._value.Equals(b); + public static Boolean operator <=(JFloat a, JFloat b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(Single a, JFloat b) => a.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(JFloat a, Single b) => a._value.CompareTo(b) < 0 || a._value.Equals(b); + #endregion + + #region Public Methods + public Int32 CompareTo(Single other) => this._value.CompareTo(other); + public Int32 CompareTo(JFloat other) => this._value.CompareTo(other._value); + public Int32 CompareTo(Object obj) => obj is JFloat jvalue ? this.CompareTo(jvalue) : obj is Single value ? this.CompareTo(value) : this._value.CompareTo(obj); + public Boolean Equals(Single other) => this._value.Equals(other); + public Boolean Equals(JFloat other) => this._value.Equals(other._value); + public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider); + #endregion + + #region Overrided Methods + public override String ToString() => this._value.ToString(); + public override Boolean Equals(Object obj) => obj is JFloat jvalue ? this.Equals(jvalue) : obj is Single value ? this.Equals(value) : this._value.Equals(obj); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Primitives/JInt.cs b/src/LibRyujinx/Jni/Primitives/JInt.cs new file mode 100644 index 000000000..adf86a8d7 --- /dev/null +++ b/src/LibRyujinx/Jni/Primitives/JInt.cs @@ -0,0 +1,73 @@ +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Primitives +{ + public readonly struct JInt : IComparable, IEquatable, IFormattable + { + internal static readonly Type Type = typeof(JInt); + + public static readonly CString Signature = (CString)"I"; + + private readonly Int32 _value; + + private JInt(Int32 value) => this._value = value; + + #region Operators + public static implicit operator JInt(Int32 value) => new(value); + public static implicit operator Int32(JInt jValue) => jValue._value; + public static JInt operator +(JInt a) => a; + public static JInt operator ++(JInt a) => new(a._value + 1); + public static JInt operator -(JInt a) => new(-a._value); + public static JInt operator --(JInt a) => new(a._value - 1); + public static JInt operator +(JInt a, JInt b) => new(a._value + b._value); + public static JInt operator +(Int32 a, JInt b) => new(a + b._value); + public static JInt operator +(JInt a, Int32 b) => new(a._value + b); + public static JInt operator -(JInt a, JInt b) => new(a._value - b._value); + public static JInt operator -(Int32 a, JInt b) => new(a - b._value); + public static JInt operator -(JInt a, Int32 b) => new(a._value - b); + public static JInt operator *(JInt a, JInt b) => new(a._value * b._value); + public static JInt operator *(Int32 a, JInt b) => new(a * b._value); + public static JInt operator *(JInt a, Int32 b) => new(a._value * b); + public static JInt operator /(JInt a, JInt b) => new(a._value / b._value); + public static JInt operator /(Int32 a, JInt b) => new(a / b._value); + public static JInt operator /(JInt a, Int32 b) => new(a._value / b); + public static JInt operator %(JInt a, JInt b) => new(a._value % b._value); + public static JInt operator %(Int32 a, JInt b) => new(a % b._value); + public static JInt operator %(JInt a, Int32 b) => new(a._value % b); + public static Boolean operator ==(JInt a, JInt b) => a._value.Equals(b._value); + public static Boolean operator ==(Int32 a, JInt b) => a.Equals(b._value); + public static Boolean operator ==(JInt a, Int32 b) => a._value.Equals(b); + public static Boolean operator !=(JInt a, JInt b) => !a._value.Equals(b._value); + public static Boolean operator !=(Int32 a, JInt b) => !a.Equals(b._value); + public static Boolean operator !=(JInt a, Int32 b) => !a._value.Equals(b); + public static Boolean operator >(JInt a, JInt b) => a._value.CompareTo(b._value) > 0; + public static Boolean operator >(Int32 a, JInt b) => a.CompareTo(b._value) > 0; + public static Boolean operator >(JInt a, Int32 b) => a._value.CompareTo(b) > 0; + public static Boolean operator <(JInt a, JInt b) => a._value.CompareTo(b._value) < 0; + public static Boolean operator <(Int32 a, JInt b) => a.CompareTo(b._value) < 0; + public static Boolean operator <(JInt a, Int32 b) => a._value.CompareTo(b) < 0; + public static Boolean operator >=(JInt a, JInt b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(Int32 a, JInt b) => a.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(JInt a, Int32 b) => a._value.CompareTo(b) > 0 || a._value.Equals(b); + public static Boolean operator <=(JInt a, JInt b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(Int32 a, JInt b) => a.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(JInt a, Int32 b) => a._value.CompareTo(b) < 0 || a._value.Equals(b); + #endregion + + #region Public Methods + public Int32 CompareTo(Int32 other) => this._value.CompareTo(other); + public Int32 CompareTo(JInt other) => this._value.CompareTo(other._value); + public Int32 CompareTo(Object obj) => obj is JInt jValue ? this.CompareTo(jValue) : obj is Int32 value ? this.CompareTo(value) : this._value.CompareTo(obj); + public Boolean Equals(Int32 other) => this._value.Equals(other); + public Boolean Equals(JInt other) => this._value.Equals(other._value); + public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider); + #endregion + + #region Overrided Methods + public override String ToString() => this._value.ToString(); + public override Boolean Equals(Object obj) => obj is JInt jvalue ? this.Equals(jvalue) : obj is Int32 value ? this.Equals(value) : this._value.Equals(obj); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Primitives/JLong.cs b/src/LibRyujinx/Jni/Primitives/JLong.cs new file mode 100644 index 000000000..a5bd6613a --- /dev/null +++ b/src/LibRyujinx/Jni/Primitives/JLong.cs @@ -0,0 +1,73 @@ +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Primitives +{ + public readonly struct JLong : IComparable, IEquatable, IFormattable + { + internal static readonly Type Type = typeof(JLong); + + public static readonly CString Signature = (CString)"J"; + + private readonly Int64 _value; + + private JLong(Int64 value) => this._value = value; + + #region Operators + public static implicit operator JLong(Int64 value) => new(value); + public static implicit operator Int64(JLong jValue) => jValue._value; + public static JLong operator +(JLong a) => a; + public static JLong operator ++(JLong a) => new(a._value + 1); + public static JLong operator -(JLong a) => new(-a._value); + public static JLong operator --(JLong a) => new(a._value - 1); + public static JLong operator +(JLong a, JLong b) => new(a._value + b._value); + public static JLong operator +(Int64 a, JLong b) => new(a + b._value); + public static JLong operator +(JLong a, Int64 b) => new(a._value + b); + public static JLong operator -(JLong a, JLong b) => new(a._value - b._value); + public static JLong operator -(Int64 a, JLong b) => new(a - b._value); + public static JLong operator -(JLong a, Int64 b) => new(a._value - b); + public static JLong operator *(JLong a, JLong b) => new(a._value * b._value); + public static JLong operator *(Int64 a, JLong b) => new(a * b._value); + public static JLong operator *(JLong a, Int64 b) => new(a._value * b); + public static JLong operator /(JLong a, JLong b) => new(a._value / b._value); + public static JLong operator /(Int64 a, JLong b) => new(a / b._value); + public static JLong operator /(JLong a, Int64 b) => new(a._value / b); + public static JLong operator %(JLong a, JLong b) => new(a._value % b._value); + public static JLong operator %(Int64 a, JLong b) => new(a % b._value); + public static JLong operator %(JLong a, Int64 b) => new(a._value % b); + public static Boolean operator ==(JLong a, JLong b) => a._value.Equals(b._value); + public static Boolean operator ==(Int64 a, JLong b) => a.Equals(b._value); + public static Boolean operator ==(JLong a, Int64 b) => a._value.Equals(b); + public static Boolean operator !=(JLong a, JLong b) => !a._value.Equals(b._value); + public static Boolean operator !=(Int64 a, JLong b) => !a.Equals(b._value); + public static Boolean operator !=(JLong a, Int64 b) => !a._value.Equals(b); + public static Boolean operator >(JLong a, JLong b) => a._value.CompareTo(b._value) > 0; + public static Boolean operator >(Int64 a, JLong b) => a.CompareTo(b._value) > 0; + public static Boolean operator >(JLong a, Int64 b) => a._value.CompareTo(b) > 0; + public static Boolean operator <(JLong a, JLong b) => a._value.CompareTo(b._value) < 0; + public static Boolean operator <(Int64 a, JLong b) => a.CompareTo(b._value) < 0; + public static Boolean operator <(JLong a, Int64 b) => a._value.CompareTo(b) < 0; + public static Boolean operator >=(JLong a, JLong b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(Int64 a, JLong b) => a.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(JLong a, Int64 b) => a._value.CompareTo(b) > 0 || a._value.Equals(b); + public static Boolean operator <=(JLong a, JLong b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(Int64 a, JLong b) => a.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(JLong a, Int64 b) => a._value.CompareTo(b) < 0 || a._value.Equals(b); + #endregion + + #region Public Methods + public Int32 CompareTo(Int64 other) => this._value.CompareTo(other); + public Int32 CompareTo(JLong other) => this._value.CompareTo(other._value); + public Int32 CompareTo(Object obj) => obj is JLong jvalue ? this.CompareTo(jvalue) : obj is Int64 value ? this.CompareTo(value) : this._value.CompareTo(obj); + public Boolean Equals(Int64 other) => this._value.Equals(other); + public Boolean Equals(JLong other) => this._value.Equals(other._value); + public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider); + #endregion + + #region Overrided Methods + public override String ToString() => this._value.ToString(); + public override Boolean Equals(Object obj) => obj is JLong jvalue ? this.Equals(jvalue) : obj is Int64 value ? this.Equals(value) : this._value.Equals(obj); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Primitives/JShort.cs b/src/LibRyujinx/Jni/Primitives/JShort.cs new file mode 100644 index 000000000..78650c2fe --- /dev/null +++ b/src/LibRyujinx/Jni/Primitives/JShort.cs @@ -0,0 +1,74 @@ +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Primitives +{ + public readonly struct JShort : IComparable, IEquatable, IFormattable + { + internal static readonly Type Type = typeof(JShort); + + public static readonly CString Signature = (CString)"S"; + + private readonly Int16 _value; + + private JShort(Int16 value) => this._value = value; + private JShort(Int32 value) => this._value = Convert.ToInt16(value); + + #region Operators + public static implicit operator JShort(Int16 value) => new(value); + public static implicit operator Int16(JShort jValue) => jValue._value; + public static JShort operator +(JShort a) => a; + public static JShort operator ++(JShort a) => new(a._value + 1); + public static JShort operator -(JShort a) => new(-a._value); + public static JShort operator --(JShort a) => new(a._value - 1); + public static JShort operator +(JShort a, JShort b) => new(a._value + b._value); + public static JShort operator +(Int16 a, JShort b) => new(a + b._value); + public static JShort operator +(JShort a, Int16 b) => new(a._value + b); + public static JShort operator -(JShort a, JShort b) => new(a._value - b._value); + public static JShort operator -(Int16 a, JShort b) => new(a - b._value); + public static JShort operator -(JShort a, Int16 b) => new(a._value - b); + public static JShort operator *(JShort a, JShort b) => new(a._value * b._value); + public static JShort operator *(Int16 a, JShort b) => new(a * b._value); + public static JShort operator *(JShort a, Int16 b) => new(a._value * b); + public static JShort operator /(JShort a, JShort b) => new(a._value / b._value); + public static JShort operator /(Int16 a, JShort b) => new(a / b._value); + public static JShort operator /(JShort a, Int16 b) => new(a._value / b); + public static JShort operator %(JShort a, JShort b) => new(a._value % b._value); + public static JShort operator %(Int16 a, JShort b) => new(a % b._value); + public static JShort operator %(JShort a, Int16 b) => new(a._value % b); + public static Boolean operator ==(JShort a, JShort b) => a._value.Equals(b._value); + public static Boolean operator ==(Int16 a, JShort b) => a.Equals(b._value); + public static Boolean operator ==(JShort a, Int16 b) => a._value.Equals(b); + public static Boolean operator !=(JShort a, JShort b) => !a._value.Equals(b._value); + public static Boolean operator !=(Int16 a, JShort b) => !a.Equals(b._value); + public static Boolean operator !=(JShort a, Int16 b) => !a._value.Equals(b); + public static Boolean operator >(JShort a, JShort b) => a._value.CompareTo(b._value) > 0; + public static Boolean operator >(Int16 a, JShort b) => a.CompareTo(b._value) > 0; + public static Boolean operator >(JShort a, Int16 b) => a._value.CompareTo(b) > 0; + public static Boolean operator <(JShort a, JShort b) => a._value.CompareTo(b._value) < 0; + public static Boolean operator <(Int16 a, JShort b) => a.CompareTo(b._value) < 0; + public static Boolean operator <(JShort a, Int16 b) => a._value.CompareTo(b) < 0; + public static Boolean operator >=(JShort a, JShort b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(Int16 a, JShort b) => a.CompareTo(b._value) > 0 || a.Equals(b._value); + public static Boolean operator >=(JShort a, Int16 b) => a._value.CompareTo(b) > 0 || a._value.Equals(b); + public static Boolean operator <=(JShort a, JShort b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(Int16 a, JShort b) => a.CompareTo(b._value) < 0 || a.Equals(b._value); + public static Boolean operator <=(JShort a, Int16 b) => a._value.CompareTo(b) < 0 || a._value.Equals(b); + #endregion + + #region Public Methods + public Int32 CompareTo(Int16 other) => this._value.CompareTo(other); + public Int32 CompareTo(JShort other) => this._value.CompareTo(other._value); + public Int32 CompareTo(Object obj) => obj is JShort jvalue ? this.CompareTo(jvalue) : obj is Int16 value ? this.CompareTo(value) : this._value.CompareTo(obj); + public Boolean Equals(Int16 other) => this._value.Equals(other); + public Boolean Equals(JShort other) => this._value.Equals(other._value); + public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider); + #endregion + + #region Overrided Methods + public override String ToString() => this._value.ToString(); + public override Boolean Equals(Object obj) => obj is JShort jvalue ? this.Equals(jvalue) : obj is Int16 value ? this.Equals(value) : this._value.Equals(obj); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/References/JArrayLocalRef.cs b/src/LibRyujinx/Jni/References/JArrayLocalRef.cs new file mode 100644 index 000000000..6501b4360 --- /dev/null +++ b/src/LibRyujinx/Jni/References/JArrayLocalRef.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.References +{ + public readonly struct JArrayLocalRef : IEquatable + { +#pragma warning disable 0649 + private readonly JObjectLocalRef _value; +#pragma warning restore 0649 + + #region Public Methods + public Boolean Equals(JArrayLocalRef other) + => this._value.Equals(other._value); + #endregion + + #region Override Methods + public override Boolean Equals([NotNullWhen(true)] Object obj) + => obj is JArrayLocalRef other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + + #region Operators + public static Boolean operator ==(JArrayLocalRef a, JArrayLocalRef b) => a.Equals(b); + public static Boolean operator !=(JArrayLocalRef a, JArrayLocalRef b) => !a.Equals(b); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/References/JClassLocalRef.cs b/src/LibRyujinx/Jni/References/JClassLocalRef.cs new file mode 100644 index 000000000..5880f5ddb --- /dev/null +++ b/src/LibRyujinx/Jni/References/JClassLocalRef.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.References +{ + public readonly struct JClassLocalRef : IEquatable + { +#pragma warning disable 0649 + public readonly JObjectLocalRef _value; +#pragma warning restore 0649 + + #region Public Methods + public Boolean Equals(JClassLocalRef other) + => this._value.Equals(other._value); + #endregion + + #region Override Methods + public override Boolean Equals([NotNullWhen(true)] Object obj) + => obj is JClassLocalRef other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + + #region Operators + public static Boolean operator ==(JClassLocalRef a, JClassLocalRef b) => a.Equals(b); + public static Boolean operator !=(JClassLocalRef a, JClassLocalRef b) => !a.Equals(b); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/References/JGlobalRef.cs b/src/LibRyujinx/Jni/References/JGlobalRef.cs new file mode 100644 index 000000000..1befe7b94 --- /dev/null +++ b/src/LibRyujinx/Jni/References/JGlobalRef.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.References +{ + public readonly struct JGlobalRef : IEquatable + { +#pragma warning disable 0649 + private readonly IntPtr _value; +#pragma warning restore 0649 + + #region Public Methods + public Boolean Equals(JGlobalRef other) + => this._value.Equals(other._value); + #endregion + + #region Override Methods + public override Boolean Equals([NotNullWhen(true)] Object obj) + => obj is JGlobalRef other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + + #region Operators + public static Boolean operator ==(JGlobalRef a, JGlobalRef b) => a.Equals(b); + public static Boolean operator !=(JGlobalRef a, JGlobalRef b) => !a.Equals(b); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/References/JObjectLocalRef.cs b/src/LibRyujinx/Jni/References/JObjectLocalRef.cs new file mode 100644 index 000000000..ff1953137 --- /dev/null +++ b/src/LibRyujinx/Jni/References/JObjectLocalRef.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.References +{ + public readonly struct JObjectLocalRef : IEquatable + { +#pragma warning disable 0649 + private readonly IntPtr _value; +#pragma warning restore 0649 + + #region Public Methods + public Boolean Equals(JObjectLocalRef other) + => this._value.Equals(other._value); + #endregion + + #region Override Methods + public override Boolean Equals([NotNullWhen(true)] Object obj) + => obj is JObjectLocalRef other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + + #region Operators + public static Boolean operator ==(JObjectLocalRef a, JObjectLocalRef b) => a.Equals(b); + public static Boolean operator !=(JObjectLocalRef a, JObjectLocalRef b) => !a.Equals(b); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/References/JStringLocalRef.cs b/src/LibRyujinx/Jni/References/JStringLocalRef.cs new file mode 100644 index 000000000..002fd9942 --- /dev/null +++ b/src/LibRyujinx/Jni/References/JStringLocalRef.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.References +{ + public readonly struct JStringLocalRef : IEquatable + { +#pragma warning disable 0649 + public readonly JObjectLocalRef _value; +#pragma warning restore 0649 + + #region Public Methods + public Boolean Equals(JStringLocalRef other) + => this._value.Equals(other._value); + #endregion + + #region Override Methods + public override Boolean Equals([NotNullWhen(true)] Object obj) + => obj is JStringLocalRef other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + + #region Operators + public static Boolean operator ==(JStringLocalRef a, JStringLocalRef b) => a.Equals(b); + public static Boolean operator !=(JStringLocalRef a, JStringLocalRef b) => !a.Equals(b); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/References/JThrowableLocalRef.cs b/src/LibRyujinx/Jni/References/JThrowableLocalRef.cs new file mode 100644 index 000000000..a7e5e3b40 --- /dev/null +++ b/src/LibRyujinx/Jni/References/JThrowableLocalRef.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.References +{ + public readonly struct JThrowableLocalRef : IEquatable + { +#pragma warning disable 0649 + private readonly IntPtr _value; +#pragma warning restore 0649 + + #region Public Methods + public Boolean Equals(JThrowableLocalRef other) + => this._value.Equals(other._value); + #endregion + + #region Override Methods + public override Boolean Equals([NotNullWhen(true)] Object obj) + => obj is JThrowableLocalRef other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + + #region Operators + public static Boolean operator ==(JThrowableLocalRef a, JThrowableLocalRef b) => a.Equals(b); + public static Boolean operator !=(JThrowableLocalRef a, JThrowableLocalRef b) => !a.Equals(b); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/References/JWeakRef.cs b/src/LibRyujinx/Jni/References/JWeakRef.cs new file mode 100644 index 000000000..9572c6035 --- /dev/null +++ b/src/LibRyujinx/Jni/References/JWeakRef.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.References +{ + public readonly struct JWeakRef : IEquatable + { +#pragma warning disable 0649 + private readonly IntPtr _value; +#pragma warning restore 0649 + + #region Public Methods + public Boolean Equals(JWeakRef other) + => this._value.Equals(other._value); + #endregion + + #region Override Methods + public override Boolean Equals([NotNullWhen(true)] Object obj) + => obj is JWeakRef other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + + #region Operators + public static Boolean operator ==(JWeakRef a, JWeakRef b) => a.Equals(b); + public static Boolean operator !=(JWeakRef a, JWeakRef b) => !a.Equals(b); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Values/JEnvValue.cs b/src/LibRyujinx/Jni/Values/JEnvValue.cs new file mode 100644 index 000000000..dc4d8a17b --- /dev/null +++ b/src/LibRyujinx/Jni/Values/JEnvValue.cs @@ -0,0 +1,30 @@ +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Values +{ + internal readonly struct JEnvValue : IEquatable + { +#pragma warning disable 0649 + private readonly IntPtr _value; +#pragma warning restore 0649 + + #region Operators + public static Boolean operator ==(JEnvValue a, JEnvValue b) => a._value.Equals(b._value); + public static Boolean operator !=(JEnvValue a, JEnvValue b) => !a._value.Equals(b._value); + #endregion + + #region Public Properties + internal readonly ref JNativeInterface Functions => ref this._value.GetUnsafeReference(); + #endregion + + #region Public Methods + public Boolean Equals(JEnvValue other) => this._value.Equals(other._value); + #endregion + + #region Overrided Methods + public override Boolean Equals(Object obj) => obj is JEnvValue other && this.Equals(other); + public override Int32 GetHashCode() => this._value.GetHashCode(); + #endregion + } +} diff --git a/src/LibRyujinx/Jni/Values/JInvokeInterface.cs b/src/LibRyujinx/Jni/Values/JInvokeInterface.cs new file mode 100644 index 000000000..14473c574 --- /dev/null +++ b/src/LibRyujinx/Jni/Values/JInvokeInterface.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.Values +{ + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "This struct is created only by binary operations.")] + public readonly struct JInvokeInterface + { +#pragma warning disable 0169 + private readonly IntPtr _reserved0; + private readonly IntPtr _reserved1; + private readonly IntPtr _reserved2; +#pragma warning restore 0169 + internal IntPtr DestroyJavaVMPointer { get; init; } + internal IntPtr AttachCurrentThreadPointer { get; init; } + internal IntPtr DetachCurrentThreadPointer { get; init; } + internal IntPtr GetEnvPointer { get; init; } + internal IntPtr AttachCurrentThreadAsDaemonPointer { get; init; } + } +} diff --git a/src/LibRyujinx/Jni/Values/JNativeInterface.cs b/src/LibRyujinx/Jni/Values/JNativeInterface.cs new file mode 100644 index 000000000..b3aa132ec --- /dev/null +++ b/src/LibRyujinx/Jni/Values/JNativeInterface.cs @@ -0,0 +1,245 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +namespace LibRyujinx.Jni.Values +{ + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "This struct is created only by binary operations.")] + public readonly struct JNativeInterface + { +#pragma warning disable 0169 + private readonly IntPtr _reserved0; + private readonly IntPtr _reserved1; + private readonly IntPtr _reserved2; + private readonly IntPtr _reserved3; +#pragma warning restore 0169 + internal readonly IntPtr GetVersionPointer { get; init; } + internal readonly IntPtr DefineClassPointer { get; init; } + internal readonly IntPtr FindClassPointer { get; init; } + internal readonly IntPtr FromReflectedMethodPointer { get; init; } + internal readonly IntPtr FromReflectedFieldPointer { get; init; } + internal readonly IntPtr ToReflectedMethodPointer { get; init; } + internal readonly IntPtr GetSuperclassPointer { get; init; } + internal readonly IntPtr IsAssignableFromPointer { get; init; } + internal readonly IntPtr ToReflectedFieldPointer { get; init; } + internal readonly IntPtr ThrowPointer { get; init; } + internal readonly IntPtr ThrowNewPointer { get; init; } + internal readonly IntPtr ExceptionOccurredPointer { get; init; } + internal readonly IntPtr ExceptionDescribePointer { get; init; } + internal readonly IntPtr ExceptionClearPointer { get; init; } + internal readonly IntPtr FatalErrorPointer { get; init; } + internal readonly IntPtr PushLocalFramePointer { get; init; } + internal readonly IntPtr PopLocalFramePointer { get; init; } + internal readonly IntPtr NewGlobalRefPointer { get; init; } + internal readonly IntPtr DeleteGlobalRefPointer { get; init; } + internal readonly IntPtr DeleteLocalRefPointer { get; init; } + internal readonly IntPtr IsSameObjectPointer { get; init; } + internal readonly IntPtr NewLocalRefPointer { get; init; } + internal readonly IntPtr EnsureLocalCapacityPointer { get; init; } + internal readonly IntPtr AllocObjectPointer { get; init; } + internal readonly IntPtr NewObjectPointer { get; init; } + internal readonly IntPtr NewObjectVPointer { get; init; } + internal readonly IntPtr NewObjectAPointer { get; init; } + internal readonly IntPtr GetObjectClassPointer { get; init; } + internal readonly IntPtr IsInstanceOfPointer { get; init; } + internal readonly IntPtr GetMethodIdPointer { get; init; } + internal readonly IntPtr CallObjectMethodPointer { get; init; } + internal readonly IntPtr CallObjectMethodVPointer { get; init; } + internal readonly IntPtr CallObjectMethodAPointer { get; init; } + internal readonly IntPtr CallBooleanMethodPointer { get; init; } + internal readonly IntPtr CallBooleanMethodVPointer { get; init; } + internal readonly IntPtr CallBooleanMethodAPointer { get; init; } + internal readonly IntPtr CallByteMethodPointer { get; init; } + internal readonly IntPtr CallByteMethodVPointer { get; init; } + internal readonly IntPtr CallByteMethodAPointer { get; init; } + internal readonly IntPtr CallCharMethodPointer { get; init; } + internal readonly IntPtr CallCharMethodVPointer { get; init; } + internal readonly IntPtr CallCharMethodAPointer { get; init; } + internal readonly IntPtr CallShortMethodPointer { get; init; } + internal readonly IntPtr CallShortMethodVPointer { get; init; } + internal readonly IntPtr CallShortMethodAPointer { get; init; } + internal readonly IntPtr CallIntMethodPointer { get; init; } + internal readonly IntPtr CallIntMethodVPointer { get; init; } + internal readonly IntPtr CallIntMethodAPointer { get; init; } + internal readonly IntPtr CallLongMethodPointer { get; init; } + internal readonly IntPtr CallLongMethodVPointer { get; init; } + internal readonly IntPtr CallLongMethodAPointer { get; init; } + internal readonly IntPtr CallFloatMethodPointer { get; init; } + internal readonly IntPtr CallFloatMethodVPointer { get; init; } + internal readonly IntPtr CallFloatMethodAPointer { get; init; } + internal readonly IntPtr CallDoubleMethodPointer { get; init; } + internal readonly IntPtr CallDoubleMethodVPointer { get; init; } + internal readonly IntPtr CallDoubleMethodAPointer { get; init; } + internal readonly IntPtr CallVoidMethodPointer { get; init; } + internal readonly IntPtr CallVoidMethodVPointer { get; init; } + internal readonly IntPtr CallVoidMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualObjectMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualObjectMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualObjectMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualBooleanMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualBooleanMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualBooleanMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualByteMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualByteMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualByteMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualCharMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualCharMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualCharMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualShortMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualShortMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualShortMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualIntMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualIntMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualIntMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualLongMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualLongMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualLongMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualFloatMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualFloatMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualFloatMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualDoubleMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualDoubleMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualDoubleMethodAPointer { get; init; } + internal readonly IntPtr CallNonVirtualVoidMethodPointer { get; init; } + internal readonly IntPtr CallNonVirtualVoidMethodVPointer { get; init; } + internal readonly IntPtr CallNonVirtualVoidMethodAPointer { get; init; } + internal readonly IntPtr GetFieldIdPointer { get; init; } + internal readonly IntPtr GetObjectFieldPointer { get; init; } + internal readonly IntPtr GetBooleanFieldPointer { get; init; } + internal readonly IntPtr GetByteFieldPointer { get; init; } + internal readonly IntPtr GetCharFieldPointer { get; init; } + internal readonly IntPtr GetShortFieldPointer { get; init; } + internal readonly IntPtr GetIntFieldPointer { get; init; } + internal readonly IntPtr GetLongFieldPointer { get; init; } + internal readonly IntPtr GetFloatFieldPointer { get; init; } + internal readonly IntPtr GetDoubleFieldPointer { get; init; } + internal readonly IntPtr SetObjectFieldPointer { get; init; } + internal readonly IntPtr SetBooleanFieldPointer { get; init; } + internal readonly IntPtr SetByteFieldPointer { get; init; } + internal readonly IntPtr SetCharFieldPointer { get; init; } + internal readonly IntPtr SetShortFieldPointer { get; init; } + internal readonly IntPtr SetIntFieldPointer { get; init; } + internal readonly IntPtr SetLongFieldPointer { get; init; } + internal readonly IntPtr SetFloatFieldPointer { get; init; } + internal readonly IntPtr SetDoubleFieldPointer { get; init; } + internal readonly IntPtr GetStaticMethodIdPointer { get; init; } + internal readonly IntPtr CallStaticObjectMethodPointer { get; init; } + internal readonly IntPtr CallStaticObjectMethodVPointer { get; init; } + internal readonly IntPtr CallStaticObjectMethodAPointer { get; init; } + internal readonly IntPtr CallStaticBooleanMethodPointer { get; init; } + internal readonly IntPtr CallStaticBooleanMethodVPointer { get; init; } + internal readonly IntPtr CallStaticBooleanMethodAPointer { get; init; } + internal readonly IntPtr CallStaticByteMethodPointer { get; init; } + internal readonly IntPtr CallStaticByteMethodVPointer { get; init; } + internal readonly IntPtr CallStaticByteMethodAPointer { get; init; } + internal readonly IntPtr CallStaticCharMethodPointer { get; init; } + internal readonly IntPtr CallStaticCharMethodVPointer { get; init; } + internal readonly IntPtr CallStaticCharMethodAPointer { get; init; } + internal readonly IntPtr CallStaticShortMethodPointer { get; init; } + internal readonly IntPtr CallStaticShortMethodVPointer { get; init; } + internal readonly IntPtr CallStaticShortMethodAPointer { get; init; } + internal readonly IntPtr CallStaticIntMethodPointer { get; init; } + internal readonly IntPtr CallStaticIntMethodVPointer { get; init; } + internal readonly IntPtr CallStaticIntMethodAPointer { get; init; } + internal readonly IntPtr CallStaticLongMethodPointer { get; init; } + internal readonly IntPtr CallStaticLongMethodVPointer { get; init; } + internal readonly IntPtr CallStaticLongMethodAPointer { get; init; } + internal readonly IntPtr CallStaticFloatMethodPointer { get; init; } + internal readonly IntPtr CallStaticFloatMethodVPointer { get; init; } + internal readonly IntPtr CallStaticFloatMethodAPointer { get; init; } + internal readonly IntPtr CallStaticDoubleMethodPointer { get; init; } + internal readonly IntPtr CallStaticDoubleMethodVPointer { get; init; } + internal readonly IntPtr CallStaticDoubleMethodAPointer { get; init; } + internal readonly IntPtr CallStaticVoidMethodPointer { get; init; } + internal readonly IntPtr CallStaticVoidMethodVPointer { get; init; } + internal readonly IntPtr CallStaticVoidMethodAPointer { get; init; } + internal readonly IntPtr GetStaticFieldIdPointer { get; init; } + internal readonly IntPtr GetStaticObjectFieldPointer { get; init; } + internal readonly IntPtr GetStaticBooleanFieldPointer { get; init; } + internal readonly IntPtr GetStaticByteFieldPointer { get; init; } + internal readonly IntPtr GetStaticCharFieldPointer { get; init; } + internal readonly IntPtr GetStaticShortFieldPointer { get; init; } + internal readonly IntPtr GetStaticIntFieldPointer { get; init; } + internal readonly IntPtr GetStaticLongFieldPointer { get; init; } + internal readonly IntPtr GetStaticFloatFieldPointer { get; init; } + internal readonly IntPtr GetStaticDoubleFieldPointer { get; init; } + internal readonly IntPtr SetStaticObjectFieldPointer { get; init; } + internal readonly IntPtr SetStaticBooleanFieldPointer { get; init; } + internal readonly IntPtr SetStaticByteFieldPointer { get; init; } + internal readonly IntPtr SetStaticCharFieldPointer { get; init; } + internal readonly IntPtr SetStaticShortFieldPointer { get; init; } + internal readonly IntPtr SetStaticIntFieldPointer { get; init; } + internal readonly IntPtr SetStaticLongFieldPointer { get; init; } + internal readonly IntPtr SetStaticFloatFieldPointer { get; init; } + internal readonly IntPtr SetStaticDoubleFieldPointer { get; init; } + internal readonly IntPtr NewStringPointer { get; init; } + internal readonly IntPtr GetStringLengthPointer { get; init; } + internal readonly IntPtr GetStringCharsPointer { get; init; } + internal readonly IntPtr ReleaseStringCharsPointer { get; init; } + internal readonly IntPtr NewStringUtfPointer { get; init; } + internal readonly IntPtr GetStringUtfLengthPointer { get; init; } + internal readonly IntPtr GetStringUtfCharsPointer { get; init; } + internal readonly IntPtr ReleaseStringUtfCharsPointer { get; init; } + internal readonly IntPtr GetArrayLengthPointer { get; init; } + internal readonly IntPtr NewObjectArrayPointer { get; init; } + internal readonly IntPtr GetObjectArrayElementPointer { get; init; } + internal readonly IntPtr SetObjectArrayElementPointer { get; init; } + internal readonly IntPtr NewBooleanArrayPointer { get; init; } + internal readonly IntPtr NewByteArrayPointer { get; init; } + internal readonly IntPtr NewCharArrayPointer { get; init; } + internal readonly IntPtr NewShortArrayPointer { get; init; } + internal readonly IntPtr NewIntArrayPointer { get; init; } + internal readonly IntPtr NewLongArrayPointer { get; init; } + internal readonly IntPtr NewFloatArrayPointer { get; init; } + internal readonly IntPtr NewDoubleArrayPointer { get; init; } + internal readonly IntPtr GetBooleanArrayElementsPointer { get; init; } + internal readonly IntPtr GetByteArrayElementsPointer { get; init; } + internal readonly IntPtr GetCharArrayElementsPointer { get; init; } + internal readonly IntPtr GetShortArrayElementsPointer { get; init; } + internal readonly IntPtr GetIntArrayElementsPointer { get; init; } + internal readonly IntPtr GetLongArrayElementsPointer { get; init; } + internal readonly IntPtr GetFloatArrayElementsPointer { get; init; } + internal readonly IntPtr GetDoubleArrayElementsPointer { get; init; } + internal readonly IntPtr ReleaseBooleanArrayElementsPointer { get; init; } + internal readonly IntPtr ReleaseByteArrayElementsPointer { get; init; } + internal readonly IntPtr ReleaseCharArrayElementsPointer { get; init; } + internal readonly IntPtr ReleaseShortArrayElementsPointer { get; init; } + internal readonly IntPtr ReleaseIntArrayElementsPointer { get; init; } + internal readonly IntPtr ReleaseLongArrayElementsPointer { get; init; } + internal readonly IntPtr ReleaseFloatArrayElementsPointer { get; init; } + internal readonly IntPtr ReleaseDoubleArrayElementsPointer { get; init; } + internal readonly IntPtr GetBooleanArrayRegionPointer { get; init; } + internal readonly IntPtr GetByteArrayRegionPointer { get; init; } + internal readonly IntPtr GetCharArrayRegionPointer { get; init; } + internal readonly IntPtr GetShortArrayRegionPointer { get; init; } + internal readonly IntPtr GetIntArrayRegionPointer { get; init; } + internal readonly IntPtr GetLongArrayRegionPointer { get; init; } + internal readonly IntPtr GetFloatArrayRegionPointer { get; init; } + internal readonly IntPtr GetDoubleArrayRegionPointer { get; init; } + internal readonly IntPtr SetBooleanArrayRegionPointer { get; init; } + internal readonly IntPtr SetByteArrayRegionPointer { get; init; } + internal readonly IntPtr SetCharArrayRegionPointer { get; init; } + internal readonly IntPtr SetShortArrayRegionPointer { get; init; } + internal readonly IntPtr SetIntArrayRegionPointer { get; init; } + internal readonly IntPtr SetLongArrayRegionPointer { get; init; } + internal readonly IntPtr SetFloatArrayRegionPointer { get; init; } + internal readonly IntPtr SetDoubleArrayRegionPointer { get; init; } + internal readonly IntPtr RegisterNativesPointer { get; init; } + internal readonly IntPtr UnregisterNativesPointer { get; init; } + internal readonly IntPtr MonitorEnterPointer { get; init; } + internal readonly IntPtr MonitorExitPointer { get; init; } + internal readonly IntPtr GetJavaVMPointer { get; init; } + internal readonly IntPtr GetStringRegionPointer { get; init; } + internal readonly IntPtr GetStringUtfRegionPointer { get; init; } + internal readonly IntPtr GetPrimitiveArrayCriticalPointer { get; init; } + internal readonly IntPtr ReleasePrimitiveArrayCriticalPointer { get; init; } + internal readonly IntPtr GetStringCriticalPointer { get; init; } + internal readonly IntPtr ReleaseStringCriticalPointer { get; init; } + internal readonly IntPtr NewWeakGlobalRefPointer { get; init; } + internal readonly IntPtr DeleteWeakGlobalRefPointer { get; init; } + internal readonly IntPtr ExceptionCheckPointer { get; init; } + internal readonly IntPtr NewDirectByteBufferPointer { get; init; } + internal readonly IntPtr GetDirectBufferAddressPointer { get; init; } + internal readonly IntPtr GetDirectBufferCapacityPointer { get; init; } + internal readonly IntPtr GetObjectRefTypePointer { get; init; } + } +} diff --git a/src/LibRyujinx/Jni/Values/JNativeMethod.cs b/src/LibRyujinx/Jni/Values/JNativeMethod.cs new file mode 100644 index 000000000..10315071e --- /dev/null +++ b/src/LibRyujinx/Jni/Values/JNativeMethod.cs @@ -0,0 +1,12 @@ +using LibRyujinx.Jni.Pointers; +using System; + +namespace LibRyujinx.Jni.Values +{ + public readonly struct JNativeMethod + { + internal CCharSequence Name { get; init; } + internal CCharSequence Signature { get; init; } + internal IntPtr Pointer { get; init; } + } +} diff --git a/src/LibRyujinx/Jni/Values/JValue.cs b/src/LibRyujinx/Jni/Values/JValue.cs new file mode 100644 index 000000000..f6fadd199 --- /dev/null +++ b/src/LibRyujinx/Jni/Values/JValue.cs @@ -0,0 +1,47 @@ +using System.Runtime.CompilerServices; + +using Rxmxnx.PInvoke; +using System; + +namespace LibRyujinx.Jni.Values +{ + internal readonly struct JValue + { + private delegate Boolean IsDefaultDelegate(in JValue value); + + public static readonly Int32 Size = NativeUtilities.SizeOf(); + + private static readonly IsDefaultDelegate isDefault = GetIsDefault(); + +#pragma warning disable 0649 +#pragma warning disable 0169 + private readonly Byte _value1; + private readonly Byte _value2; + private readonly Int16 _value3; + private readonly Int32 _value4; +#pragma warning restore 0169 +#pragma warning restore 0649 + + public Boolean IsDefault => isDefault(this); + + public static JValue Create(in ReadOnlySpan source) + { + Byte[] result = new Byte[Size]; + for (Int32 i = 0; i < source.Length; i++) + result[i] = source[i]; + return result.ToValue(); + } + + private static IsDefaultDelegate GetIsDefault() => Environment.Is64BitProcess ? DefaultLong : Default; + + private static Boolean Default(in JValue jValue) + => (jValue._value1 + jValue._value2 + jValue._value3) == default + && jValue._value4 == default; + + private static Boolean DefaultLong(in JValue jValue) + { + var jv = jValue; + return Unsafe.AsRef(ref jv).Transform() == default; + } + } +} diff --git a/src/LibRyujinx/Jni/Values/JavaVMAttachArgs.cs b/src/LibRyujinx/Jni/Values/JavaVMAttachArgs.cs new file mode 100644 index 000000000..d8b827c4d --- /dev/null +++ b/src/LibRyujinx/Jni/Values/JavaVMAttachArgs.cs @@ -0,0 +1,13 @@ +using LibRyujinx.Jni.Pointers; +using LibRyujinx.Jni.References; +using System; + +namespace LibRyujinx.Jni.Values +{ + public readonly struct JavaVMAttachArgs + { + internal Int32 Version { get; init; } + internal CCharSequence Name { get; init; } + internal JObjectLocalRef Group { get; init; } + } +} diff --git a/src/LibRyujinx/Jni/Values/JavaVMInitArgs.cs b/src/LibRyujinx/Jni/Values/JavaVMInitArgs.cs new file mode 100644 index 000000000..d9fa6d161 --- /dev/null +++ b/src/LibRyujinx/Jni/Values/JavaVMInitArgs.cs @@ -0,0 +1,12 @@ +using System; + +namespace LibRyujinx.Jni.Values +{ + public readonly struct JavaVMInitArgs + { + internal Int32 Version { get; init; } + internal Int32 OptionsLenght { get; init; } + internal IntPtr Options { get; init; } + internal Boolean IgnoreUnrecognized { get; init; } + } +} diff --git a/src/LibRyujinx/Jni/Values/JavaVMOption.cs b/src/LibRyujinx/Jni/Values/JavaVMOption.cs new file mode 100644 index 000000000..153fc64c3 --- /dev/null +++ b/src/LibRyujinx/Jni/Values/JavaVMOption.cs @@ -0,0 +1,11 @@ +using LibRyujinx.Jni.Pointers; +using System; + +namespace LibRyujinx.Jni.Values +{ + public readonly struct JavaVMOption + { + internal CCharSequence Name { get; init; } + internal IntPtr ExtraInfo { get; init; } + } +} diff --git a/src/LibRyujinx/LibRyujinx.Device.cs b/src/LibRyujinx/LibRyujinx.Device.cs new file mode 100644 index 000000000..d6f90d2e0 --- /dev/null +++ b/src/LibRyujinx/LibRyujinx.Device.cs @@ -0,0 +1,303 @@ +using ARMeilleure.Translation; +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Microsoft.Win32.SafeHandles; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Input.HLE; +using Silk.NET.Vulkan; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace LibRyujinx +{ + public static partial class LibRyujinx + { + [UnmanagedCallersOnly(EntryPoint = "device_initialize")] + public static bool InitializeDeviceNative(bool isHostMapped, + bool useNce, + SystemLanguage systemLanguage, + RegionCode regionCode, + bool enableVsync, + bool enableDockedMode, + bool enablePtc, + bool enableInternetAccess, + IntPtr timeZone, + bool ignoreMissingServices) + { + return InitializeDevice(isHostMapped, + useNce, + systemLanguage, + regionCode, + enableVsync, + enableDockedMode, + enablePtc, + enableInternetAccess, + Marshal.PtrToStringAnsi(timeZone), + ignoreMissingServices); + } + + [UnmanagedCallersOnly(EntryPoint = "device_reloadFilesystem")] + public static void ReloadFileSystem() + { + SwitchDevice?.ReloadFileSystem(); + } + + public static bool InitializeDevice(bool isHostMapped, + bool useNce, + SystemLanguage systemLanguage, + RegionCode regionCode, + bool enableVsync, + bool enableDockedMode, + bool enablePtc, + bool enableInternetAccess, + string? timeZone, + bool ignoreMissingServices) + { + if (SwitchDevice == null) + { + return false; + } + + return SwitchDevice.InitializeContext(isHostMapped, + useNce, + systemLanguage, + regionCode, + enableVsync, + enableDockedMode, + enablePtc, + enableInternetAccess, + timeZone, + ignoreMissingServices); + } + + [UnmanagedCallersOnly(EntryPoint = "device_load")] + public static bool LoadApplicationNative(IntPtr pathPtr) + { + if(SwitchDevice?.EmulationContext == null) + { + return false; + } + + var path = Marshal.PtrToStringAnsi(pathPtr); + + return LoadApplication(path); + } + + [UnmanagedCallersOnly(EntryPoint = "device_install_firmware")] + public static void InstallFirmwareNative(int descriptor, bool isXci) + { + var stream = OpenFile(descriptor); + + InstallFirmware(stream, isXci); + } + + [UnmanagedCallersOnly(EntryPoint = "device_get_installed_firmware_version")] + public static IntPtr GetInstalledFirmwareVersionNative() + { + var result = GetInstalledFirmwareVersion(); + return Marshal.StringToHGlobalAnsi(result); + } + + public static void InstallFirmware(Stream stream, bool isXci) + { + SwitchDevice?.ContentManager.InstallFirmware(stream, isXci); + } + + public static string GetInstalledFirmwareVersion() + { + var version = SwitchDevice?.ContentManager.GetCurrentFirmwareVersion(); + + if (version != null) + { + return version.VersionString; + } + + return String.Empty; + } + + public static SystemVersion? VerifyFirmware(Stream stream, bool isXci) + { + return SwitchDevice?.ContentManager?.VerifyFirmwarePackage(stream, isXci) ?? null; + } + + public static bool LoadApplication(Stream stream, FileType type, Stream? updateStream = null) + { + var emulationContext = SwitchDevice.EmulationContext; + return type switch + { + FileType.None => false, + FileType.Nsp => emulationContext?.LoadNsp(stream, updateStream) ?? false, + FileType.Xci => emulationContext?.LoadXci(stream, updateStream) ?? false, + FileType.Nro => emulationContext?.LoadProgram(stream, true, "") ?? false, + }; + } + + public static bool LaunchMiiEditApplet() + { + string contentPath = SwitchDevice.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); + + return LoadApplication(contentPath); + } + + public static bool LoadApplication(string? path) + { + var emulationContext = SwitchDevice.EmulationContext; + + if (Directory.Exists(path)) + { + string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(path, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + if (!emulationContext.LoadCart(path, romFsFiles[0])) + { + SwitchDevice.DisposeContext(); + + return false; + } + } + else + { + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + if (!emulationContext.LoadCart(path)) + { + SwitchDevice.DisposeContext(); + + return false; + } + } + } + else if (File.Exists(path)) + { + switch (Path.GetExtension(path).ToLowerInvariant()) + { + case ".xci": + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + if (!emulationContext.LoadXci(path)) + { + SwitchDevice.DisposeContext(); + + return false; + } + break; + case ".nca": + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + if (!emulationContext.LoadNca(path)) + { + SwitchDevice.DisposeContext(); + + return false; + } + break; + case ".nsp": + case ".pfs0": + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + if (!emulationContext.LoadNsp(path)) + { + SwitchDevice.DisposeContext(); + + return false; + } + break; + default: + Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); + try + { + if (!emulationContext.LoadProgram(path)) + { + SwitchDevice.DisposeContext(); + + return false; + } + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + SwitchDevice.DisposeContext(); + + return false; + } + break; + } + } + else + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{path}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + SwitchDevice.DisposeContext(); + + return false; + } + + Translator.IsReadyForTranslation.Reset(); + + return true; + } + + public static void SignalEmulationClose() + { + _isStopped = true; + _isActive = false; + + debug_break(2); + } + + public static void CloseEmulation() + { + if (SwitchDevice == null) + return; + + _npadManager?.Dispose(); + _npadManager = null; + + _touchScreenManager?.Dispose(); + _touchScreenManager = null; + + SwitchDevice?.InputManager?.Dispose(); + SwitchDevice.InputManager = null; + _inputManager = null; + + + _surfaceEvent?.Set(); + + if (Renderer != null) + { + _gpuDoneEvent.WaitOne(); + _gpuDoneEvent.Dispose(); + _gpuDoneEvent = null; + SwitchDevice?.DisposeContext(); + Renderer = null; + } + } + + private static FileStream OpenFile(int descriptor) + { + var safeHandle = new SafeFileHandle(descriptor, false); + + return new FileStream(safeHandle, FileAccess.ReadWrite); + } + + public enum FileType + { + None, + Nsp, + Xci, + Nro + } + } +} diff --git a/src/LibRyujinx/LibRyujinx.Graphics.cs b/src/LibRyujinx/LibRyujinx.Graphics.cs new file mode 100644 index 000000000..24364a242 --- /dev/null +++ b/src/LibRyujinx/LibRyujinx.Graphics.cs @@ -0,0 +1,325 @@ +using ARMeilleure.Translation; +using LibHac.Bcat; +using LibRyujinx.Shared; +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Configuration; +using Ryujinx.Cpu; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.Graphics.Vulkan; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; + +namespace LibRyujinx +{ + public static partial class LibRyujinx + { + private static bool _isActive; + private static bool _isStopped; + private static CancellationTokenSource _gpuCancellationTokenSource; + private static SwapBuffersCallback? _swapBuffersCallback; + private static NativeGraphicsInterop _nativeGraphicsInterop; + private static ManualResetEvent _gpuDoneEvent; + + public delegate void SwapBuffersCallback(); + public delegate IntPtr GetProcAddress(string name); + public delegate IntPtr CreateSurface(IntPtr instance); + + public static IRenderer? Renderer { get; set; } + public static GraphicsConfiguration GraphicsConfiguration { get; private set; } + + [UnmanagedCallersOnly(EntryPoint = "graphics_initialize")] + public static bool InitializeGraphicsNative(GraphicsConfiguration graphicsConfiguration) + { + if (Ryujinx.Common.PlatformInfo.IsBionic) + { + Silk.NET.Core.Loader.SearchPathContainer.Platform = Silk.NET.Core.Loader.UnderlyingPlatform.Android; + } + else if (OperatingSystem.IsIOS()) + { + // Yes, macOS not iOS + Silk.NET.Core.Loader.SearchPathContainer.Platform = Silk.NET.Core.Loader.UnderlyingPlatform.MacOS; + } + return InitializeGraphics(graphicsConfiguration); + } + + public static bool InitializeGraphics(GraphicsConfiguration graphicsConfiguration) + { + GraphicsConfig.ResScale = graphicsConfiguration.ResScale; + GraphicsConfig.MaxAnisotropy = graphicsConfiguration.MaxAnisotropy; + GraphicsConfig.FastGpuTime = graphicsConfiguration.FastGpuTime; + GraphicsConfig.Fast2DCopy = graphicsConfiguration.Fast2DCopy; + GraphicsConfig.EnableMacroJit = graphicsConfiguration.EnableMacroJit; + GraphicsConfig.EnableMacroHLE = graphicsConfiguration.EnableMacroHLE; + GraphicsConfig.EnableShaderCache = graphicsConfiguration.EnableShaderCache; + GraphicsConfig.EnableTextureRecompression = graphicsConfiguration.EnableTextureRecompression; + + GraphicsConfiguration = graphicsConfiguration; + + return true; + } + + [UnmanagedCallersOnly(EntryPoint = "graphics_initialize_renderer")] + public unsafe static bool InitializeGraphicsRendererNative(GraphicsBackend graphicsBackend, NativeGraphicsInterop nativeGraphicsInterop) + { + _nativeGraphicsInterop = nativeGraphicsInterop; + if (Renderer != null) + { + return false; + } + + List extensions = new List(); + var size = Marshal.SizeOf(); + var extPtr = (IntPtr*)nativeGraphicsInterop.VkRequiredExtensions; + for (int i = 0; i < nativeGraphicsInterop.VkRequiredExtensionsCount; i++) + { + var ptr = extPtr[i]; + extensions.Add(Marshal.PtrToStringAnsi(ptr) ?? string.Empty); + } + + CreateSurface createSurfaceFunc = nativeGraphicsInterop.VkCreateSurface == IntPtr.Zero ? default : Marshal.GetDelegateForFunctionPointer(nativeGraphicsInterop.VkCreateSurface); + + return InitializeGraphicsRenderer(graphicsBackend, createSurfaceFunc, extensions.ToArray()); + } + + public static bool InitializeGraphicsRenderer(GraphicsBackend graphicsBackend, CreateSurface createSurfaceFunc, string?[] requiredExtensions) + { + if (Renderer != null) + { + return false; + } + + if (graphicsBackend == GraphicsBackend.OpenGl) + { + Renderer = new OpenGLRenderer(); + } + else if (graphicsBackend == GraphicsBackend.Vulkan) + { + Renderer = new VulkanRenderer(VulkanLoader?.GetApi() ?? Vk.GetApi(), (instance, vk) => new SurfaceKHR((ulong?)createSurfaceFunc(instance.Handle)), + () => requiredExtensions, + null); + } + else + { + return false; + } + + return true; + } + + + [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_size")] + public static void SetRendererSizeNative(int width, int height) + { + SetRendererSize(width, height); + } + + public static void SetRendererSize(int width, int height) + { + Renderer?.Window?.SetSize(width, height); + } + + [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_run_loop")] + public static void RunLoopNative() + { + if (Renderer is OpenGLRenderer) + { + var proc = Marshal.GetDelegateForFunctionPointer(_nativeGraphicsInterop.GlGetProcAddress); + GL.LoadBindings(new OpenTKBindingsContext(x => proc!.Invoke(x))); + } + RunLoop(); + } + + [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_vsync")] + public static void SetVsyncStateNative(bool enabled) + { + SetVsyncState(enabled); + } + + public static void SetVsyncState(bool enabled) + { + var device = SwitchDevice!.EmulationContext!; + device.EnableDeviceVsync = enabled; + device.Gpu.Renderer.Window.ChangeVSyncMode(enabled); + } + + public static void RunLoop() + { + if (Renderer == null) + { + return; + } + var device = SwitchDevice!.EmulationContext!; + _gpuDoneEvent = new ManualResetEvent(true); + + device.Gpu.Renderer.Initialize(GraphicsDebugLevel.None); + _gpuCancellationTokenSource = new CancellationTokenSource(); + + device.Gpu.ShaderCacheStateChanged += LoadProgressStateChangedHandler; + device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += LoadProgressStateChangedHandler; + + try + { + device.Gpu.Renderer.RunLoop(() => + { + _gpuDoneEvent.Reset(); + device.Gpu.SetGpuThread(); + device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + Translator.IsReadyForTranslation.Set(); + + _isActive = true; + + while (_isActive) + { + if (_isStopped) + { + break; + } + + debug_break(1); + + if (Ryujinx.Common.PlatformInfo.IsBionic) + { + setRenderingThread(); + } + + if (device.WaitFifo()) + { + device.Statistics.RecordFifoStart(); + device.ProcessFrame(); + device.Statistics.RecordFifoEnd(); + } + + while (device.ConsumeFrameAvailable()) + { + device.PresentFrame(() => + { + VulkanRenderer? vk = device.Gpu.Renderer as VulkanRenderer; + if (vk == null) + { + vk = (device.Gpu.Renderer as ThreadedRenderer)?.BaseRenderer as VulkanRenderer; + } + + if (vk != null) + { + var transform = vk.CurrentTransform; + + setCurrentTransform(_window, (int)transform); + } + _swapBuffersCallback?.Invoke(); + }); + } + } + + if (device.Gpu.Renderer is ThreadedRenderer threaded) + { + threaded.FlushThreadedCommands(); + } + + _gpuDoneEvent.Set(); + }); + } + finally + { + device.Gpu.ShaderCacheStateChanged -= LoadProgressStateChangedHandler; + device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= LoadProgressStateChangedHandler; + } + } + + private static void LoadProgressStateChangedHandler(T state, int current, int total) where T : Enum + { + void SetInfo(string status, float value) + { + var ptr = Marshal.StringToHGlobalAnsi(status); + + setProgressInfo(ptr, value); + + Marshal.FreeHGlobal(ptr); + } + var status = $"{current} / {total}"; + var progress = current / (float)total; + + switch (state) + { + case LoadState ptcState: + if (float.IsNaN((progress))) + progress = 0; + + switch (ptcState) + { + case LoadState.Unloaded: + case LoadState.Loading: + SetInfo($"Loading PTC {status}", progress); + break; + case LoadState.Loaded: + SetInfo($"PTC Loaded", -1); + break; + } + break; + case ShaderCacheState shaderCacheState: + switch (shaderCacheState) + { + case ShaderCacheState.Start: + case ShaderCacheState.Loading: + SetInfo($"Compiling Shaders {status}", progress); + break; + case ShaderCacheState.Packaging: + SetInfo($"Packaging Shaders {status}", progress); + break; + case ShaderCacheState.Loaded: + SetInfo($"Shaders Loaded", -1); + break; + } + break; + default: + throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"); + } + } + + [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_swap_buffer_callback")] + public static void SetSwapBuffersCallbackNative(IntPtr swapBuffersCallback) + { + _swapBuffersCallback = Marshal.GetDelegateForFunctionPointer(swapBuffersCallback); + } + + public static void SetSwapBuffersCallback(SwapBuffersCallback swapBuffersCallback) + { + _swapBuffersCallback = swapBuffersCallback; + } + } + + + [StructLayout(LayoutKind.Sequential)] + public struct GraphicsConfiguration + { + public float ResScale = 1f; + public float MaxAnisotropy = -1; + public bool FastGpuTime = true; + public bool Fast2DCopy = true; + public bool EnableMacroJit = false; + public bool EnableMacroHLE = true; + public bool EnableShaderCache = true; + public bool EnableTextureRecompression = false; + public BackendThreading BackendThreading = BackendThreading.Auto; + public AspectRatio AspectRatio = AspectRatio.Fixed16x9; + + public GraphicsConfiguration() + { + } + } + + public struct NativeGraphicsInterop + { + public IntPtr GlGetProcAddress; + public IntPtr VkNativeContextLoader; + public IntPtr VkCreateSurface; + public IntPtr VkRequiredExtensions; + public int VkRequiredExtensionsCount; + } +} diff --git a/src/LibRyujinx/LibRyujinx.Input.cs b/src/LibRyujinx/LibRyujinx.Input.cs new file mode 100644 index 000000000..d10e37550 --- /dev/null +++ b/src/LibRyujinx/LibRyujinx.Input.cs @@ -0,0 +1,602 @@ +using DiscordRPC; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Input; +using Ryujinx.Input.HLE; +using Ryujinx.Ui.Common.Configuration; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; +using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; +using StickInputId = Ryujinx.Input.StickInputId; + +namespace LibRyujinx +{ + public static partial class LibRyujinx + { + private static VirtualGamepadDriver? _gamepadDriver; + private static VirtualTouchScreen? _virtualTouchScreen; + private static VirtualTouchScreenDriver? _touchScreenDriver; + private static TouchScreenManager? _touchScreenManager; + private static InputManager? _inputManager; + private static NpadManager? _npadManager; + private static InputConfig[] _configs; + + public static void InitializeInput(int width, int height) + { + if(SwitchDevice!.InputManager != null) + { + throw new InvalidOperationException("Input is already initialized"); + } + + _gamepadDriver = new VirtualGamepadDriver(4); + _configs = new InputConfig[4]; + _virtualTouchScreen = new VirtualTouchScreen(); + _touchScreenDriver = new VirtualTouchScreenDriver(_virtualTouchScreen); + _inputManager = new InputManager(null, _gamepadDriver); + _inputManager.SetMouseDriver(_touchScreenDriver); + _npadManager = _inputManager.CreateNpadManager(); + + SwitchDevice!.InputManager = _inputManager; + + _touchScreenManager = _inputManager.CreateTouchScreenManager(); + _touchScreenManager.Initialize(SwitchDevice!.EmulationContext); + + _npadManager.Initialize(SwitchDevice.EmulationContext, new List(), false, false); + + _virtualTouchScreen.ClientSize = new Size(width, height); + } + + public static void SetClientSize(int width, int height) + { + _virtualTouchScreen!.ClientSize = new Size(width, height); + } + + public static void SetTouchPoint(int x, int y) + { + _virtualTouchScreen?.SetPosition(x, y); + } + + public static void ReleaseTouchPoint() + { + _virtualTouchScreen?.ReleaseTouch(); + } + + public static void SetButtonPressed(GamepadButtonInputId button, int id) + { + _gamepadDriver?.SetButtonPressed(button, id); + } + + public static void SetButtonReleased(GamepadButtonInputId button, int id) + { + _gamepadDriver?.SetButtonReleased(button, id); + } + + public static void SetAccelerometerData(Vector3 accel, int id) + { + _gamepadDriver?.SetAccelerometerData(accel, id); + } + + public static void SetGryoData(Vector3 gyro, int id) + { + _gamepadDriver?.SetGryoData(gyro, id); + } + + public static void SetStickAxis(StickInputId stick, Vector2 axes, int deviceId) + { + _gamepadDriver?.SetStickAxis(stick, axes, deviceId); + } + + public static int ConnectGamepad(int index) + { + var gamepad = _gamepadDriver?.GetGamepad(index); + if (gamepad != null) + { + var config = CreateDefaultInputConfig(); + + config.Id = gamepad.Id; + config.PlayerIndex = (PlayerIndex)index; + + _configs[index] = config; + } + + _npadManager?.ReloadConfiguration(_configs.Where(x => x != null).ToList(), false, false); + + return int.TryParse(gamepad?.Id, out var idInt) ? idInt : -1; + } + + private static InputConfig CreateDefaultInputConfig() + { + return new StandardControllerInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.GamepadSDL2, + Id = null, + ControllerType = ControllerType.ProController, + DeadzoneLeft = 0.1f, + DeadzoneRight = 0.1f, + RangeLeft = 1.0f, + RangeRight = 1.0f, + TriggerThreshold = 0.5f, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = ConfigGamepadInputId.DpadUp, + DpadDown = ConfigGamepadInputId.DpadDown, + DpadLeft = ConfigGamepadInputId.DpadLeft, + DpadRight = ConfigGamepadInputId.DpadRight, + ButtonMinus = ConfigGamepadInputId.Minus, + ButtonL = ConfigGamepadInputId.LeftShoulder, + ButtonZl = ConfigGamepadInputId.LeftTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + LeftJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Left, + StickButton = ConfigGamepadInputId.LeftStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = ConfigGamepadInputId.A, + ButtonB = ConfigGamepadInputId.B, + ButtonX = ConfigGamepadInputId.X, + ButtonY = ConfigGamepadInputId.Y, + ButtonPlus = ConfigGamepadInputId.Plus, + ButtonR = ConfigGamepadInputId.RightShoulder, + ButtonZr = ConfigGamepadInputId.RightTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + RightJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Right, + StickButton = ConfigGamepadInputId.RightStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + Motion = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = true, + Sensitivity = 100, + GyroDeadzone = 1, + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false + } + }; + } + + public static void UpdateInput() + { + _npadManager?.Update(GraphicsConfiguration.AspectRatio.ToFloat()); + + if(!_touchScreenManager!.Update(true, _virtualTouchScreen!.IsButtonPressed(MouseButton.Button1), GraphicsConfiguration.AspectRatio.ToFloat())) + { + SwitchDevice!.EmulationContext?.Hid.Touchscreen.Update(); + } + } + + // Native Methods + + [UnmanagedCallersOnly(EntryPoint = "input_initialize")] + public static void InitializeInputNative(int width, int height) + { + InitializeInput(width, height); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_client_size")] + public static void SetClientSizeNative(int width, int height) + { + SetClientSize(width, height); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_touch_point")] + public static void SetTouchPointNative(int x, int y) + { + SetTouchPoint(x, y); + } + + + [UnmanagedCallersOnly(EntryPoint = "input_release_touch_point")] + public static void ReleaseTouchPointNative() + { + ReleaseTouchPoint(); + } + + [UnmanagedCallersOnly(EntryPoint = "input_update")] + public static void UpdateInputNative() + { + UpdateInput(); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_button_pressed")] + public static void SetButtonPressedNative(GamepadButtonInputId button, int id) + { + SetButtonPressed(button, id); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_button_released")] + public static void SetButtonReleasedNative(GamepadButtonInputId button, int id) + { + SetButtonReleased(button, id); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_accelerometer_data")] + public static void SetAccelerometerDataNative(Vector3 accel, int id) + { + SetAccelerometerData(accel, id); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_gyro_data")] + public static void SetGryoDataNatuve(Vector3 gyro, int id) + { + SetGryoData(gyro, id); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_stick_axis")] + public static void SetStickAxisNative(StickInputId stick, Vector2 axes, int id) + { + SetStickAxis(stick, axes, id); + } + + [UnmanagedCallersOnly(EntryPoint = "input_connect_gamepad")] + public static IntPtr ConnectGamepadNative(int index) + { + return ConnectGamepad(index); + } + + } + + public class VirtualTouchScreen : IMouse + { + public Size ClientSize { get; set; } + + public bool[] Buttons { get; } + + public VirtualTouchScreen() + { + Buttons = new bool[2]; + } + + public Vector2 CurrentPosition { get; private set; } + public Vector2 Scroll { get; private set; } + public string Id => "0"; + public string Name => "AvaloniaMouse"; + + public bool IsConnected => true; + public GamepadFeaturesFlag Features => throw new NotImplementedException(); + + public void Dispose() + { + + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + throw new NotImplementedException(); + } + + public void SetPosition(int x, int y) + { + CurrentPosition = new Vector2(x, y); + + Buttons[0] = true; + } + + public void ReleaseTouch() + { + Buttons[0] = false; + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + throw new NotImplementedException(); + } + + public Vector2 GetPosition() + { + return CurrentPosition; + } + + public Vector2 GetScroll() + { + return Scroll; + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotImplementedException(); + } + + public (float, float) GetStick(Ryujinx.Input.StickInputId inputId) + { + throw new NotImplementedException(); + } + + public bool IsButtonPressed(MouseButton button) + { + return Buttons[0]; + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotImplementedException(); + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + throw new NotImplementedException(); + } + + public void SetConfiguration(InputConfig configuration) + { + throw new NotImplementedException(); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + throw new NotImplementedException(); + } + } + + public class VirtualTouchScreenDriver : IGamepadDriver + { + private readonly VirtualTouchScreen _virtualTouchScreen; + + public VirtualTouchScreenDriver(VirtualTouchScreen virtualTouchScreen) + { + _virtualTouchScreen = virtualTouchScreen; + } + + public string DriverName => "VirtualTouchDriver"; + + public ReadOnlySpan GamepadsIds => new[] { "0" }; + + + public event Action OnGamepadConnected + { + add { } + remove { } + } + + public event Action OnGamepadDisconnected + { + add { } + remove { } + } + + public void Dispose() + { + + } + + public IGamepad GetGamepad(string id) + { + return _virtualTouchScreen; + } + } + + public class VirtualGamepadDriver : IGamepadDriver + { + private readonly int _controllerCount; + + public ReadOnlySpan GamepadsIds => _gamePads.Keys.Select(x => x.ToString()).ToArray(); + + public string DriverName => "Virtual"; + + public event Action OnGamepadConnected; + public event Action OnGamepadDisconnected; + + private Dictionary _gamePads; + + public VirtualGamepadDriver(int controllerCount) + { + _gamePads = new Dictionary(); + for (int joystickIndex = 0; joystickIndex < controllerCount; joystickIndex++) + { + HandleJoyStickConnected(joystickIndex); + } + + _controllerCount = controllerCount; + } + + private void HandleJoyStickConnected(int joystickDeviceId) + { + _gamePads[joystickDeviceId] = new VirtualGamepad(this, joystickDeviceId); + OnGamepadConnected?.Invoke(joystickDeviceId.ToString()); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Simulate a full disconnect when disposing + var ids = GamepadsIds; + foreach (string id in ids) + { + OnGamepadDisconnected?.Invoke(id); + } + + _gamePads.Clear(); + } + } + + public void Dispose() + { + Dispose(true); + } + + public IGamepad GetGamepad(string id) + { + return _gamePads[int.Parse(id)]; + } + + public IGamepad GetGamepad(int index) + { + return _gamePads[index]; + } + + public void SetStickAxis(StickInputId stick, Vector2 axes, int deviceId) + { + if(_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.StickInputs[(int)stick] = axes; + } + } + + public void SetButtonPressed(GamepadButtonInputId button, int deviceId) + { + if (_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.ButtonInputs[(int)button] = true; + } + } + + public void SetButtonReleased(GamepadButtonInputId button, int deviceId) + { + if (_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.ButtonInputs[(int)button] = false; + } + } + + public void SetAccelerometerData(Vector3 accel, int deviceId) + { + if (_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.Accelerometer = accel; + } + } + + public void SetGryoData(Vector3 gyro, int deviceId) + { + if (_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.Gyro = gyro; + } + } + } + + public class VirtualGamepad : IGamepad + { + private readonly VirtualGamepadDriver _driver; + + private bool[] _buttonInputs; + + private Vector2[] _stickInputs; + + public VirtualGamepad(VirtualGamepadDriver driver, int id) + { + _buttonInputs = new bool[(int)GamepadButtonInputId.Count]; + _stickInputs = new Vector2[(int)StickInputId.Count]; + _driver = driver; + Id = id.ToString(); + IdInt = id; + } + + public void Dispose() { } + + public GamepadFeaturesFlag Features { get; } = GamepadFeaturesFlag.Motion; + public string Id { get; } + + internal readonly int IdInt; + + public string Name => Id; + public bool IsConnected { get; } + public Vector2[] StickInputs { get => _stickInputs; set => _stickInputs = value; } + public bool[] ButtonInputs { get => _buttonInputs; set => _buttonInputs = value; } + public Vector3 Accelerometer { get; internal set; } + public Vector3 Gyro { get; internal set; } + + public bool IsPressed(GamepadButtonInputId inputId) + { + return _buttonInputs[(int)inputId]; + } + + public (float, float) GetStick(StickInputId inputId) + { + var v = _stickInputs[(int)inputId]; + + return (v.X, v.Y); + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + if (inputId == MotionInputId.Accelerometer) + return Accelerometer; + else if (inputId == MotionInputId.Gyroscope) + return RadToDegree(Gyro); + return new Vector3(); + } + + private static Vector3 RadToDegree(Vector3 rad) + { + return rad * (180 / MathF.PI); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + //throw new System.NotImplementedException(); + } + + public void SetConfiguration(InputConfig configuration) + { + //throw new System.NotImplementedException(); + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + //throw new System.NotImplementedException(); + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + GamepadStateSnapshot result = default; + + foreach (var button in Enum.GetValues()) + { + // Do not touch state of button already pressed + if (button != GamepadButtonInputId.Count && !result.IsPressed(button)) + { + result.SetPressed(button, IsPressed(button)); + } + } + + (float leftStickX, float leftStickY) = GetStick(StickInputId.Left); + (float rightStickX, float rightStickY) = GetStick(StickInputId.Right); + + result.SetStick(StickInputId.Left, leftStickX, leftStickY); + result.SetStick(StickInputId.Right, rightStickX, rightStickY); + + return result; + } + + public GamepadStateSnapshot GetStateSnapshot() + { + return new GamepadStateSnapshot(); + } + } +} diff --git a/src/LibRyujinx/LibRyujinx.User.cs b/src/LibRyujinx/LibRyujinx.User.cs new file mode 100644 index 000000000..f78ee22be --- /dev/null +++ b/src/LibRyujinx/LibRyujinx.User.cs @@ -0,0 +1,82 @@ +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; +using System.Linq; + +namespace LibRyujinx +{ + public static partial class LibRyujinx + { + public static string GetOpenedUser() + { + var lastProfile = SwitchDevice?.AccountManager.LastOpenedUser; + + return lastProfile?.UserId.ToString() ?? ""; + } + + public static string GetUserPicture(string userId) + { + var uid = new UserId(userId); + + var user = SwitchDevice?.AccountManager.GetAllUsers().FirstOrDefault(x => x.UserId == uid); + + if (user == null) + return ""; + + var pic = user.Image; + + return pic != null ? Convert.ToBase64String(pic) : ""; + } + + public static void SetUserPicture(string userId, string picture) + { + var uid = new UserId(userId); + + SwitchDevice?.AccountManager.SetUserImage(uid, Convert.FromBase64String(picture)); + } + + public static string GetUserName(string userId) + { + var uid = new UserId(userId); + + var user = SwitchDevice?.AccountManager.GetAllUsers().FirstOrDefault(x => x.UserId == uid); + + return user?.Name ?? ""; + } + + public static void SetUserName(string userId, string name) + { + var uid = new UserId(userId); + + SwitchDevice?.AccountManager.SetUserName(uid, name); + } + + public static string[] GetAllUsers() + { + return SwitchDevice?.AccountManager.GetAllUsers().Select(x => x.UserId.ToString()).ToArray() ?? + Array.Empty(); + } + + public static void AddUser(string userName, string picture) + { + SwitchDevice?.AccountManager.AddUser(userName, Convert.FromBase64String(picture)); + } + + public static void DeleteUser(string userId) + { + var uid = new UserId(userId); + SwitchDevice?.AccountManager.DeleteUser(uid); + } + + public static void OpenUser(string userId) + { + var uid = new UserId(userId); + SwitchDevice?.AccountManager.OpenUser(uid); + } + + public static void CloseUser(string userId) + { + var uid = new UserId(userId); + SwitchDevice?.AccountManager.CloseUser(uid); + } + } +} diff --git a/src/LibRyujinx/LibRyujinx.cs b/src/LibRyujinx/LibRyujinx.cs new file mode 100644 index 000000000..9162d03d0 --- /dev/null +++ b/src/LibRyujinx/LibRyujinx.cs @@ -0,0 +1,883 @@ +// State class for the library +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS; +using Ryujinx.Input.HLE; +using Ryujinx.HLE; +using System; +using System.Runtime.InteropServices; +using Ryujinx.Common.Configuration; +using LibHac.Tools.FsSystem; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Ui.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Backends.SDL2; +using System.IO; +using LibHac.Common.Keys; +using LibHac.Common; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem.NcaUtils; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Fs; +using Path = System.IO.Path; +using LibHac; +using OpenTK.Audio.OpenAL; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.HLE.Loaders.Npdm; +using Ryujinx.Common.Utilities; +using System.Globalization; +using Ryujinx.Ui.Common.Configuration.System; +using Ryujinx.Common.Logging.Targets; +using System.Collections.Generic; +using LibHac.Bcat; +using Ryujinx.Ui.App.Common; +using System.Text; +using Ryujinx.HLE.Ui; +using LibRyujinx.Android; + +namespace LibRyujinx +{ + public static partial class LibRyujinx + { + internal static IHardwareDeviceDriver AudioDriver { get; set; } = new DummyHardwareDeviceDriver(); + + private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + public static SwitchDevice? SwitchDevice { get; set; } + + [UnmanagedCallersOnly(EntryPoint = "initialize")] + public static bool Initialize(IntPtr basePathPtr) + { + var path = Marshal.PtrToStringAnsi(basePathPtr); + + var res = Initialize(path); + + InitializeAudio(); + + return res; + } + + public static bool Initialize(string? basePath) + { + if (SwitchDevice != null) + { + return false; + } + + try + { + AppDataManager.Initialize(basePath); + + ConfigurationState.Initialize(); + LoggerModule.Initialize(); + + Logger.AddTarget(new AsyncLogTargetWrapper( + new FileLogTarget(AppDataManager.BaseDirPath, "file"), + 1000, + AsyncLogTargetOverflowAction.Block + )); + + Logger.Notice.Print(LogClass.Application, "Initializing..."); + Logger.Notice.Print(LogClass.Application, $"Using base path: {AppDataManager.BaseDirPath}"); + + SwitchDevice = new SwitchDevice(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + return false; + } + + OpenALLibraryNameContainer.OverridePath = "libopenal.so"; + + Logger.Notice.Print(LogClass.Application, "RyujinxAndroid is ready!"); + + return true; + } + + public static void InitializeAudio() + { + AudioDriver = new SDL2HardwareDeviceDriver(); + } + + public static GameStats GetGameStats() + { + if (SwitchDevice?.EmulationContext == null) + return new GameStats(); + + var context = SwitchDevice.EmulationContext; + + return new GameStats + { + Fifo = context.Statistics.GetFifoPercent(), + GameFps = context.Statistics.GetGameFrameRate(), + GameTime = context.Statistics.GetGameFrameTime() + }; + } + + + public static GameInfo? GetGameInfo(string? file) + { + if (string.IsNullOrWhiteSpace(file)) + { + return new GameInfo(); + } + + Logger.Info?.Print(LogClass.Application, $"Getting game info for file: {file}"); + + using var stream = File.Open(file, FileMode.Open); + + return GetGameInfo(stream, new FileInfo(file).Extension.Remove('.')); + } + + [UnmanagedCallersOnly(EntryPoint = "get_game_info")] + public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr) + { + var extension = Marshal.PtrToStringAnsi(extensionPtr); + var stream = OpenFile(descriptor); + + var gameInfo = GetGameInfo(stream, extension); + + return new GameInfoNative(0, gameInfo.TitleName, 0, gameInfo.Developer, 0); + } + + public static GameInfo? GetGameInfo(Stream gameStream, string extension) + { + if (SwitchDevice == null) + { + Logger.Error?.Print(LogClass.Application, "SwitchDevice is not initialized."); + return null; + } + + var gameInfo = new GameInfo + { + FileSize = gameStream.Length * 0.000000000931, + TitleName = "Unknown", + TitleId = "0000000000000000", + Developer = "Unknown", + Version = "0", + Icon = null + }; + + const Language TitleLanguage = Language.AmericanEnglish; + + BlitStruct controlHolder = new(1); + + try + { + try + { + if (extension == "nsp" || extension == "pfs0" || extension == "xci") + { + IFileSystem pfs; + + bool isExeFs = false; + + if (extension == "xci") + { + Xci xci = new(SwitchDevice.VirtualFileSystem.KeySet, gameStream.AsStorage()); + + pfs = xci.OpenPartition(XciPartitionType.Secure); + } + else + { + var pfsTemp = new PartitionFileSystem(); + pfsTemp.Initialize(gameStream.AsStorage()).ThrowIfFailure(); + pfs = pfsTemp; + + // If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application. + bool hasMainNca = false; + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*")) + { + if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca") + { + using UniqueRef ncaFile = new(); + + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new(SwitchDevice.VirtualFileSystem.KeySet, ncaFile.Get.AsStorage()); + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + // Some main NCAs don't have a data partition, so check if the partition exists before opening it + if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())) + { + hasMainNca = true; + + break; + } + } + else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main") + { + isExeFs = true; + } + } + + if (!hasMainNca && !isExeFs) + { + return null; + } + } + + if (isExeFs) + { + using UniqueRef npdmFile = new(); + + Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); + + if (ResultFs.PathNotFound.Includes(result)) + { + Npdm npdm = new(npdmFile.Get.AsStream()); + + gameInfo.TitleName = npdm.TitleName; + gameInfo.TitleId = npdm.Aci0.TitleId.ToString("x16"); + } + } + else + { + GetControlFsAndTitleId(pfs, out IFileSystem? controlFs, out string? id); + + gameInfo.TitleId = id; + + if (controlFs == null) + { + Logger.Error?.Print(LogClass.Application, $"No control FS was returned. Unable to process game any further: {gameInfo.TitleName}"); + return null; + } + + // Check if there is an update available. + if (IsUpdateApplied(gameInfo.TitleId, out IFileSystem? updatedControlFs)) + { + // Replace the original ControlFs by the updated one. + controlFs = updatedControlFs; + } + + ReadControlData(controlFs, controlHolder.ByteSpan); + + GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out _, out gameInfo.Developer, out gameInfo.Version); + + // Read the icon from the ControlFS and store it as a byte array + try + { + using UniqueRef icon = new(); + + controlFs?.OpenFile(ref icon.Ref, $"/icon_{TitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = new(); + + icon.Get.AsStream().CopyTo(stream); + gameInfo.Icon = stream.ToArray(); + } + catch (HorizonResultException) + { + foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) + { + if (entry.Name == "control.nacp") + { + continue; + } + + using var icon = new UniqueRef(); + + controlFs?.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = new(); + + icon.Get.AsStream().CopyTo(stream); + gameInfo.Icon = stream.ToArray(); + + if (gameInfo.Icon != null) + { + break; + } + } + + } + } + } + else if (extension == "nro") + { + BinaryReader reader = new(gameStream); + + byte[] Read(long position, int size) + { + gameStream.Seek(position, SeekOrigin.Begin); + + return reader.ReadBytes(size); + } + + gameStream.Seek(24, SeekOrigin.Begin); + + int assetOffset = reader.ReadInt32(); + + if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") + { + byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); + + long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); + long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); + + ulong nacpOffset = reader.ReadUInt64(); + ulong nacpSize = reader.ReadUInt64(); + + // Reads and stores game icon as byte array + if (iconSize > 0) + { + gameInfo.Icon = Read(assetOffset + iconOffset, (int)iconSize); + } + + // Read the NACP data + Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan); + + GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out _, out gameInfo.Developer, out gameInfo.Version); + } + } + } + catch (MissingKeyException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); + } + catch (InvalidDataException exception) + { + Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. {exception}"); + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"The gameStream encountered was not of a valid type. Error: {exception}"); + + return null; + } + } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, exception.Message); + } + + void ReadControlData(IFileSystem? controlFs, Span outProperty) + { + using UniqueRef controlFile = new(); + + controlFs?.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure(); + } + + void GetGameInformation(ref ApplicationControlProperty controlData, out string? titleName, out string titleId, out string? publisher, out string? version) + { + _ = Enum.TryParse(TitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage); + + if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage) + { + titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString(); + publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString(); + } + else + { + titleName = null; + publisher = null; + } + + if (string.IsNullOrWhiteSpace(titleName)) + { + foreach (ref readonly var controlTitle in controlData.Title.ItemsRo) + { + if (!controlTitle.NameString.IsEmpty()) + { + titleName = controlTitle.NameString.ToString(); + + break; + } + } + } + + if (string.IsNullOrWhiteSpace(publisher)) + { + foreach (ref readonly var controlTitle in controlData.Title.ItemsRo) + { + if (!controlTitle.PublisherString.IsEmpty()) + { + publisher = controlTitle.PublisherString.ToString(); + + break; + } + } + } + + if (controlData.PresenceGroupId != 0) + { + titleId = controlData.PresenceGroupId.ToString("x16"); + } + else if (controlData.SaveDataOwnerId != 0) + { + titleId = controlData.SaveDataOwnerId.ToString(); + } + else if (controlData.AddOnContentBaseId != 0) + { + titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16"); + } + else + { + titleId = "0000000000000000"; + } + + version = controlData.DisplayVersionString.ToString(); + } + + void GetControlFsAndTitleId(IFileSystem pfs, out IFileSystem? controlFs, out string? titleId) + { + if (SwitchDevice == null) + { + Logger.Error?.Print(LogClass.Application, "SwitchDevice is not initialized."); + + controlFs = null; + titleId = null; + return; + } + (_, _, Nca? controlNca) = GetGameData(SwitchDevice.VirtualFileSystem, pfs, 0); + + if (controlNca == null) + { + Logger.Warning?.Print(LogClass.Application, "Control NCA is null. Unable to load control FS."); + } + + // Return the ControlFS + controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); + titleId = controlNca?.Header.TitleId.ToString("x16"); + } + + (Nca? mainNca, Nca? patchNca, Nca? controlNca) GetGameData(VirtualFileSystem fileSystem, IFileSystem pfs, int programIndex) + { + Nca? mainNca = null; + Nca? patchNca = null; + Nca? controlNca = null; + + fileSystem.ImportTickets(pfs); + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef(); + + Logger.Info?.Print(LogClass.Application, $"Loading file from PFS: {fileEntry.FullPath}"); + + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage()); + + int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); + + if (ncaProgramIndex != programIndex) + { + continue; + } + + if (nca.Header.ContentType == NcaContentType.Program) + { + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()) + { + patchNca = nca; + } + else + { + mainNca = nca; + } + } + else if (nca.Header.ContentType == NcaContentType.Control) + { + controlNca = nca; + } + } + + return (mainNca, patchNca, controlNca); + } + + bool IsUpdateApplied(string? titleId, out IFileSystem? updatedControlFs) + { + updatedControlFs = null; + + string? updatePath = "(unknown)"; + + if (SwitchDevice?.VirtualFileSystem == null) + { + Logger.Error?.Print(LogClass.Application, "SwitchDevice was not initialized."); + return false; + } + + try + { + (Nca? patchNca, Nca? controlNca) = GetGameUpdateData(SwitchDevice.VirtualFileSystem, titleId, 0, out updatePath); + + if (patchNca != null && controlNca != null) + { + updatedControlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); + + return true; + } + } + catch (InvalidDataException) + { + Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}"); + } + catch (MissingKeyException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}"); + } + + return false; + } + + (Nca? patch, Nca? control) GetGameUpdateData(VirtualFileSystem fileSystem, string? titleId, int programIndex, out string? updatePath) + { + updatePath = null; + + if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase)) + { + // Clear the program index part. + titleIdBase &= ~0xFUL; + + // Load update information if exists. + string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); + + if (File.Exists(titleUpdateMetadataPath)) + { + updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected; + + if (File.Exists(updatePath)) + { + FileStream file = new(updatePath, FileMode.Open, FileAccess.Read); + PartitionFileSystem nsp = new(); + nsp.Initialize(file.AsStorage()).ThrowIfFailure(); + + return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex); + } + } + } + + return (null, null); + } + + (Nca? patchNca, Nca? controlNca) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex) + { + Nca? patchNca = null; + Nca? controlNca = null; + + fileSystem.ImportTickets(pfs); + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef(); + + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage()); + + int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); + + if (ncaProgramIndex != programIndex) + { + continue; + } + + if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId) + { + break; + } + + if (nca.Header.ContentType == NcaContentType.Program) + { + patchNca = nca; + } + else if (nca.Header.ContentType == NcaContentType.Control) + { + controlNca = nca; + } + } + + return (patchNca, controlNca); + } + + return gameInfo; + } + + public static string GetDlcTitleId(string path, string ncaPath) + { + if (File.Exists(path)) + { + using FileStream containerFile = File.OpenRead(path); + + PartitionFileSystem partitionFileSystem = new(); + partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure(); + + SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem); + + using UniqueRef ncaFile = new(); + + partitionFileSystem.OpenFile(ref ncaFile.Ref, ncaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), ncaPath); + if (nca != null) + { + return nca.Header.TitleId.ToString("X16"); + } + } + + return string.Empty; + } + + + private static Nca TryOpenNca(IStorage ncaStorage, string containerPath) + { + try + { + return new Nca(SwitchDevice.VirtualFileSystem.KeySet, ncaStorage); + } + catch (Exception ex) + { + } + + return null; + } + + public static List GetDlcContentList(string path, ulong titleId) + { + if (!File.Exists(path)) + return new List(); + + using FileStream containerFile = File.OpenRead(path); + + PartitionFileSystem partitionFileSystem = new(); + partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure(); + + SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem); + List paths = new List(); + + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef(); + + partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path); + if (nca == null) + { + continue; + } + + if (nca.Header.ContentType == NcaContentType.PublicData) + { + if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != titleId) + { + break; + } + + paths.Add(fileEntry.FullPath); + } + } + + return paths; + } + + public static void SetupUiHandler() + { + if (SwitchDevice is { } switchDevice) + { + switchDevice.HostUiHandler = new AndroidUiHandler(); + } + } + + public static void WaitUiHandler() + { + if (SwitchDevice?.HostUiHandler is AndroidUiHandler uiHandler) + { + uiHandler.Wait(); + } + } + + public static void StopUiHandlerWait() + { + if (SwitchDevice?.HostUiHandler is AndroidUiHandler uiHandler) + { + uiHandler.Set(); + } + } + + public static void SetUiHandlerResponse(bool isOkPressed, long input) + { + if (SwitchDevice?.HostUiHandler is AndroidUiHandler uiHandler) + { + uiHandler.SetResponse(isOkPressed, input); + } + } + } + + public class SwitchDevice : IDisposable + { + private readonly SystemVersion _firmwareVersion; + public VirtualFileSystem VirtualFileSystem { get; set; } + public ContentManager ContentManager { get; set; } + public AccountManager AccountManager { get; set; } + public LibHacHorizonManager LibHacHorizonManager { get; set; } + public UserChannelPersistence UserChannelPersistence { get; set; } + public InputManager? InputManager { get; set; } + public Switch? EmulationContext { get; set; } + public IHostUiHandler? HostUiHandler { get; set; } + + public void Dispose() + { + GC.SuppressFinalize(this); + + VirtualFileSystem.Dispose(); + InputManager?.Dispose(); + EmulationContext?.Dispose(); + } + + public SwitchDevice() + { + VirtualFileSystem = VirtualFileSystem.CreateInstance(); + LibHacHorizonManager = new LibHacHorizonManager(); + + LibHacHorizonManager.InitializeFsServer(VirtualFileSystem); + LibHacHorizonManager.InitializeArpServer(); + LibHacHorizonManager.InitializeBcatServer(); + LibHacHorizonManager.InitializeSystemClients(); + + ContentManager = new ContentManager(VirtualFileSystem); + AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient); + UserChannelPersistence = new UserChannelPersistence(); + + _firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + if (_firmwareVersion != null) + { + Logger.Notice.Print(LogClass.Application, $"System Firmware Version: {_firmwareVersion.VersionString}"); + } + else + { + Logger.Notice.Print(LogClass.Application, $"System Firmware not installed"); + } + } + + public bool InitializeContext(bool isHostMapped, + bool useNce, + SystemLanguage systemLanguage, + RegionCode regionCode, + bool enableVsync, + bool enableDockedMode, + bool enablePtc, + bool enableInternetAccess, + string? timeZone, + bool ignoreMissingServices) + { + if (LibRyujinx.Renderer == null) + { + return false; + } + + var renderer = LibRyujinx.Renderer; + BackendThreading threadingMode = LibRyujinx.GraphicsConfiguration.BackendThreading; + + bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + + if (threadedGAL) + { + renderer = new ThreadedRenderer(renderer); + } + + HLEConfiguration configuration = new HLEConfiguration(VirtualFileSystem, + LibHacHorizonManager, + ContentManager, + AccountManager, + UserChannelPersistence, + renderer, + LibRyujinx.AudioDriver, //Audio + MemoryConfiguration.MemoryConfiguration4GiB, + HostUiHandler, + systemLanguage, + regionCode, + enableVsync, + enableDockedMode, + enablePtc, + enableInternetAccess, + IntegrityCheckLevel.None, + 0, + 0, + timeZone, + isHostMapped ? MemoryManagerMode.HostMappedUnsafe : MemoryManagerMode.SoftwarePageTable, + ignoreMissingServices, + LibRyujinx.GraphicsConfiguration.AspectRatio, + 100, + useNce, + "", + MultiplayerMode.Disabled); + + EmulationContext = new Switch(configuration); + + return true; + } + + internal void ReloadFileSystem() + { + VirtualFileSystem.ReloadKeySet(); + ContentManager = new ContentManager(VirtualFileSystem); + AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient); + } + + internal void DisposeContext() + { + EmulationContext?.Dispose(); + EmulationContext?.DisposeGpu(); + EmulationContext = null; + LibRyujinx.Renderer = null; + } + } + + public class GameInfo + { + public double FileSize; + public string? TitleName; + public string? TitleId; + public string? Developer; + public string? Version; + public byte[]? Icon; + } + + public unsafe struct GameInfoNative + { + public ulong FileSize; + public fixed byte TitleName[512]; + public ulong TitleId; + public fixed byte Developer[256]; + public uint Version; + + public GameInfoNative(ulong fileSize, string titleName, ulong titleId, string developer, uint version) + { + FileSize = fileSize; + TitleId = titleId; + Version = version; + + fixed (byte* developerPtr = Developer) + fixed (byte* titleNamePtr = TitleName) + { + CopyStringToFixedArray(titleName, titleNamePtr, 512); + CopyStringToFixedArray(developer, developerPtr, 256); + } + } + + private void CopyStringToFixedArray(string source, byte* destination, int length) + { + var span = new Span(destination, length); + Encoding.UTF8.GetBytes(source, span); + } + } + + public class GameStats + { + public double Fifo; + public double GameFps; + public double GameTime; + } +} diff --git a/src/LibRyujinx/LibRyujinx.csproj b/src/LibRyujinx/LibRyujinx.csproj new file mode 100644 index 000000000..65bcc5dd0 --- /dev/null +++ b/src/LibRyujinx/LibRyujinx.csproj @@ -0,0 +1,80 @@ + + + net8.0 + linux-bionic-arm64;ios-arm64 + enable + lld + $(DefineConstants);FORCE_EXTERNAL_BASE_DIR + + + true + true + + Shared + true + true + true + armv8.2-a + + + true + Speed + + + + + + + + + $(XcodeSelect) + $([MSBuild]::EnsureTrailingSlash('$(XCodePath)')) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LibRyujinx/OpenTKBindingsContext.cs b/src/LibRyujinx/OpenTKBindingsContext.cs new file mode 100644 index 000000000..203caee8b --- /dev/null +++ b/src/LibRyujinx/OpenTKBindingsContext.cs @@ -0,0 +1,20 @@ +using OpenTK; +using System; + +namespace LibRyujinx.Shared +{ + public class OpenTKBindingsContext : IBindingsContext + { + private readonly Func _getProcAddress; + + public OpenTKBindingsContext(Func getProcAddress) + { + _getProcAddress = getProcAddress; + } + + public IntPtr GetProcAddress(string procName) + { + return _getProcAddress(procName); + } + } +} \ No newline at end of file diff --git a/src/LibRyujinx/VulkanLoader.cs b/src/LibRyujinx/VulkanLoader.cs new file mode 100644 index 000000000..5b832b3b4 --- /dev/null +++ b/src/LibRyujinx/VulkanLoader.cs @@ -0,0 +1,99 @@ +using Ryujinx.Common.Logging; +using Silk.NET.Core.Contexts; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace LibRyujinx +{ + public class VulkanLoader : IDisposable + { + private delegate IntPtr GetInstanceProcAddress(IntPtr instance, IntPtr name); + private delegate IntPtr GetDeviceProcAddress(IntPtr device, IntPtr name); + + private IntPtr _loadedLibrary = IntPtr.Zero; + private GetInstanceProcAddress _getInstanceProcAddr; + private GetDeviceProcAddress _getDeviceProcAddr; + + public void Dispose() + { + if (_loadedLibrary != IntPtr.Zero) + { + NativeLibrary.Free(_loadedLibrary); + _loadedLibrary = IntPtr.Zero; + } + } + + public VulkanLoader(IntPtr driver) + { + _loadedLibrary = driver; + + if (_loadedLibrary != IntPtr.Zero) + { + var instanceGetProc = NativeLibrary.GetExport(_loadedLibrary, "vkGetInstanceProcAddr"); + var deviceProc = NativeLibrary.GetExport(_loadedLibrary, "vkGetDeviceProcAddr"); + + _getInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(instanceGetProc); + _getDeviceProcAddr = Marshal.GetDelegateForFunctionPointer(deviceProc); + } + } + + public unsafe Vk GetApi() + { + + if (_loadedLibrary == IntPtr.Zero) + { + return Vk.GetApi(); + } + var ctx = new MultiNativeContext(new INativeContext[1]); + var ret = new Vk(ctx); + ctx.Contexts[0] = new LamdaNativeContext + ( + x => + { + var xPtr = Marshal.StringToHGlobalAnsi(x); + byte* xp = (byte*)xPtr; + LibRyujinx.debug_break(0); + try + { + nint ptr = default; + ptr = _getInstanceProcAddr(ret.CurrentInstance.GetValueOrDefault().Handle, xPtr); + + if (ptr == default) + { + ptr = _getInstanceProcAddr(IntPtr.Zero, xPtr); + + if (ptr == default) + { + var currentDevice = ret.CurrentDevice.GetValueOrDefault().Handle; + if (currentDevice != IntPtr.Zero) + { + ptr = _getDeviceProcAddr(currentDevice, xPtr); + } + + if (ptr == default) + { + Logger.Warning?.Print(LogClass.Gpu, $"Failed to get function pointer: {x}"); + } + + } + } + + return ptr; + } + finally + { + Marshal.FreeHGlobal(xPtr); + } + } + ); + return ret; + } + } +} diff --git a/src/LibRyujinx/rd.xml b/src/LibRyujinx/rd.xml new file mode 100644 index 000000000..220da644e --- /dev/null +++ b/src/LibRyujinx/rd.xml @@ -0,0 +1,610 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs new file mode 100644 index 000000000..bd7a9c25c --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Audio.Backends.OpenAL +{ + class OpenALAudioBuffer + { + public int BufferId; + public ulong DriverIdentifier; + public ulong SampleCount; + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs new file mode 100644 index 000000000..744a4bc56 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs @@ -0,0 +1,168 @@ +using OpenTK.Audio.OpenAL; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.OpenAL +{ + public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ALDevice _device; + private readonly ALContext _context; + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + private bool _stillRunning; + private readonly Thread _updaterThread; + + public OpenALHardwareDeviceDriver() + { + _device = ALC.OpenDevice(""); + _context = ALC.CreateContext(_device, new ALContextAttributes()); + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + _stillRunning = true; + _updaterThread = new Thread(Update) + { + Name = "HardwareDeviceDriver.OpenAL", + }; + + _updaterThread.Start(); + } + + public static bool IsSupported + { + get + { + try + { + return ALC.GetStringList(GetEnumerationStringList.DeviceSpecifier).Any(); + } + catch + { + return false; + } + } + } + + 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 ArgumentException($"{direction}"); + } + else if (!SupportsChannelCount(channelCount)) + { + throw new ArgumentException($"{channelCount}"); + } + + OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + + _sessions.TryAdd(session, 0); + + return session; + } + + internal bool Unregister(OpenALHardwareDeviceSession session) + { + return _sessions.TryRemove(session, out _); + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + private void Update() + { + ALC.MakeContextCurrent(_context); + + while (_stillRunning) + { + bool updateRequired = false; + + foreach (OpenALHardwareDeviceSession session in _sessions.Keys) + { + if (session.Update()) + { + updateRequired = true; + } + } + + if (updateRequired) + { + _updateRequiredEvent.Set(); + } + + // If it's not slept it will waste cycles. + Thread.Sleep(10); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _stillRunning = false; + + foreach (OpenALHardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + ALC.DestroyContext(_context); + ALC.CloseDevice(_device); + + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return true; + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs new file mode 100644 index 000000000..a52821616 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs @@ -0,0 +1,206 @@ +using OpenTK.Audio.OpenAL; +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Audio.Backends.OpenAL +{ + class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private readonly OpenALHardwareDeviceDriver _driver; + private readonly int _sourceId; + private readonly ALFormat _targetFormat; + private bool _isActive; + private readonly Queue _queuedBuffers; + private ulong _playedSampleCount; + + private readonly object _lock = new(); + + public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _queuedBuffers = new Queue(); + _sourceId = AL.GenSource(); + _targetFormat = GetALFormat(); + _isActive = false; + _playedSampleCount = 0; + SetVolume(requestedVolume); + } + + private ALFormat GetALFormat() + { + return RequestedSampleFormat switch + { + SampleFormat.PcmInt16 => RequestedChannelCount switch + { + 1 => ALFormat.Mono16, + 2 => ALFormat.Stereo16, + 6 => ALFormat.Multi51Chn16Ext, + _ => throw new NotImplementedException($"Unsupported channel config {RequestedChannelCount}"), + }, + _ => throw new NotImplementedException($"Unsupported sample format {RequestedSampleFormat}"), + }; + } + + public override void PrepareToClose() { } + + private void StartIfNotPlaying() + { + AL.GetSource(_sourceId, ALGetSourcei.SourceState, out int stateInt); + + ALSourceState State = (ALSourceState)stateInt; + + if (State != ALSourceState.Playing) + { + AL.SourcePlay(_sourceId); + } + } + + public override void QueueBuffer(AudioBuffer buffer) + { + lock (_lock) + { + OpenALAudioBuffer driverBuffer = new() + { + DriverIdentifier = buffer.DataPointer, + BufferId = AL.GenBuffer(), + SampleCount = GetSampleCount(buffer), + }; + + AL.BufferData(driverBuffer.BufferId, _targetFormat, buffer.Data, (int)RequestedSampleRate); + + _queuedBuffers.Enqueue(driverBuffer); + + AL.SourceQueueBuffer(_sourceId, driverBuffer.BufferId); + + if (_isActive) + { + StartIfNotPlaying(); + } + } + } + + public override void SetVolume(float volume) + { + lock (_lock) + { + AL.Source(_sourceId, ALSourcef.Gain, volume); + } + } + + public override float GetVolume() + { + AL.GetSource(_sourceId, ALSourcef.Gain, out float volume); + + return volume; + } + + public override void Start() + { + lock (_lock) + { + _isActive = true; + + StartIfNotPlaying(); + } + } + + public override void Stop() + { + lock (_lock) + { + SetVolume(0.0f); + + AL.SourceStop(_sourceId); + + _isActive = false; + } + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + lock (_lock) + { + if (!_queuedBuffers.TryPeek(out OpenALAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + } + + public override ulong GetPlayedSampleCount() + { + lock (_lock) + { + return _playedSampleCount; + } + } + + public bool Update() + { + lock (_lock) + { + if (_isActive) + { + AL.GetSource(_sourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); + + if (releasedCount > 0) + { + int[] bufferIds = new int[releasedCount]; + + AL.SourceUnqueueBuffers(_sourceId, releasedCount, bufferIds); + + int i = 0; + + while (_queuedBuffers.TryPeek(out OpenALAudioBuffer buffer) && i < bufferIds.Length) + { + if (buffer.BufferId == bufferIds[i]) + { + _playedSampleCount += buffer.SampleCount; + + _queuedBuffers.TryDequeue(out _); + + i++; + } + } + + Debug.Assert(i == bufferIds.Length, "Unknown buffer ids found!"); + + AL.DeleteBuffers(bufferIds); + } + + return releasedCount > 0; + } + + return false; + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _driver.Unregister(this)) + { + lock (_lock) + { + PrepareToClose(); + Stop(); + + AL.DeleteSource(_sourceId); + } + } + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj new file mode 100644 index 000000000..b5fd8f9e7 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + + + + + + + + + + + diff --git a/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj new file mode 100644 index 000000000..dd18e70a1 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + + + + + + + + diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs new file mode 100644 index 000000000..a390c5467 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Audio.Backends.SDL2 +{ + class SDL2AudioBuffer + { + public readonly ulong DriverIdentifier; + public readonly ulong SampleCount; + public ulong SamplePlayed; + + public SDL2AudioBuffer(ulong driverIdentifier, ulong sampleCount) + { + DriverIdentifier = driverIdentifier; + SampleCount = sampleCount; + SamplePlayed = 0; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs new file mode 100644 index 000000000..b83e63dbc --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs @@ -0,0 +1,204 @@ +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.Runtime.InteropServices; +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 _sessions; + + private readonly bool _supportSurroundConfiguration; + + // TODO: Add this to SDL2-CS + // NOTE: We use a DllImport here because of marshaling issue for spec. +#pragma warning disable SYSLIB1054 + [DllImport("SDL2")] + private static extern int SDL_GetDefaultAudioInfo(IntPtr name, out SDL_AudioSpec spec, int isCapture); +#pragma warning restore SYSLIB1054 + + public SDL2HardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + SDL2Driver.Instance.Initialize(); + + int res = SDL_GetDefaultAudioInfo(IntPtr.Zero, out var spec, 0); + + if (res != 0) + { + Logger.Error?.Print(LogClass.Application, + $"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\""); + + _supportSurroundConfiguration = true; + } + else + { + _supportSurroundConfiguration = spec.channels >= 6; + } + } + + 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(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() + { + GC.SuppressFinalize(this); + 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) + { + if (channelCount == 6) + { + return _supportSurroundConfiguration; + } + + return true; + } + + public bool SupportsDirection(Direction direction) + { + // TODO: add direction input when supported. + return direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs new file mode 100644 index 000000000..7a683f4ed --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs @@ -0,0 +1,230 @@ +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 readonly SDL2HardwareDeviceDriver _driver; + private readonly ConcurrentQueue _queuedBuffers; + private readonly DynamicRingBuffer _ringBuffer; + private ulong _playedSampleCount; + private readonly ManualResetEvent _updateRequiredEvent; + private uint _outputStream; + private bool _hasSetupError; + private readonly SDL_AudioCallback _callbackDelegate; + private readonly int _bytesPerFrame; + private uint _sampleCount; + private bool _started; + private float _volume; + private readonly 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(); + _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 && !_hasSetupError) || + (bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount); + + if (needAudioSetup) + { + _sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount); + + uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate); + + _hasSetupError = newOutputStream == 0; + + if (!_hasSetupError) + { + 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 streamSpan = new((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.Clear(); + + 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.Clear(); + + // 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); + + if (_outputStream != 0) + { + SDL2AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer)); + + _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); + + _queuedBuffers.Enqueue(driverBuffer); + } + else + { + Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); + + _updateRequiredEvent.Set(); + } + } + + 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); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs new file mode 100644 index 000000000..7fdb1fc04 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs @@ -0,0 +1,178 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public static partial class SoundIo + { + private const string LibraryName = "libsoundio"; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void OnDeviceChangeNativeDelegate(IntPtr ctx); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void OnBackendDisconnectedDelegate(IntPtr ctx, SoundIoError err); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void OnEventsSignalDelegate(IntPtr ctx); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void EmitRtPrioWarningDelegate(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void JackCallbackDelegate(IntPtr msg); + + [StructLayout(LayoutKind.Sequential)] + public struct SoundIoStruct + { + public IntPtr UserData; + public IntPtr OnDeviceChange; + public IntPtr OnBackendDisconnected; + public IntPtr OnEventsSignal; + public SoundIoBackend CurrentBackend; + public IntPtr ApplicationName; + public IntPtr EmitRtPrioWarning; + public IntPtr JackInfoCallback; + public IntPtr JackErrorCallback; + } + + public struct SoundIoChannelLayout + { + public IntPtr Name; + public int ChannelCount; + public Array24 Channels; + + public static IntPtr GetDefault(int channelCount) + { + return soundio_channel_layout_get_default(channelCount); + } + + public static unsafe SoundIoChannelLayout GetDefaultValue(int channelCount) + { + return Unsafe.AsRef((SoundIoChannelLayout*)GetDefault(channelCount)); + } + } + + public struct SoundIoSampleRateRange + { + public int Min; + public int Max; + } + + public struct SoundIoDevice + { + public IntPtr SoundIo; + public IntPtr Id; + public IntPtr Name; + public SoundIoDeviceAim Aim; + public IntPtr Layouts; + public int LayoutCount; + public SoundIoChannelLayout CurrentLayout; + public IntPtr Formats; + public int FormatCount; + public SoundIoFormat CurrentFormat; + public IntPtr SampleRates; + public int SampleRateCount; + public int SampleRateCurrent; + public double SoftwareLatencyMin; + public double SoftwareLatencyMax; + public double SoftwareLatencyCurrent; + public bool IsRaw; + public int RefCount; + public SoundIoError ProbeError; + } + + public struct SoundIoOutStream + { + public IntPtr Device; + public SoundIoFormat Format; + public int SampleRate; + public SoundIoChannelLayout Layout; + public double SoftwareLatency; + public float Volume; + public IntPtr UserData; + public IntPtr WriteCallback; + public IntPtr UnderflowCallback; + public IntPtr ErrorCallback; + public IntPtr Name; + public bool NonTerminalHint; + public int BytesPerFrame; + public int BytesPerSample; + public SoundIoError LayoutError; + } + + public struct SoundIoChannelArea + { + public IntPtr Pointer; + public int Step; + } + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_create(); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_connect(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial void soundio_disconnect(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial void soundio_flush_events(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial int soundio_output_device_count(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial int soundio_default_output_device_index(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_get_output_device(IntPtr ctx, int index); + + [LibraryImport(LibraryName)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool soundio_device_supports_format(IntPtr devCtx, SoundIoFormat format); + + [LibraryImport(LibraryName)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool soundio_device_supports_layout(IntPtr devCtx, IntPtr layout); + + [LibraryImport(LibraryName)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool soundio_device_supports_sample_rate(IntPtr devCtx, int sampleRate); + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_outstream_create(IntPtr devCtx); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_open(IntPtr outStreamCtx); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_start(IntPtr outStreamCtx); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_begin_write(IntPtr outStreamCtx, IntPtr areas, IntPtr frameCount); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_end_write(IntPtr outStreamCtx); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_pause(IntPtr devCtx, [MarshalAs(UnmanagedType.Bool)] bool pause); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_set_volume(IntPtr devCtx, double volume); + + [LibraryImport(LibraryName)] + internal static partial void soundio_outstream_destroy(IntPtr streamCtx); + + [LibraryImport(LibraryName)] + internal static partial void soundio_destroy(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_channel_layout_get_default(int channelCount); + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_strerror(SoundIoError err); + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs new file mode 100644 index 000000000..d91eca488 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoBackend + { + None = 0, + Jack = 1, + PulseAudio = 2, + Alsa = 3, + CoreAudio = 4, + Wasapi = 5, + Dummy = 6, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs new file mode 100644 index 000000000..c1a14e1ea --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs @@ -0,0 +1,75 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoChannelId + { + Invalid = 0, + FrontLeft = 1, + FrontRight = 2, + FrontCenter = 3, + Lfe = 4, + BackLeft = 5, + BackRight = 6, + FrontLeftCenter = 7, + FrontRightCenter = 8, + BackCenter = 9, + SideLeft = 10, + SideRight = 11, + TopCenter = 12, + TopFrontLeft = 13, + TopFrontCenter = 14, + TopFrontRight = 15, + TopBackLeft = 16, + TopBackCenter = 17, + TopBackRight = 18, + BackLeftCenter = 19, + BackRightCenter = 20, + FrontLeftWide = 21, + FrontRightWide = 22, + FrontLeftHigh = 23, + FrontCenterHigh = 24, + FrontRightHigh = 25, + TopFrontLeftCenter = 26, + TopFrontRightCenter = 27, + TopSideLeft = 28, + TopSideRight = 29, + LeftLfe = 30, + RightLfe = 31, + Lfe2 = 32, + BottomCenter = 33, + BottomLeftCenter = 34, + BottomRightCenter = 35, + MsMid = 36, + MsSide = 37, + AmbisonicW = 38, + AmbisonicX = 39, + AmbisonicY = 40, + AmbisonicZ = 41, + XyX = 42, + XyY = 43, + HeadphonesLeft = 44, + HeadphonesRight = 45, + ClickTrack = 46, + ForeignLanguage = 47, + HearingImpaired = 48, + Narration = 49, + Haptic = 50, + DialogCentricMix = 51, + Aux = 52, + Aux0 = 53, + Aux1 = 54, + Aux2 = 55, + Aux3 = 56, + Aux4 = 57, + Aux5 = 58, + Aux6 = 59, + Aux7 = 60, + Aux8 = 61, + Aux9 = 62, + Aux10 = 63, + Aux11 = 64, + Aux12 = 65, + Aux13 = 66, + Aux14 = 67, + Aux15 = 68, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs new file mode 100644 index 000000000..f2e91fcd7 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public class SoundIoContext : IDisposable + { + private IntPtr _context; + private Action _onBackendDisconnect; + private OnBackendDisconnectedDelegate _onBackendDisconnectNative; + + public IntPtr Context => _context; + + internal SoundIoContext(IntPtr context) + { + _context = context; + _onBackendDisconnect = null; + _onBackendDisconnectNative = null; + } + + public SoundIoError Connect() => soundio_connect(_context); + public void Disconnect() => soundio_disconnect(_context); + + public void FlushEvents() => soundio_flush_events(_context); + + public int OutputDeviceCount => soundio_output_device_count(_context); + + public int DefaultOutputDeviceIndex => soundio_default_output_device_index(_context); + + public Action OnBackendDisconnect + { + get { return _onBackendDisconnect; } + set + { + _onBackendDisconnect = value; + + if (_onBackendDisconnect == null) + { + _onBackendDisconnectNative = null; + } + else + { + _onBackendDisconnectNative = (ctx, err) => _onBackendDisconnect(err); + } + + GetContext().OnBackendDisconnected = Marshal.GetFunctionPointerForDelegate(_onBackendDisconnectNative); + } + } + + private ref SoundIoStruct GetContext() + { + unsafe + { + return ref Unsafe.AsRef((SoundIoStruct*)_context); + } + } + + public SoundIoDeviceContext GetOutputDevice(int index) + { + IntPtr deviceContext = soundio_get_output_device(_context, index); + + if (deviceContext == IntPtr.Zero) + { + return null; + } + + return new SoundIoDeviceContext(deviceContext); + } + + public static SoundIoContext Create() + { + IntPtr context = soundio_create(); + + if (context == IntPtr.Zero) + { + return null; + } + + return new SoundIoContext(context); + } + + protected virtual void Dispose(bool disposing) + { + IntPtr currentContext = Interlocked.Exchange(ref _context, IntPtr.Zero); + + if (currentContext != IntPtr.Zero) + { + soundio_destroy(currentContext); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~SoundIoContext() + { + Dispose(false); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs new file mode 100644 index 000000000..58bdce394 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoDeviceAim + { + SoundIoDeviceAimInput = 0, + SoundIoDeviceAimOutput = 1, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs new file mode 100644 index 000000000..7923e9b17 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public class SoundIoDeviceContext + { + private readonly IntPtr _context; + + public IntPtr Context => _context; + + internal SoundIoDeviceContext(IntPtr context) + { + _context = context; + } + + private ref SoundIoDevice GetDeviceContext() + { + unsafe + { + return ref Unsafe.AsRef((SoundIoDevice*)_context); + } + } + + public bool IsRaw => GetDeviceContext().IsRaw; + + public string Id => Marshal.PtrToStringAnsi(GetDeviceContext().Id); + + public bool SupportsSampleRate(int sampleRate) => soundio_device_supports_sample_rate(_context, sampleRate); + + public bool SupportsFormat(SoundIoFormat format) => soundio_device_supports_format(_context, format); + + public bool SupportsChannelCount(int channelCount) => soundio_device_supports_layout(_context, SoundIoChannelLayout.GetDefault(channelCount)); + + public SoundIoOutStreamContext CreateOutStream() + { + IntPtr context = soundio_outstream_create(_context); + + if (context == IntPtr.Zero) + { + return null; + } + + return new SoundIoOutStreamContext(context); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs new file mode 100644 index 000000000..64329450c --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoError + { + None = 0, + NoMem = 1, + InitAudioBackend = 2, + SystemResources = 3, + OpeningDevice = 4, + NoSuchDevice = 5, + Invalid = 6, + BackendUnavailable = 7, + Streaming = 8, + IncompatibleDevice = 9, + NoSuchClient = 10, + IncompatibleBackend = 11, + BackendDisconnected = 12, + Interrupted = 13, + Underflow = 14, + EncodingString = 15, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs new file mode 100644 index 000000000..1a33472da --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs @@ -0,0 +1,11 @@ +using System; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + internal class SoundIoException : Exception + { + internal SoundIoException(SoundIoError error) : base(Marshal.PtrToStringAnsi(soundio_strerror(error))) { } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs new file mode 100644 index 000000000..7e8c22a9f --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoFormat + { + Invalid = 0, + S8 = 1, + U8 = 2, + S16LE = 3, + S16BE = 4, + U16LE = 5, + U16BE = 6, + S24LE = 7, + S24BE = 8, + U24LE = 9, + U24BE = 10, + S32LE = 11, + S32BE = 12, + U32LE = 13, + U32BE = 14, + Float32LE = 15, + Float32BE = 16, + Float64LE = 17, + Float64BE = 18, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs new file mode 100644 index 000000000..4148ea0dd --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs @@ -0,0 +1,164 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public class SoundIoOutStreamContext : IDisposable + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void WriteCallbackDelegate(IntPtr ctx, int frameCountMin, int frameCountMax); + + private IntPtr _context; + private IntPtr _nameStored; + private Action _writeCallback; + private WriteCallbackDelegate _writeCallbackNative; + + public IntPtr Context => _context; + + internal SoundIoOutStreamContext(IntPtr context) + { + _context = context; + _nameStored = IntPtr.Zero; + _writeCallback = null; + _writeCallbackNative = null; + } + + private ref SoundIoOutStream GetOutContext() + { + unsafe + { + return ref Unsafe.AsRef((SoundIoOutStream*)_context); + } + } + + public string Name + { + get => Marshal.PtrToStringAnsi(GetOutContext().Name); + set + { + var context = GetOutContext(); + + if (_nameStored != IntPtr.Zero && context.Name == _nameStored) + { + Marshal.FreeHGlobal(_nameStored); + } + + _nameStored = Marshal.StringToHGlobalAnsi(value); + GetOutContext().Name = _nameStored; + } + } + + public SoundIoChannelLayout Layout + { + get => GetOutContext().Layout; + set => GetOutContext().Layout = value; + } + + public SoundIoFormat Format + { + get => GetOutContext().Format; + set => GetOutContext().Format = value; + } + + public int SampleRate + { + get => GetOutContext().SampleRate; + set => GetOutContext().SampleRate = value; + } + + public float Volume + { + get => GetOutContext().Volume; + set => GetOutContext().Volume = value; + } + + public int BytesPerFrame + { + get => GetOutContext().BytesPerFrame; + set => GetOutContext().BytesPerFrame = value; + } + + public int BytesPerSample + { + get => GetOutContext().BytesPerSample; + set => GetOutContext().BytesPerSample = value; + } + + public Action WriteCallback + { + get { return _writeCallback; } + set + { + _writeCallback = value; + + if (_writeCallback == null) + { + _writeCallbackNative = null; + } + else + { + _writeCallbackNative = (ctx, frameCountMin, frameCountMax) => _writeCallback(frameCountMin, frameCountMax); + } + + GetOutContext().WriteCallback = Marshal.GetFunctionPointerForDelegate(_writeCallbackNative); + } + } + + private static void CheckError(SoundIoError error) + { + if (error != SoundIoError.None) + { + throw new SoundIoException(error); + } + } + + public void Open() => CheckError(soundio_outstream_open(_context)); + + public void Start() => CheckError(soundio_outstream_start(_context)); + + public void Pause(bool pause) => CheckError(soundio_outstream_pause(_context, pause)); + + public void SetVolume(double volume) => CheckError(soundio_outstream_set_volume(_context, volume)); + + public Span BeginWrite(ref int frameCount) + { + IntPtr arenas = default; + int nativeFrameCount = frameCount; + + unsafe + { + var frameCountPtr = &nativeFrameCount; + var arenasPtr = &arenas; + CheckError(soundio_outstream_begin_write(_context, (IntPtr)arenasPtr, (IntPtr)frameCountPtr)); + + frameCount = *frameCountPtr; + + return new Span((void*)arenas, Layout.ChannelCount); + } + } + + public void EndWrite() => CheckError(soundio_outstream_end_write(_context)); + + protected virtual void Dispose(bool disposing) + { + if (_context != IntPtr.Zero) + { + soundio_outstream_destroy(_context); + _context = IntPtr.Zero; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~SoundIoOutStreamContext() + { + Dispose(false); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll new file mode 100644 index 000000000..48804312e Binary files /dev/null and b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll differ diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib new file mode 100644 index 000000000..10171f4fb Binary files /dev/null and b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib differ diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so new file mode 100644 index 000000000..87c8b5065 Binary files /dev/null and b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so differ diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj new file mode 100644 index 000000000..1d92d9d2e --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + true + win-x64;osx-x64;linux-x64 + + + + + + + + + PreserveNewest + libsoundio.dll + + + PreserveNewest + libsoundio.dylib + + + PreserveNewest + libsoundio.so + + + + diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs new file mode 100644 index 000000000..e1a62de75 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Audio.Backends.SoundIo +{ + class SoundIoAudioBuffer + { + public readonly ulong DriverIdentifier; + public readonly ulong SampleCount; + public ulong SamplePlayed; + + public SoundIoAudioBuffer(ulong driverIdentifier, ulong sampleCount) + { + DriverIdentifier = driverIdentifier; + SampleCount = sampleCount; + SamplePlayed = 0; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs new file mode 100644 index 000000000..ff0392882 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs @@ -0,0 +1,249 @@ +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.Backends.SoundIo.Native.SoundIo; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +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 _sessions; + private int _disposeState; + + public SoundIoHardwareDeviceDriver() + { + _audioContext = SoundIoContext.Create(); + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + _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(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() + { + GC.SuppressFinalize(this); + + 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; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs new file mode 100644 index 000000000..123cfd27a --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs @@ -0,0 +1,438 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Backends.SoundIo.Native; +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Threading; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo +{ + class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private readonly SoundIoHardwareDeviceDriver _driver; + private readonly ConcurrentQueue _queuedBuffers; + private SoundIoOutStreamContext _outputStream; + private readonly DynamicRingBuffer _ringBuffer; + private ulong _playedSampleCount; + private readonly ManualResetEvent _updateRequiredEvent; + private int _disposeState; + + public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); + _queuedBuffers = new ConcurrentQueue(); + _ringBuffer = new DynamicRingBuffer(); + + SetupOutputStream(requestedVolume); + } + + private void SetupOutputStream(float requestedVolume) + { + _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount); + _outputStream.WriteCallback += Update; + _outputStream.Volume = requestedVolume; + // TODO: Setup other callbacks (errors, ect). + + _outputStream.Open(); + } + + public override ulong GetPlayedSampleCount() + { + return Interlocked.Read(ref _playedSampleCount); + } + + public override float GetVolume() + { + return _outputStream.Volume; + } + + public override void PrepareToClose() { } + + public override void QueueBuffer(AudioBuffer buffer) + { + SoundIoAudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer)); + + _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); + + _queuedBuffers.Enqueue(driverBuffer); + } + + public override void SetVolume(float volume) + { + _outputStream.SetVolume(volume); + } + + public override void Start() + { + _outputStream.Start(); + _outputStream.Pause(false); + + _driver.FlushContextEvents(); + } + + public override void Stop() + { + _outputStream.Pause(true); + + _driver.FlushContextEvents(); + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + + private unsafe void Update(int minFrameCount, int maxFrameCount) + { + int bytesPerFrame = _outputStream.BytesPerFrame; + uint bytesPerSample = (uint)_outputStream.BytesPerSample; + + int bufferedFrames = _ringBuffer.Length / bytesPerFrame; + + int frameCount = Math.Min(bufferedFrames, maxFrameCount); + + if (frameCount == 0) + { + return; + } + + Span areas = _outputStream.BeginWrite(ref frameCount); + + int channelCount = areas.Length; + + byte[] samples = new byte[frameCount * bytesPerFrame]; + + _ringBuffer.Read(samples, 0, samples.Length); + + // This is a huge ugly block of code, but we save + // a significant amount of time over the generic + // loop that handles other channel counts. + // TODO: Is this still right in 2022? + + // Mono + if (channelCount == 1) + { + ref SoundIoChannelArea area = ref areas[0]; + + fixed (byte* srcptr = samples) + { + if (bytesPerSample == 1) + { + for (int frame = 0; frame < frameCount; frame++) + { + ((byte*)area.Pointer)[0] = srcptr[frame * bytesPerFrame]; + + area.Pointer += area.Step; + } + } + else if (bytesPerSample == 2) + { + for (int frame = 0; frame < frameCount; frame++) + { + ((short*)area.Pointer)[0] = ((short*)srcptr)[frame * bytesPerFrame >> 1]; + + area.Pointer += area.Step; + } + } + else if (bytesPerSample == 4) + { + for (int frame = 0; frame < frameCount; frame++) + { + ((int*)area.Pointer)[0] = ((int*)srcptr)[frame * bytesPerFrame >> 2]; + + area.Pointer += area.Step; + } + } + else + { + for (int frame = 0; frame < frameCount; frame++) + { + Unsafe.CopyBlockUnaligned((byte*)area.Pointer, srcptr + (frame * bytesPerFrame), bytesPerSample); + + area.Pointer += area.Step; + } + } + } + } + // Stereo + else if (channelCount == 2) + { + ref SoundIoChannelArea area1 = ref areas[0]; + ref SoundIoChannelArea area2 = ref areas[1]; + + fixed (byte* srcptr = samples) + { + if (bytesPerSample == 1) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; + + // Channel 2 + ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + else if (bytesPerSample == 2) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; + + // Channel 2 + ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + else if (bytesPerSample == 4) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; + + // Channel 2 + ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + else + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); + + // Channel 2 + Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample); + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + } + } + // Surround + else if (channelCount == 6) + { + ref SoundIoChannelArea area1 = ref areas[0]; + ref SoundIoChannelArea area2 = ref areas[1]; + ref SoundIoChannelArea area3 = ref areas[2]; + ref SoundIoChannelArea area4 = ref areas[3]; + ref SoundIoChannelArea area5 = ref areas[4]; + ref SoundIoChannelArea area6 = ref areas[5]; + + fixed (byte* srcptr = samples) + { + if (bytesPerSample == 1) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; + + // Channel 2 + ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1]; + + // Channel 3 + ((byte*)area3.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 2]; + + // Channel 4 + ((byte*)area4.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 3]; + + // Channel 5 + ((byte*)area5.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 4]; + + // Channel 6 + ((byte*)area6.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 5]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + else if (bytesPerSample == 2) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; + + // Channel 2 + ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1]; + + // Channel 3 + ((short*)area3.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 2]; + + // Channel 4 + ((short*)area4.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 3]; + + // Channel 5 + ((short*)area5.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 4]; + + // Channel 6 + ((short*)area6.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 5]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + else if (bytesPerSample == 4) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; + + // Channel 2 + ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1]; + + // Channel 3 + ((int*)area3.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 2]; + + // Channel 4 + ((int*)area4.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 3]; + + // Channel 5 + ((int*)area5.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 4]; + + // Channel 6 + ((int*)area6.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 5]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + else + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); + + // Channel 2 + Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample); + + // Channel 3 + Unsafe.CopyBlockUnaligned((byte*)area3.Pointer, srcptr + (frame * bytesPerFrame) + (2 * bytesPerSample), bytesPerSample); + + // Channel 4 + Unsafe.CopyBlockUnaligned((byte*)area4.Pointer, srcptr + (frame * bytesPerFrame) + (3 * bytesPerSample), bytesPerSample); + + // Channel 5 + Unsafe.CopyBlockUnaligned((byte*)area5.Pointer, srcptr + (frame * bytesPerFrame) + (4 * bytesPerSample), bytesPerSample); + + // Channel 6 + Unsafe.CopyBlockUnaligned((byte*)area6.Pointer, srcptr + (frame * bytesPerFrame) + (5 * bytesPerSample), bytesPerSample); + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + } + } + // Every other channel count + else + { + fixed (byte* srcptr = samples) + { + for (int frame = 0; frame < frameCount; frame++) + { + for (int channel = 0; channel < areas.Length; channel++) + { + // Copy channel by channel, frame by frame. This is slow! + Unsafe.CopyBlockUnaligned((byte*)areas[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample); + + areas[channel].Pointer += areas[channel].Step; + } + } + } + } + + _outputStream.EndWrite(); + + ulong sampleCount = (ulong)(samples.Length / bytesPerSample / channelCount); + + ulong availaibleSampleCount = sampleCount; + + bool needUpdate = false; + + while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) + { + ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed); + ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); + + Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount); + availaibleSampleCount -= playedAudioBufferSampleCount; + + if (Interlocked.Read(ref driverBuffer.SamplePlayed) == driverBuffer.SampleCount) + { + _queuedBuffers.TryDequeue(out _); + + needUpdate = true; + } + + Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount); + } + + // Notify the output if needed. + if (needUpdate) + { + _updateRequiredEvent.Set(); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _driver.Unregister(this)) + { + PrepareToClose(); + Stop(); + + _outputStream.Dispose(); + } + } + + public override void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + } +} diff --git a/src/Ryujinx.Audio/AudioManager.cs b/src/Ryujinx.Audio/AudioManager.cs new file mode 100644 index 000000000..370d3d098 --- /dev/null +++ b/src/Ryujinx.Audio/AudioManager.cs @@ -0,0 +1,133 @@ +using System; +using System.Threading; + +namespace Ryujinx.Audio +{ + /// + /// Manage audio input and output system. + /// + public class AudioManager : IDisposable + { + /// + /// Lock used to control the waiters registration. + /// + private readonly object _lock = new(); + + /// + /// Events signaled when the driver played audio buffers. + /// + private readonly ManualResetEvent[] _updateRequiredEvents; + + /// + /// Action to execute when the driver played audio buffers. + /// + private readonly Action[] _actions; + + /// + /// The worker thread in charge of handling sessions update. + /// + private readonly Thread _workerThread; + + private bool _isRunning; + + /// + /// Create a new . + /// + public AudioManager() + { + _updateRequiredEvents = new ManualResetEvent[2]; + _actions = new Action[2]; + _isRunning = false; + + // Termination event. + _updateRequiredEvents[1] = new ManualResetEvent(false); + + _workerThread = new Thread(Update) + { + Name = "AudioManager.Worker", + }; + } + + /// + /// Start the . + /// + public void Start() + { + if (_workerThread.IsAlive) + { + throw new InvalidOperationException(); + } + + _isRunning = true; + _workerThread.Start(); + } + + /// + /// Initialize update handlers. + /// + /// The driver event that will get signaled by the device driver when an audio buffer finished playing/being captured + /// The callback to call when an audio buffer finished playing + /// The callback to call when an audio buffer was captured + public void Initialize(ManualResetEvent updatedRequiredEvent, Action outputCallback, Action inputCallback) + { + lock (_lock) + { + _updateRequiredEvents[0] = updatedRequiredEvent; + _actions[0] = outputCallback; + _actions[1] = inputCallback; + } + } + + /// + /// Entrypoint of the in charge of updating the . + /// + private void Update() + { + while (_isRunning) + { + int index = WaitHandle.WaitAny(_updateRequiredEvents); + + // Last index is here to indicate thread termination. + if (index + 1 == _updateRequiredEvents.Length) + { + break; + } + + lock (_lock) + { + foreach (Action action in _actions) + { + action?.Invoke(); + } + + _updateRequiredEvents[0].Reset(); + } + } + } + + /// + /// Stop updating the without stopping the worker thread. + /// + public void StopUpdates() + { + _isRunning = false; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _updateRequiredEvents[1].Set(); + _workerThread.Join(); + + _updateRequiredEvents[1].Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Common/BackendHelper.cs b/src/Ryujinx.Audio/Backends/Common/BackendHelper.cs new file mode 100644 index 000000000..124d8364f --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Common/BackendHelper.cs @@ -0,0 +1,26 @@ +using Ryujinx.Audio.Common; +using System; + +namespace Ryujinx.Audio.Backends.Common +{ + public static class BackendHelper + { + public static int GetSampleSize(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => sizeof(byte), + SampleFormat.PcmInt16 => sizeof(ushort), + SampleFormat.PcmInt24 => 3, + SampleFormat.PcmInt32 => sizeof(int), + SampleFormat.PcmFloat => sizeof(float), + _ => throw new ArgumentException($"{format}"), + }; + } + + public static int GetSampleCount(SampleFormat format, int channelCount, int bufferSize) + { + return bufferSize / GetSampleSize(format) / channelCount; + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs new file mode 100644 index 000000000..05dd2162a --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs @@ -0,0 +1,166 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.Audio.Backends.Common +{ + /// + /// A ring buffer that grow if data written to it is too big to fit. + /// + public class DynamicRingBuffer + { + private const int RingBufferAlignment = 2048; + + private readonly object _lock = new(); + + private byte[] _buffer; + private int _size; + private int _headOffset; + private int _tailOffset; + + public int Length => _size; + + public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) + { + _buffer = new byte[initialCapacity]; + } + + public void Clear() + { + _size = 0; + _headOffset = 0; + _tailOffset = 0; + } + + public void Clear(int size) + { + lock (_lock) + { + if (size > _size) + { + size = _size; + } + + if (size == 0) + { + return; + } + + _headOffset = (_headOffset + size) % _buffer.Length; + _size -= size; + + if (_size == 0) + { + _headOffset = 0; + _tailOffset = 0; + } + } + } + + private void SetCapacityLocked(int capacity) + { + byte[] buffer = new byte[capacity]; + + if (_size > 0) + { + if (_headOffset < _tailOffset) + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size); + } + else + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset); + Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset); + } + } + + _buffer = buffer; + _headOffset = 0; + _tailOffset = _size; + } + + + public void Write(T[] buffer, int index, int count) + { + if (count == 0) + { + return; + } + + lock (_lock) + { + if ((_size + count) > _buffer.Length) + { + SetCapacityLocked(BitUtils.AlignUp(_size + count, RingBufferAlignment)); + } + + if (_headOffset < _tailOffset) + { + int tailLength = _buffer.Length - _tailOffset; + + if (tailLength >= count) + { + Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + } + else + { + Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength); + Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength); + } + } + else + { + Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + } + + _size += count; + _tailOffset = (_tailOffset + count) % _buffer.Length; + } + } + + public int Read(T[] buffer, int index, int count) + { + lock (_lock) + { + if (count > _size) + { + count = _size; + } + + if (count == 0) + { + return 0; + } + + if (_headOffset < _tailOffset) + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + } + else + { + int tailLength = _buffer.Length - _headOffset; + + if (tailLength >= count) + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + } + else + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength); + Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength); + } + } + + _size -= count; + _headOffset = (_headOffset + count) % _buffer.Length; + + if (_size == 0) + { + _headOffset = 0; + _tailOffset = 0; + } + + return count; + } + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs b/src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs new file mode 100644 index 000000000..5599c0827 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Backends.Common +{ + public abstract class HardwareDeviceSessionOutputBase : IHardwareDeviceSession + { + public IVirtualMemoryManager MemoryManager { get; } + public SampleFormat RequestedSampleFormat { get; } + public uint RequestedSampleRate { get; } + public uint RequestedChannelCount { get; } + + public HardwareDeviceSessionOutputBase(IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) + { + MemoryManager = memoryManager; + RequestedSampleFormat = requestedSampleFormat; + RequestedSampleRate = requestedSampleRate; + RequestedChannelCount = requestedChannelCount; + } + + private byte[] GetBufferSamples(AudioBuffer buffer) + { + if (buffer.DataPointer == 0) + { + return null; + } + + byte[] data = new byte[buffer.DataSize]; + + MemoryManager.Read(buffer.DataPointer, data); + + return data; + } + + protected ulong GetSampleCount(AudioBuffer buffer) + { + return GetSampleCount((int)buffer.DataSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected ulong GetSampleCount(int dataSize) + { + return (ulong)BackendHelper.GetSampleCount(RequestedSampleFormat, (int)RequestedChannelCount, dataSize); + } + + public abstract void Dispose(); + public abstract void PrepareToClose(); + public abstract void QueueBuffer(AudioBuffer buffer); + public abstract void SetVolume(float volume); + public abstract float GetVolume(); + public abstract void Start(); + public abstract void Stop(); + public abstract ulong GetPlayedSampleCount(); + public abstract bool WasBufferFullyConsumed(AudioBuffer buffer); + public virtual bool RegisterBuffer(AudioBuffer buffer) + { + return RegisterBuffer(buffer, GetBufferSamples(buffer)); + } + + public virtual bool RegisterBuffer(AudioBuffer buffer, byte[] samples) + { + if (samples == null) + { + return false; + } + + buffer.Data ??= samples; + + return true; + } + + public virtual void UnregisterBuffer(AudioBuffer buffer) { } + } +} diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs new file mode 100644 index 000000000..3f3806c3e --- /dev/null +++ b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs @@ -0,0 +1,186 @@ +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 readonly IHardwareDeviceDriver _realDriver; + + public static bool IsSupported => true; + + public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice) + { + _realDriver = realDevice; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _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; + } + + if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt24)) + { + return SampleFormat.PcmInt24; + } + + // If nothing is truly supported, attempt PCM8 at the cost of losing 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); + } + + 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); + } + + // 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; + } + } +} diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs new file mode 100644 index 000000000..a9acabec9 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs @@ -0,0 +1,162 @@ +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 readonly HardwareDeviceSessionOutputBase _realSession; + private readonly SampleFormat _userSampleFormat; + private readonly 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 samples = MemoryMarshal.Cast(buffer.Data); + byte[] convertedSamples = new byte[BackendHelper.GetSampleSize(realSampleFormat) * userSampleCount]; + + switch (realSampleFormat) + { + case SampleFormat.PcmInt8: + PcmHelper.ConvertSampleToPcm8(MemoryMarshal.Cast(convertedSamples), samples); + break; + case SampleFormat.PcmInt24: + PcmHelper.ConvertSampleToPcm24(convertedSamples, samples); + break; + case SampleFormat.PcmInt32: + PcmHelper.ConvertSampleToPcm32(MemoryMarshal.Cast(convertedSamples), samples); + break; + case SampleFormat.PcmFloat: + PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast(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 samplesPCM16 = MemoryMarshal.Cast(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(samplesPCM16).ToArray(); + } + + AudioBuffer fakeBuffer = new() + { + 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); + } + } +} diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs b/src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs new file mode 100644 index 000000000..ffd427a5e --- /dev/null +++ b/src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs @@ -0,0 +1,125 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Backends.CompatLayer +{ + public static class Downmixing + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct Channel51FormatPCM16 + { + public short FrontLeft; + public short FrontRight; + public short FrontCenter; + public short LowFrequency; + public short BackLeft; + public short BackRight; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct ChannelStereoFormatPCM16 + { + public short Left; + public short Right; + } + + private const int Q15Bits = 16; + private const int RawQ15One = 1 << Q15Bits; + private const int RawQ15HalfOne = (int)(0.5f * RawQ15One); + private const int Minus3dBInQ15 = (int)(0.707f * RawQ15One); + private const int Minus6dBInQ15 = (int)(0.501f * RawQ15One); + private const int Minus12dBInQ15 = (int)(0.251f * RawQ15One); + + private static readonly int[] _defaultSurroundToStereoCoefficients = new int[4] + { + RawQ15One, + Minus3dBInQ15, + Minus12dBInQ15, + Minus3dBInQ15, + }; + + private static readonly int[] _defaultStereoToMonoCoefficients = new int[2] + { + Minus6dBInQ15, + Minus6dBInQ15, + }; + + private const int SurroundChannelCount = 6; + private const int StereoChannelCount = 2; + private const int MonoChannelCount = 1; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetSurroundBuffer(ReadOnlySpan data) + { + return MemoryMarshal.Cast(data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetStereoBuffer(ReadOnlySpan data) + { + return MemoryMarshal.Cast(data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short DownMixStereoToMono(ReadOnlySpan coefficients, short left, short right) + { + return (short)((left * coefficients[0] + right * coefficients[1]) >> Q15Bits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short DownMixSurroundToStereo(ReadOnlySpan coefficients, short back, short lfe, short center, short front) + { + return (short)((coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front + RawQ15HalfOne) >> Q15Bits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short[] DownMixSurroundToStereo(ReadOnlySpan coefficients, ReadOnlySpan data) + { + int samplePerChannelCount = data.Length / SurroundChannelCount; + + short[] downmixedBuffer = new short[samplePerChannelCount * StereoChannelCount]; + + ReadOnlySpan channels = GetSurroundBuffer(data); + + for (int i = 0; i < samplePerChannelCount; i++) + { + Channel51FormatPCM16 channel = channels[i]; + + downmixedBuffer[i * 2] = DownMixSurroundToStereo(coefficients, channel.BackLeft, channel.LowFrequency, channel.FrontCenter, channel.FrontLeft); + downmixedBuffer[i * 2 + 1] = DownMixSurroundToStereo(coefficients, channel.BackRight, channel.LowFrequency, channel.FrontCenter, channel.FrontRight); + } + + return downmixedBuffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short[] DownMixStereoToMono(ReadOnlySpan coefficients, ReadOnlySpan data) + { + int samplePerChannelCount = data.Length / StereoChannelCount; + + short[] downmixedBuffer = new short[samplePerChannelCount * MonoChannelCount]; + + ReadOnlySpan channels = GetStereoBuffer(data); + + for (int i = 0; i < samplePerChannelCount; i++) + { + ChannelStereoFormatPCM16 channel = channels[i]; + + downmixedBuffer[i] = DownMixStereoToMono(coefficients, channel.Left, channel.Right); + } + + return downmixedBuffer; + } + + public static short[] DownMixStereoToMono(ReadOnlySpan data) + { + return DownMixStereoToMono(_defaultStereoToMonoCoefficients, data); + } + + public static short[] DownMixSurroundToStereo(ReadOnlySpan data) + { + return DownMixSurroundToStereo(_defaultSurroundToStereoCoefficients, data); + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs new file mode 100644 index 000000000..bac21c448 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs @@ -0,0 +1,88 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Threading; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.Dummy +{ + public class DummyHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + + public static bool IsSupported => true; + + 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); + } + + return new DummyHardwareDeviceSessionInput(this, memoryManager); + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + 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; + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs new file mode 100644 index 000000000..f51a63393 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Audio.Backends.Dummy +{ + class DummyHardwareDeviceSessionInput : IHardwareDeviceSession + { + private float _volume; + private readonly IHardwareDeviceDriver _manager; + private readonly IVirtualMemoryManager _memoryManager; + + public DummyHardwareDeviceSessionInput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager) + { + _volume = 1.0f; + _manager = manager; + _memoryManager = memoryManager; + } + + public void Dispose() + { + // Nothing to do. + } + + public ulong GetPlayedSampleCount() + { + // Not implemented for input. + throw new NotSupportedException(); + } + + public float GetVolume() + { + return _volume; + } + + public void PrepareToClose() { } + + public void QueueBuffer(AudioBuffer buffer) + { + _memoryManager.Fill(buffer.DataPointer, buffer.DataSize, 0); + + _manager.GetUpdateRequiredEvent().Set(); + } + + public bool RegisterBuffer(AudioBuffer buffer) + { + return buffer.DataPointer != 0; + } + + public void SetVolume(float volume) + { + _volume = volume; + } + + public void Start() { } + + public void Stop() { } + + public void UnregisterBuffer(AudioBuffer buffer) { } + + public bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return true; + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs new file mode 100644 index 000000000..1c248faaa --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System.Threading; + +namespace Ryujinx.Audio.Backends.Dummy +{ + internal class DummyHardwareDeviceSessionOutput : HardwareDeviceSessionOutputBase + { + private float _volume; + private readonly IHardwareDeviceDriver _manager; + + private ulong _playedSampleCount; + + public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _volume = requestedVolume; + _manager = manager; + } + + public override void Dispose() + { + // Nothing to do. + } + + 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) + { + Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); + + _manager.GetUpdateRequiredEvent().Set(); + } + + public override void SetVolume(float volume) + { + _volume = volume; + } + + public override void Start() { } + + public override void Stop() { } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return true; + } + } +} diff --git a/src/Ryujinx.Audio/Common/AudioBuffer.cs b/src/Ryujinx.Audio/Common/AudioBuffer.cs new file mode 100644 index 000000000..87a7d5f32 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioBuffer.cs @@ -0,0 +1,37 @@ +using Ryujinx.Audio.Integration; + +namespace Ryujinx.Audio.Common +{ + /// + /// Represent an audio buffer that will be used by an . + /// + public class AudioBuffer + { + /// + /// Unique tag of this buffer. + /// + /// Unique per session + public ulong BufferTag; + + /// + /// Pointer to the user samples. + /// + public ulong DataPointer; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// The timestamp at which the buffer was played. + /// + /// Not used but useful for debugging + public ulong PlayedTimestamp; + + /// + /// The user samples. + /// + public byte[] Data; + } +} diff --git a/src/Ryujinx.Audio/Common/AudioDeviceSession.cs b/src/Ryujinx.Audio/Common/AudioDeviceSession.cs new file mode 100644 index 000000000..a0e04c80d --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioDeviceSession.cs @@ -0,0 +1,516 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Common; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Common +{ + /// + /// An audio device session. + /// + class AudioDeviceSession : IDisposable + { + /// + /// The volume of the . + /// + private float _volume; + + /// + /// The state of the . + /// + private AudioDeviceState _state; + + /// + /// Array of all buffers currently used or released. + /// + private readonly AudioBuffer[] _buffers; + + /// + /// The server index inside (appended but not queued to device driver). + /// + private uint _serverBufferIndex; + + /// + /// The hardware index inside (queued to device driver). + /// + private uint _hardwareBufferIndex; + + /// + /// The released index inside (released by the device driver). + /// + private uint _releasedBufferIndex; + + /// + /// The count of buffer appended (server side). + /// + private uint _bufferAppendedCount; + + /// + /// The count of buffer registered (driver side). + /// + private uint _bufferRegisteredCount; + + /// + /// The count of buffer released (released by the driver side). + /// + private uint _bufferReleasedCount; + + /// + /// The released buffer event. + /// + private readonly IWritableEvent _bufferEvent; + + /// + /// The session on the device driver. + /// + private readonly IHardwareDeviceSession _hardwareDeviceSession; + + /// + /// Max number of buffers that can be registered to the device driver at a time. + /// + private readonly uint _bufferRegisteredLimit; + + /// + /// Create a new . + /// + /// The device driver session associated + /// The release buffer event + /// The max number of buffers that can be registered to the device driver at a time + 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; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent GetBufferEvent() + { + return _bufferEvent; + } + + /// + /// Get the state of the session. + /// + /// The state of the session + public AudioDeviceState GetState() + { + Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped); + + return _state; + } + + /// + /// Get the total buffer count (server + driver + released). + /// + /// Return the total buffer count + private uint GetTotalBufferCount() + { + uint bufferCount = _bufferAppendedCount + _bufferRegisteredCount + _bufferReleasedCount; + + Debug.Assert(bufferCount <= Constants.AudioDeviceBufferCountMax); + + return bufferCount; + } + + /// + /// Register a new on the server side. + /// + /// The to register + /// True if the operation succeeded + private bool RegisterBuffer(AudioBuffer buffer) + { + if (GetTotalBufferCount() == Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _buffers[_serverBufferIndex] = buffer; + _serverBufferIndex = (_serverBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount++; + + return true; + } + + /// + /// Flush server buffers to hardware. + /// + 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]); + } + } + + /// + /// Get the current index of the playing on the driver side. + /// + /// The output index of the playing on the driver side + /// True if any buffer is playing + private bool TryGetPlayingBufferIndex(out uint playingIndex) + { + if (_bufferRegisteredCount > 0) + { + playingIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + return true; + } + + playingIndex = 0; + + return false; + } + + /// + /// Try to pop the playing on the driver side. + /// + /// The output playing on the driver side + /// True if any buffer is playing + 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; + } + + /// + /// Try to pop a released by the driver side. + /// + /// The output released by the driver side + /// True if any buffer has been released + 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; + } + + /// + /// Release a . + /// + /// The to release + private void ReleaseBuffer(AudioBuffer buffer) + { + buffer.PlayedTimestamp = (ulong)PerformanceCounter.ElapsedNanoseconds; + + _bufferRegisteredCount--; + _bufferReleasedCount++; + + _releasedBufferIndex = (_releasedBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + /// + /// Update the released buffers. + /// + /// True if the session is currently stopping + 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(); + } + } + + /// + /// Append a new . + /// + /// The to append + /// True if the buffer was appended + public bool AppendBuffer(AudioBuffer buffer) + { + if (_hardwareDeviceSession.RegisterBuffer(buffer)) + { + if (RegisterBuffer(buffer)) + { + FlushToHardware(); + + return true; + } + + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + return false; + } + + public static bool AppendUacBuffer(AudioBuffer buffer, uint handle) + { + // NOTE: On hardware, there is another RegisterBuffer method taking a handle. + // This variant of the call always return false (stubbed?) as a result this logic will never succeed. + + return false; + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + if (_state == AudioDeviceState.Started) + { + return ResultCode.OperationFailed; + } + + _hardwareDeviceSession.Start(); + + _state = AudioDeviceState.Started; + + FlushToHardware(); + + _hardwareDeviceSession.SetVolume(_volume); + + return ResultCode.Success; + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.Stop(); + + UpdateReleaseBuffers(true); + + _state = AudioDeviceState.Stopped; + } + + return ResultCode.Success; + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + return _hardwareDeviceSession.GetVolume(); + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + _volume = volume; + + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + return _bufferAppendedCount + _bufferRegisteredCount; + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; + + uint totalBufferCount = GetTotalBufferCount(); + + for (int i = 0; i < totalBufferCount; i++) + { + if (_buffers[bufferIndex].BufferTag == bufferTag) + { + return true; + } + + bufferIndex = (bufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + return false; + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + if (_state == AudioDeviceState.Stopped) + { + return 0; + } + + return _hardwareDeviceSession.GetPlayedSampleCount(); + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffer was flushed + 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; + } + + /// + /// Update the session. + /// + 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(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Common/AudioDeviceState.cs b/src/Ryujinx.Audio/Common/AudioDeviceState.cs new file mode 100644 index 000000000..8705e802e --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioDeviceState.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Common +{ + /// + /// Audio device state. + /// + public enum AudioDeviceState : uint + { + /// + /// The audio device is started. + /// + Started, + + /// + /// The audio device is stopped. + /// + Stopped, + } +} diff --git a/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs b/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs new file mode 100644 index 000000000..078c3a394 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs @@ -0,0 +1,29 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user input configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioInputConfiguration + { + /// + /// The target sample rate of the user. + /// + /// Only 48000Hz is considered valid, other sample rates will be refused. + public uint SampleRate; + + /// + /// The target channel count of the user. + /// + /// Only Stereo and Surround are considered valid, other configurations will be refused. + /// Not used in audin. + public ushort ChannelCount; + + /// + /// Reserved/unused. + /// + private readonly ushort _reserved; + } +} diff --git a/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs b/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs new file mode 100644 index 000000000..594f12250 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs @@ -0,0 +1,37 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio system output configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioOutputConfiguration + { + /// + /// The target sample rate of the system. + /// + public uint SampleRate; + + /// + /// The target channel count of the system. + /// + public uint ChannelCount; + + /// + /// Reserved/unused + /// + public SampleFormat SampleFormat; + + /// + /// Reserved/unused. + /// + private Array3 _padding; + + /// + /// The initial audio system state. + /// + public AudioDeviceState AudioOutState; + } +} diff --git a/src/Ryujinx.Audio/Common/AudioUserBuffer.cs b/src/Ryujinx.Audio/Common/AudioUserBuffer.cs new file mode 100644 index 000000000..bb71165ff --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioUserBuffer.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user buffer. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioUserBuffer + { + /// + /// Pointer to the next buffer (ignored). + /// + public ulong NextBuffer; + + /// + /// Pointer to the user samples. + /// + public ulong Data; + + /// + /// Capacity of the buffer (unused). + /// + public ulong Capacity; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// Offset in the user samples region (unused). + /// + public ulong DataOffset; + } +} diff --git a/src/Ryujinx.Audio/Common/SampleFormat.cs b/src/Ryujinx.Audio/Common/SampleFormat.cs new file mode 100644 index 000000000..39e525e87 --- /dev/null +++ b/src/Ryujinx.Audio/Common/SampleFormat.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Audio.Common +{ + /// + /// Sample format definition. + /// + public enum SampleFormat : byte + { + /// + /// Invalid sample format. + /// + Invalid = 0, + + /// + /// PCM8 sample format. (unsupported) + /// + PcmInt8 = 1, + + /// + /// PCM16 sample format. + /// + PcmInt16 = 2, + + /// + /// PCM24 sample format. (unsupported) + /// + PcmInt24 = 3, + + /// + /// PCM32 sample format. + /// + PcmInt32 = 4, + + /// + /// PCM Float sample format. + /// + PcmFloat = 5, + + /// + /// ADPCM sample format. (Also known as GC-ADPCM) + /// + Adpcm = 6, + } +} diff --git a/src/Ryujinx.Audio/Constants.cs b/src/Ryujinx.Audio/Constants.cs new file mode 100644 index 000000000..eb5b39013 --- /dev/null +++ b/src/Ryujinx.Audio/Constants.cs @@ -0,0 +1,175 @@ +namespace Ryujinx.Audio +{ + /// + /// Define constants used by the audio system. + /// + public static class Constants + { + /// + /// The default device output name. + /// + public const string DefaultDeviceOutputName = "DeviceOut"; + + /// + /// The default device input name. + /// + public const string DefaultDeviceInputName = "BuiltInHeadset"; + + /// + /// The maximum number of channels supported. (6 channels for 5.1 surround) + /// + public const int ChannelCountMax = 6; + + /// + /// The maximum number of channels supported per voice. + /// + public const int VoiceChannelCountMax = ChannelCountMax; + + /// + /// The maximum count of mix buffer supported per operations (volumes, mix effect, ...) + /// + public const int MixBufferCountMax = 24; + + /// + /// The maximum count of wavebuffer per voice. + /// + public const int VoiceWaveBufferCount = 4; + + /// + /// The maximum count of biquad filter per voice. + /// + public const int VoiceBiquadFilterCount = 2; + + /// + /// The lowest priority that a voice can have. + /// + public const int VoiceLowestPriority = 0xFF; + + /// + /// The highest priority that a voice can have. + /// + /// Voices with the highest priority will not be dropped if a voice drop needs to occur. + public const int VoiceHighestPriority = 0; + + /// + /// Maximum that can be returned by . + /// + public const int MaxErrorInfos = 10; + + /// + /// Default alignment for buffers. + /// + public const int BufferAlignment = 0x40; + + /// + /// Alignment required for the work buffer. + /// + public const int WorkBufferAlignment = 0x1000; + + /// + /// Alignment required for every performance metrics frame. + /// + public const int PerformanceMetricsPerFramesSizeAlignment = 0x100; + + /// + /// The id of the final mix. + /// + public const int FinalMixId = 0; + + /// + /// The id defining an unused mix id. + /// + public const int UnusedMixId = int.MaxValue; + + /// + /// The id defining an unused splitter id as a signed integer. + /// + public const int UnusedSplitterIdInt = -1; + + /// + /// The id defining an unused splitter id. + /// + public const uint UnusedSplitterId = uint.MaxValue; + + /// + /// The id of invalid/unused node id. + /// + public const int InvalidNodeId = -268435456; + + /// + /// The indice considered invalid for processing order. + /// + public const int InvalidProcessingOrder = -1; + + /// + /// The maximum number of audio renderer sessions allowed to be created system wide. + /// + public const int AudioRendererSessionCountMax = 2; + + /// + /// The maximum number of audio output sessions allowed to be created system wide. + /// + public const int AudioOutSessionCountMax = 12; + + /// + /// The maximum number of audio input sessions allowed to be created system wide. + /// + public const int AudioInSessionCountMax = 4; + + /// + /// Maximum buffers supported by one audio device session. + /// + public const int AudioDeviceBufferCountMax = 32; + + /// + /// The target sample rate of the audio renderer. (48kHz) + /// + public const uint TargetSampleRate = 48000; + + /// + /// The target sample size of the audio renderer. (PCM16) + /// + public const int TargetSampleSize = sizeof(ushort); + + /// + /// The target sample count per audio renderer update. + /// + public const int TargetSampleCount = 240; + + /// + /// The size of an upsampler entry to process upsampling to . + /// + public const int UpSampleEntrySize = TargetSampleCount * VoiceChannelCountMax; + + /// + /// The target audio latency computed from and . + /// + public const int AudioProcessorMaxUpdateTimeTarget = 1000000000 / ((int)TargetSampleRate / TargetSampleCount); // 5.00 ms + + /// + /// The maximum update time of the DSP on original hardware. + /// + public const int AudioProcessorMaxUpdateTime = 5760000; // 5.76 ms + + /// + /// The maximum update time per audio renderer session. + /// + public const int AudioProcessorMaxUpdateTimePerSessions = AudioProcessorMaxUpdateTime / AudioRendererSessionCountMax; + + /// + /// Guest timer frequency used for system ticks. + /// + public const int TargetTimerFrequency = 19200000; + + /// + /// The default coefficients used for standard 5.1 surround to stereo downmixing. + /// + public static readonly float[] DefaultSurroundToStereoCoefficients = new float[4] + { + 1.0f, + 0.707f, + 0.251f, + 0.707f, + }; + } +} diff --git a/src/Ryujinx.Audio/Input/AudioInputManager.cs b/src/Ryujinx.Audio/Input/AudioInputManager.cs new file mode 100644 index 000000000..4d1796c96 --- /dev/null +++ b/src/Ryujinx.Audio/Input/AudioInputManager.cs @@ -0,0 +1,270 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Audio.Input +{ + /// + /// The audio input manager. + /// + public class AudioInputManager : IDisposable + { + private readonly object _lock = new(); + + /// + /// Lock used for session allocation. + /// + private readonly object _sessionLock = new(); + + /// + /// The session ids allocation table. + /// + private readonly int[] _sessionIds; + + /// + /// The device driver. + /// + private IHardwareDeviceDriver _deviceDriver; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsBufferEvents; + + /// + /// The session instances. + /// + private readonly AudioInputSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + public AudioInputManager() + { + _sessionIds = new int[Constants.AudioInSessionCountMax]; + _sessions = new AudioInputSystem[Constants.AudioInSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The device driver. + /// The events associated to each session. + public void Initialize(IHardwareDeviceDriver deviceDriver, IWritableEvent[] sessionRegisterEvents) + { + _deviceDriver = deviceDriver; + _sessionsBufferEvents = sessionRegisterEvents; + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new input ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered input ({sessionId})"); + } + + /// + /// Used to update audio input system. + /// + public void Update() + { + lock (_sessionLock) + { + foreach (AudioInputSystem input in _sessions) + { + input?.Update(); + } + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioInputSystem input) + { + lock (_sessionLock) + { + _sessions[input.GetSessionId()] = input; + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioInputSystem input) + { + lock (_sessionLock) + { + int sessionId = input.GetSessionId(); + + _sessions[input.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + } + + /// + /// Get the list of all audio inputs names. + /// + /// If true, filter disconnected devices + /// The list of all audio inputs name +#pragma warning disable CA1822 // Mark member as static + public string[] ListAudioIns(bool filtered) + { + if (filtered) + { + // TODO: Detect if the driver supports audio input + } + + return new[] { Constants.DefaultDeviceInputName }; + } +#pragma warning restore CA1822 + + /// + /// Open a new . + /// + /// The output device name selected by the + /// The output audio configuration selected by the + /// The new + /// The memory manager that will be used for all guest memory operations + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The applet resource user id of the application + /// The process handle of the application + /// A reporting an error or a success + public ResultCode OpenAudioIn(out string outputDeviceName, + out AudioOutputConfiguration outputConfiguration, + out AudioInputSystem obj, + IVirtualMemoryManager memoryManager, + string inputDeviceName, + SampleFormat sampleFormat, + ref AudioInputConfiguration parameter, + ulong appletResourceUserId, + uint processHandle) + { + int sessionId = AcquireSessionId(); + + _sessionsBufferEvents[sessionId].Clear(); + + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Input, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount); + + AudioInputSystem audioIn = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); + + ResultCode result = audioIn.Initialize(inputDeviceName, sampleFormat, ref parameter, sessionId); + + if (result == ResultCode.Success) + { + outputDeviceName = audioIn.DeviceName; + outputConfiguration = new AudioOutputConfiguration + { + ChannelCount = audioIn.ChannelCount, + SampleFormat = audioIn.SampleFormat, + SampleRate = audioIn.SampleRate, + AudioOutState = audioIn.GetState(), + }; + + obj = audioIn; + + Register(audioIn); + } + else + { + ReleaseSessionId(sessionId); + + obj = null; + outputDeviceName = null; + outputConfiguration = default; + } + + return result; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Clone the sessions array to dispose them outside the lock. + AudioInputSystem[] sessions; + + lock (_sessionLock) + { + sessions = _sessions.ToArray(); + } + + foreach (AudioInputSystem input in sessions) + { + input?.Dispose(); + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Input/AudioInputSystem.cs b/src/Ryujinx.Audio/Input/AudioInputSystem.cs new file mode 100644 index 000000000..34623b34f --- /dev/null +++ b/src/Ryujinx.Audio/Input/AudioInputSystem.cs @@ -0,0 +1,396 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Input +{ + /// + /// Audio input system. + /// + public class AudioInputSystem : IDisposable + { + /// + /// The session id associated to the . + /// + private int _sessionId; + + /// + /// The session the . + /// + private readonly AudioDeviceSession _session; + + /// + /// The target device name of the . + /// + public string DeviceName { get; private set; } + + /// + /// The target sample rate of the . + /// + public uint SampleRate { get; private set; } + + /// + /// The target channel count of the . + /// + public uint ChannelCount { get; private set; } + + /// + /// The target sample format of the . + /// + public SampleFormat SampleFormat { get; private set; } + + /// + /// The owning this. + /// + private readonly AudioInputManager _manager; + + /// + /// The lock of the parent. + /// + private readonly object _parentLock; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + /// The manager instance + /// The lock of the manager + /// The hardware device session + /// The buffer release event of the audio input + public AudioInputSystem(AudioInputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent) + { + _manager = manager; + _parentLock = parentLock; + _session = new AudioDeviceSession(deviceSession, bufferEvent); + } + + /// + /// Get the default device name on the system. + /// + /// The default device name on the system. + private static string GetDeviceDefaultName() + { + return Constants.DefaultDeviceInputName; + } + + /// + /// Check if a given configuration and device name is valid on the system. + /// + /// The configuration to check. + /// The device name to check. + /// A reporting an error or a success. + private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName) + { + if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName())) + { + return ResultCode.DeviceNotFound; + } + + if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate) + { + return ResultCode.UnsupportedSampleRate; + } + + if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6) + { + return ResultCode.UnsupportedChannelConfiguration; + } + + return ResultCode.Success; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent RegisterBufferEvent() + { + lock (_parentLock) + { + return _session.GetBufferEvent(); + } + } + + /// + /// Update the . + /// + public void Update() + { + lock (_parentLock) + { + _session.Update(); + } + } + + /// + /// Get the id of this session. + /// + /// The id of this session + public int GetSessionId() + { + return _sessionId; + } + + /// + /// Initialize the . + /// + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The session id associated to this + /// A reporting an error or a success. + public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId) + { + _sessionId = sessionId; + + ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName); + + if (result == ResultCode.Success) + { + if (inputDeviceName.Length == 0) + { + DeviceName = GetDeviceDefaultName(); + } + else + { + DeviceName = inputDeviceName; + } + + if (parameter.ChannelCount == 6) + { + ChannelCount = 6; + } + else + { + ChannelCount = 2; + } + + SampleFormat = sampleFormat; + SampleRate = Constants.TargetSampleRate; + } + + return result; + } + + /// + /// Append a new audio buffer to the audio input. + /// + /// The unique tag of this buffer. + /// The buffer informations. + /// A reporting an error or a success. + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer) + { + lock (_parentLock) + { + AudioBuffer buffer = new() + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize, + }; + + if (_session.AppendBuffer(buffer)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Append a new audio buffer to the audio input. + /// + /// This is broken by design, only added for completness. + /// The unique tag of this buffer. + /// The buffer informations. + /// Some unknown handle. + /// A reporting an error or a success. + public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer, uint handle) + { + lock (_parentLock) + { + AudioBuffer buffer = new() + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize, + }; + + if (AudioDeviceSession.AppendUacBuffer(buffer, handle)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Get the release buffers. + /// + /// The buffer to write the release buffers + /// The count of released buffers + /// A reporting an error or a success. + public ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount) + { + releasedCount = 0; + + // Ensure that the first entry is set to zero if no entries are returned. + if (releasedBuffers.Length > 0) + { + releasedBuffers[0] = 0; + } + + lock (_parentLock) + { + for (int i = 0; i < releasedBuffers.Length; i++) + { + if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer)) + { + break; + } + + releasedBuffers[i] = buffer.BufferTag; + releasedCount++; + } + } + + return ResultCode.Success; + } + + /// + /// Get the current state of the . + /// + /// Return the curent sta\te of the + public AudioDeviceState GetState() + { + lock (_parentLock) + { + return _session.GetState(); + } + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + lock (_parentLock) + { + return _session.Start(); + } + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + lock (_parentLock) + { + return _session.Stop(); + } + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + lock (_parentLock) + { + return _session.GetVolume(); + } + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + lock (_parentLock) + { + _session.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + lock (_parentLock) + { + return _session.GetBufferCount(); + } + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + lock (_parentLock) + { + return _session.ContainsBuffer(bufferTag); + } + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + lock (_parentLock) + { + return _session.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffers was flushed + public bool FlushBuffers() + { + lock (_parentLock) + { + return _session.FlushBuffers(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + + _manager.Unregister(this); + } + } + } +} diff --git a/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs new file mode 100644 index 000000000..576954b96 --- /dev/null +++ b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Common; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Integration +{ + public class HardwareDeviceImpl : IHardwareDevice + { + private readonly IHardwareDeviceSession _session; + private readonly uint _channelCount; + private readonly uint _sampleRate; + private uint _currentBufferTag; + + private readonly byte[] _buffer; + + public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume) + { + _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume); + _channelCount = channelCount; + _sampleRate = sampleRate; + _currentBufferTag = 0; + + _buffer = new byte[Constants.TargetSampleCount * channelCount * sizeof(ushort)]; + + _session.Start(); + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + data.CopyTo(MemoryMarshal.Cast(_buffer)); + + _session.QueueBuffer(new AudioBuffer + { + DataPointer = _currentBufferTag++, + Data = _buffer, + DataSize = (ulong)_buffer.Length, + }); + + _currentBufferTag %= 4; + } + + public void SetVolume(float volume) + { + _session.SetVolume(volume); + } + + public float GetVolume() + { + return _session.GetVolume(); + } + + public uint GetChannelCount() + { + return _channelCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Integration/IHardwareDevice.cs b/src/Ryujinx.Audio/Integration/IHardwareDevice.cs new file mode 100644 index 000000000..f9ade9dbc --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IHardwareDevice.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent an hardware device used in + /// + public interface IHardwareDevice : IDisposable + { + /// + /// Sets the volume level for this device. + /// + /// The volume level to set. + void SetVolume(float volume); + + /// + /// Gets the volume level for this device. + /// + /// The volume level of this device. + float GetVolume(); + + /// + /// Get the supported sample rate of this device. + /// + /// The supported sample rate of this device. + uint GetSampleRate(); + + /// + /// Get the channel count supported by this device. + /// + /// The channel count supported by this device. + uint GetChannelCount(); + + /// + /// Appends new PCM16 samples to the device. + /// + /// The new PCM16 samples. + /// The number of channels. + void AppendBuffer(ReadOnlySpan data, uint channelCount); + + /// + /// Check if the audio renderer needs to perform downmixing. + /// + /// True if downmixing is needed. + public bool NeedDownmixing() + { + uint channelCount = GetChannelCount(); + + Debug.Assert(channelCount > 0 && channelCount <= Constants.ChannelCountMax); + + return channelCount != Constants.ChannelCountMax; + } + } +} diff --git a/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs new file mode 100644 index 000000000..9c812fb9a --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs @@ -0,0 +1,36 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent an hardware device driver used in . + /// + 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); + + static abstract bool IsSupported { get; } + + IHardwareDeviceDriver GetRealDeviceDriver() + { + return this; + } + } +} diff --git a/src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs b/src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs new file mode 100644 index 000000000..f29c109cb --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs @@ -0,0 +1,28 @@ +using Ryujinx.Audio.Common; +using System; + +namespace Ryujinx.Audio.Integration +{ + public interface IHardwareDeviceSession : IDisposable + { + bool RegisterBuffer(AudioBuffer buffer); + + void UnregisterBuffer(AudioBuffer buffer); + + void QueueBuffer(AudioBuffer buffer); + + bool WasBufferFullyConsumed(AudioBuffer buffer); + + void SetVolume(float volume); + + float GetVolume(); + + ulong GetPlayedSampleCount(); + + void Start(); + + void Stop(); + + void PrepareToClose(); + } +} diff --git a/src/Ryujinx.Audio/Integration/IWritableEvent.cs b/src/Ryujinx.Audio/Integration/IWritableEvent.cs new file mode 100644 index 000000000..a3b3bc0bc --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IWritableEvent.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent a writable event with manual clear. + /// + public interface IWritableEvent + { + /// + /// Signal the event. + /// + void Signal(); + + /// + /// Clear the signaled state of the event. + /// + void Clear(); + } +} diff --git a/src/Ryujinx.Audio/Output/AudioOutputManager.cs b/src/Ryujinx.Audio/Output/AudioOutputManager.cs new file mode 100644 index 000000000..5232357bb --- /dev/null +++ b/src/Ryujinx.Audio/Output/AudioOutputManager.cs @@ -0,0 +1,301 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Audio.Output +{ + /// + /// The audio output manager. + /// + public class AudioOutputManager : IDisposable + { + private readonly object _lock = new(); + + /// + /// Lock used for session allocation. + /// + private readonly object _sessionLock = new(); + + /// + /// The session ids allocation table. + /// + private readonly int[] _sessionIds; + + /// + /// The device driver. + /// + private IHardwareDeviceDriver _deviceDriver; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsBufferEvents; + + /// + /// The session instances. + /// + private readonly AudioOutputSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + public AudioOutputManager() + { + _sessionIds = new int[Constants.AudioOutSessionCountMax]; + _sessions = new AudioOutputSystem[Constants.AudioOutSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The device driver. + /// The events associated to each session. + public void Initialize(IHardwareDeviceDriver deviceDriver, IWritableEvent[] sessionRegisterEvents) + { + _deviceDriver = deviceDriver; + _sessionsBufferEvents = sessionRegisterEvents; + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new output ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered output ({sessionId})"); + } + + /// + /// Used to update audio output system. + /// + public void Update() + { + lock (_sessionLock) + { + foreach (AudioOutputSystem output in _sessions) + { + output?.Update(); + } + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioOutputSystem output) + { + lock (_sessionLock) + { + _sessions[output.GetSessionId()] = output; + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioOutputSystem output) + { + lock (_sessionLock) + { + int sessionId = output.GetSessionId(); + + _sessions[output.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + } + + /// + /// Get the list of all audio outputs name. + /// + /// The list of all audio outputs name +#pragma warning disable CA1822 // Mark member as static + public string[] ListAudioOuts() + { + return new[] { Constants.DefaultDeviceOutputName }; + } +#pragma warning restore CA1822 + + /// + /// Open a new . + /// + /// The output device name selected by the + /// The output audio configuration selected by the + /// The new + /// The memory manager that will be used for all guest memory operations + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The applet resource user id of the application + /// The process handle of the application + /// The volume level to request + /// A reporting an error or a success + public ResultCode OpenAudioOut(out string outputDeviceName, + out AudioOutputConfiguration outputConfiguration, + out AudioOutputSystem obj, + IVirtualMemoryManager memoryManager, + string inputDeviceName, + SampleFormat sampleFormat, + ref AudioInputConfiguration parameter, + ulong appletResourceUserId, + uint processHandle, + float volume) + { + int sessionId = AcquireSessionId(); + + _sessionsBufferEvents[sessionId].Clear(); + + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume); + + AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); + + ResultCode result = audioOut.Initialize(inputDeviceName, sampleFormat, ref parameter, sessionId); + + if (result == ResultCode.Success) + { + outputDeviceName = audioOut.DeviceName; + outputConfiguration = new AudioOutputConfiguration + { + ChannelCount = audioOut.ChannelCount, + SampleFormat = audioOut.SampleFormat, + SampleRate = audioOut.SampleRate, + AudioOutState = audioOut.GetState(), + }; + + obj = audioOut; + + Register(audioOut); + } + else + { + ReleaseSessionId(sessionId); + + obj = null; + outputDeviceName = null; + outputConfiguration = default; + } + + return result; + } + + /// + /// Sets the volume for all output devices. + /// + /// The volume to set. + public void SetVolume(float volume) + { + if (_sessions != null) + { + foreach (AudioOutputSystem session in _sessions) + { + session?.SetVolume(volume); + } + } + } + + /// + /// Gets the volume for all output devices. + /// + /// A float indicating the volume level. + public float GetVolume() + { + if (_sessions != null) + { + foreach (AudioOutputSystem session in _sessions) + { + if (session != null) + { + return session.GetVolume(); + } + } + } + + return 0.0f; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Clone the sessions array to dispose them outside the lock. + AudioOutputSystem[] sessions; + + lock (_sessionLock) + { + sessions = _sessions.ToArray(); + } + + foreach (AudioOutputSystem output in sessions) + { + output?.Dispose(); + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Output/AudioOutputSystem.cs b/src/Ryujinx.Audio/Output/AudioOutputSystem.cs new file mode 100644 index 000000000..f9b9bdcf1 --- /dev/null +++ b/src/Ryujinx.Audio/Output/AudioOutputSystem.cs @@ -0,0 +1,369 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Output +{ + /// + /// Audio output system. + /// + public class AudioOutputSystem : IDisposable + { + /// + /// The session id associated to the . + /// + private int _sessionId; + + /// + /// The session the . + /// + private readonly AudioDeviceSession _session; + + /// + /// The target device name of the . + /// + public string DeviceName { get; private set; } + + /// + /// The target sample rate of the . + /// + public uint SampleRate { get; private set; } + + /// + /// The target channel count of the . + /// + public uint ChannelCount { get; private set; } + + /// + /// The target sample format of the . + /// + public SampleFormat SampleFormat { get; private set; } + + /// + /// The owning this. + /// + private readonly AudioOutputManager _manager; + + /// + /// THe lock of the parent. + /// + private readonly object _parentLock; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + /// The manager instance + /// The lock of the manager + /// The hardware device session + /// The buffer release event of the audio output + public AudioOutputSystem(AudioOutputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent) + { + _manager = manager; + _parentLock = parentLock; + _session = new AudioDeviceSession(deviceSession, bufferEvent); + } + + /// + /// Get the default device name on the system. + /// + /// The default device name on the system. + private static string GetDeviceDefaultName() + { + return Constants.DefaultDeviceOutputName; + } + + /// + /// Check if a given configuration and device name is valid on the system. + /// + /// The configuration to check. + /// The device name to check. + /// A reporting an error or a success. + private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName) + { + if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName())) + { + return ResultCode.DeviceNotFound; + } + + if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate) + { + return ResultCode.UnsupportedSampleRate; + } + + if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6) + { + return ResultCode.UnsupportedChannelConfiguration; + } + + return ResultCode.Success; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent RegisterBufferEvent() + { + lock (_parentLock) + { + return _session.GetBufferEvent(); + } + } + + /// + /// Update the . + /// + public void Update() + { + lock (_parentLock) + { + _session.Update(); + } + } + + /// + /// Get the id of this session. + /// + /// The id of this session + public int GetSessionId() + { + return _sessionId; + } + + /// + /// Initialize the . + /// + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The session id associated to this + /// A reporting an error or a success. + public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId) + { + _sessionId = sessionId; + + ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName); + + if (result == ResultCode.Success) + { + if (inputDeviceName.Length == 0) + { + DeviceName = GetDeviceDefaultName(); + } + else + { + DeviceName = inputDeviceName; + } + + if (parameter.ChannelCount == 6) + { + ChannelCount = 6; + } + else + { + ChannelCount = 2; + } + + SampleFormat = sampleFormat; + SampleRate = Constants.TargetSampleRate; + } + + return result; + } + + /// + /// Append a new audio buffer to the audio output. + /// + /// The unique tag of this buffer. + /// The buffer informations. + /// A reporting an error or a success. + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer) + { + lock (_parentLock) + { + AudioBuffer buffer = new() + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize, + }; + + if (_session.AppendBuffer(buffer)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Get the release buffers. + /// + /// The buffer to write the release buffers + /// The count of released buffers + /// A reporting an error or a success. + public ResultCode GetReleasedBuffer(Span releasedBuffers, out uint releasedCount) + { + releasedCount = 0; + + // Ensure that the first entry is set to zero if no entries are returned. + if (releasedBuffers.Length > 0) + { + releasedBuffers[0] = 0; + } + + lock (_parentLock) + { + for (int i = 0; i < releasedBuffers.Length; i++) + { + if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer)) + { + break; + } + + releasedBuffers[i] = buffer.BufferTag; + releasedCount++; + } + } + + return ResultCode.Success; + } + + /// + /// Get the current state of the . + /// + /// Return the curent sta\te of the + /// + public AudioDeviceState GetState() + { + lock (_parentLock) + { + return _session.GetState(); + } + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + lock (_parentLock) + { + return _session.Start(); + } + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + lock (_parentLock) + { + return _session.Stop(); + } + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + lock (_parentLock) + { + return _session.GetVolume(); + } + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + lock (_parentLock) + { + _session.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + lock (_parentLock) + { + return _session.GetBufferCount(); + } + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + lock (_parentLock) + { + return _session.ContainsBuffer(bufferTag); + } + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + lock (_parentLock) + { + return _session.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffers was flushed + public bool FlushBuffers() + { + lock (_parentLock) + { + return _session.FlushBuffers(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + + _manager.Unregister(this); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs b/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs new file mode 100644 index 000000000..b7b97d5d8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AuxiliaryBufferAddresses + { + public ulong SendBufferInfo; + public ulong SendBufferInfoBase; + public ulong ReturnBufferInfo; + public ulong ReturnBufferInfoBase; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs new file mode 100644 index 000000000..b0963c935 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs @@ -0,0 +1,50 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represents the input parameter for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BehaviourParameter + { + /// + /// The current audio renderer revision in use. + /// + public int UserRevision; + + /// + /// Reserved/padding. + /// + private readonly uint _padding; + + /// + /// The flags given controlling behaviour of the audio renderer + /// + /// See and . + public ulong Flags; + + /// + /// Represents an error during . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ErrorInfo + { + /// + /// The error code to report. + /// + public ResultCode ErrorCode; + + /// + /// Reserved/padding. + /// + private readonly uint _padding; + + /// + /// Extra information given with the + /// + /// This is usually used to report a faulting cpu address when a mapping fail. + public ulong ExtraErrorInfo; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs b/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs new file mode 100644 index 000000000..3beb62399 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs @@ -0,0 +1,150 @@ +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represents a adjacent matrix. + /// + /// This is used for splitter routing. + public class EdgeMatrix + { + /// + /// Backing used for node connections. + /// + private BitArray _storage; + + /// + /// The count of nodes of the current instance. + /// + private int _nodeCount; + + /// + /// Get the required work buffer size memory needed for the . + /// + /// The count of nodes. + /// The size required for the given . + public static int GetWorkBufferSize(int nodeCount) + { + int size = BitUtils.AlignUp(nodeCount * nodeCount, Constants.BufferAlignment); + + return size / Unsafe.SizeOf(); + } + + /// + /// Initializes the instance with backing memory. + /// + /// The backing memory. + /// The count of nodes. + public void Initialize(Memory edgeMatrixWorkBuffer, int nodeCount) + { + Debug.Assert(edgeMatrixWorkBuffer.Length >= GetWorkBufferSize(nodeCount)); + + _storage = new BitArray(edgeMatrixWorkBuffer); + + _nodeCount = nodeCount; + + _storage.Reset(); + } + + /// + /// Test if the bit at the given index is set. + /// + /// A bit index. + /// Returns true if the bit at the given index is set + public bool Test(int index) + { + return _storage.Test(index); + } + + /// + /// Reset all bits in the storage. + /// + public void Reset() + { + _storage.Reset(); + } + + /// + /// Reset the bit at the given index. + /// + /// A bit index. + public void Reset(int index) + { + _storage.Reset(index); + } + + /// + /// Set the bit at the given index. + /// + /// A bit index. + public void Set(int index) + { + _storage.Set(index); + } + + /// + /// Connect a given source to a given destination. + /// + /// The source index. + /// The destination index. + public void Connect(int source, int destination) + { + Debug.Assert(source < _nodeCount); + Debug.Assert(destination < _nodeCount); + + _storage.Set(_nodeCount * source + destination); + } + + /// + /// Check if the given source is connected to the given destination. + /// + /// The source index. + /// The destination index. + /// Returns true if the given source is connected to the given destination. + public bool Connected(int source, int destination) + { + Debug.Assert(source < _nodeCount); + Debug.Assert(destination < _nodeCount); + + return _storage.Test(_nodeCount * source + destination); + } + + /// + /// Disconnect a given source from a given destination. + /// + /// The source index. + /// The destination index. + public void Disconnect(int source, int destination) + { + Debug.Assert(source < _nodeCount); + Debug.Assert(destination < _nodeCount); + + _storage.Reset(_nodeCount * source + destination); + } + + /// + /// Remove all edges from a given source. + /// + /// The source index. + public void RemoveEdges(int source) + { + for (int i = 0; i < _nodeCount; i++) + { + Disconnect(source, i); + } + } + + /// + /// Get the total node count. + /// + /// The total node count. + public int GetNodeCount() + { + return _nodeCount; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/EffectType.cs b/src/Ryujinx.Audio/Renderer/Common/EffectType.cs new file mode 100644 index 000000000..7c8713b12 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/EffectType.cs @@ -0,0 +1,58 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// The type of an effect. + /// + public enum EffectType : byte + { + /// + /// Invalid effect. + /// + Invalid, + + /// + /// Effect applying additional mixing capability. + /// + BufferMix, + + /// + /// Effect applying custom user effect (via auxiliary buffers). + /// + AuxiliaryBuffer, + + /// + /// Effect applying a delay. + /// + Delay, + + /// + /// Effect applying a reverberation effect via a given preset. + /// + Reverb, + + /// + /// Effect applying a 3D reverberation effect via a given preset. + /// + Reverb3d, + + /// + /// Effect applying a biquad filter. + /// + BiquadFilter, + + /// + /// Effect applying a limiter (DRC). + /// + Limiter, + + /// + /// Effect to capture mixes (via auxiliary buffers). + /// + CaptureBuffer, + + /// + /// Effect applying a compressor filter (DRC). + /// + Compressor, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs b/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs new file mode 100644 index 000000000..6d835879c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represents the state of a memory pool. + /// + public enum MemoryPoolUserState : uint + { + /// + /// Invalid state. + /// + Invalid = 0, + + /// + /// The memory pool is new. (client side only) + /// + New = 1, + + /// + /// The user asked to detach the memory pool from the . + /// + RequestDetach = 2, + + /// + /// The memory pool is detached from the . + /// + Detached = 3, + + /// + /// The user asked to attach the memory pool to the . + /// + RequestAttach = 4, + + /// + /// The memory pool is attached to the . + /// + Attached = 5, + + /// + /// The memory pool is released. (client side only) + /// + Released = 6, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs b/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs new file mode 100644 index 000000000..76fba54b6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Helper for manipulating node ids. + /// + public static class NodeIdHelper + { + /// + /// Get the type of a node from a given node id. + /// + /// Id of the node. + /// The type of the node. + public static NodeIdType GetType(int nodeId) + { + return (NodeIdType)(nodeId >> 28); + } + + /// + /// Get the base of a node from a given node id. + /// + /// Id of the node. + /// The base of the node. + public static int GetBase(int nodeId) + { + return (nodeId >> 16) & 0xFFF; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs b/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs new file mode 100644 index 000000000..b226da14f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// The type of a node. + /// + public enum NodeIdType : byte + { + /// + /// Invalid node id. + /// + Invalid = 0, + + /// + /// Voice related node id. (data source, biquad filter, ...) + /// + Voice = 1, + + /// + /// Mix related node id. (mix, effects, splitters, ...) + /// + Mix = 2, + + /// + /// Sink related node id. (device & circular buffer sink) + /// + Sink = 3, + + /// + /// Performance monitoring related node id (performance commands) + /// + Performance = 15, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs b/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs new file mode 100644 index 000000000..75290a741 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs @@ -0,0 +1,230 @@ +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Common +{ + public class NodeStates + { + private class Stack + { + private Memory _storage; + private int _index; + + private int _nodeCount; + + public void Reset(Memory storage, int nodeCount) + { + Debug.Assert(storage.Length * sizeof(int) >= CalcBufferSize(nodeCount)); + + _storage = storage; + _index = 0; + _nodeCount = nodeCount; + } + + public int GetCurrentCount() + { + return _index; + } + + public void Push(int data) + { + Debug.Assert(_index + 1 <= _nodeCount); + + _storage.Span[_index++] = data; + } + + public int Pop() + { + Debug.Assert(_index > 0); + + return _storage.Span[--_index]; + } + + public int Top() + { + return _storage.Span[_index - 1]; + } + + public static int CalcBufferSize(int nodeCount) + { + return nodeCount * sizeof(int); + } + } + + private int _nodeCount; + private readonly EdgeMatrix _discovered; + private readonly EdgeMatrix _finished; + private Memory _resultArray; + private readonly Stack _stack; + private int _tsortResultIndex; + + private enum NodeState : byte + { + Unknown, + Discovered, + Finished, + } + + public NodeStates() + { + _stack = new Stack(); + _discovered = new EdgeMatrix(); + _finished = new EdgeMatrix(); + } + + public static int GetWorkBufferSize(int nodeCount) + { + return Stack.CalcBufferSize(nodeCount * nodeCount) + 0xC * nodeCount + 2 * EdgeMatrix.GetWorkBufferSize(nodeCount); + } + + public void Initialize(Memory nodeStatesWorkBuffer, int nodeCount) + { + int workBufferSize = GetWorkBufferSize(nodeCount); + + Debug.Assert(nodeStatesWorkBuffer.Length >= workBufferSize); + + _nodeCount = nodeCount; + + int edgeMatrixWorkBufferSize = EdgeMatrix.GetWorkBufferSize(nodeCount); + + _discovered.Initialize(nodeStatesWorkBuffer[..edgeMatrixWorkBufferSize], nodeCount); + _finished.Initialize(nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize, edgeMatrixWorkBufferSize), nodeCount); + + nodeStatesWorkBuffer = nodeStatesWorkBuffer[(edgeMatrixWorkBufferSize * 2)..]; + + _resultArray = SpanMemoryManager.Cast(nodeStatesWorkBuffer[..(sizeof(int) * nodeCount)]); + + nodeStatesWorkBuffer = nodeStatesWorkBuffer[(sizeof(int) * nodeCount)..]; + + Memory stackWorkBuffer = SpanMemoryManager.Cast(nodeStatesWorkBuffer[..Stack.CalcBufferSize(nodeCount * nodeCount)]); + + _stack.Reset(stackWorkBuffer, nodeCount * nodeCount); + } + + private void Reset() + { + _discovered.Reset(); + _finished.Reset(); + _tsortResultIndex = 0; + _resultArray.Span.Fill(-1); + } + + private NodeState GetState(int index) + { + Debug.Assert(index < _nodeCount); + + if (_discovered.Test(index)) + { + Debug.Assert(!_finished.Test(index)); + + return NodeState.Discovered; + } + + if (_finished.Test(index)) + { + Debug.Assert(!_discovered.Test(index)); + + return NodeState.Finished; + } + + return NodeState.Unknown; + } + + private void SetState(int index, NodeState state) + { + switch (state) + { + case NodeState.Unknown: + _discovered.Reset(index); + _finished.Reset(index); + break; + case NodeState.Discovered: + _discovered.Set(index); + _finished.Reset(index); + break; + case NodeState.Finished: + _finished.Set(index); + _discovered.Reset(index); + break; + } + } + + private void PushTsortResult(int index) + { + Debug.Assert(index < _nodeCount); + + _resultArray.Span[_tsortResultIndex++] = index; + } + + public ReadOnlySpan GetTsortResult() + { + return _resultArray.Span[.._tsortResultIndex]; + } + + public bool Sort(EdgeMatrix edgeMatrix) + { + Reset(); + + if (_nodeCount <= 0) + { + return true; + } + + for (int i = 0; i < _nodeCount; i++) + { + if (GetState(i) == NodeState.Unknown) + { + _stack.Push(i); + } + + while (_stack.GetCurrentCount() > 0) + { + int topIndex = _stack.Top(); + + NodeState topState = GetState(topIndex); + + if (topState == NodeState.Discovered) + { + SetState(topIndex, NodeState.Finished); + PushTsortResult(topIndex); + _stack.Pop(); + } + else if (topState == NodeState.Finished) + { + _stack.Pop(); + } + else + { + if (topState == NodeState.Unknown) + { + SetState(topIndex, NodeState.Discovered); + } + + for (int j = 0; j < edgeMatrix.GetNodeCount(); j++) + { + if (edgeMatrix.Connected(topIndex, j)) + { + NodeState jState = GetState(j); + + if (jState == NodeState.Unknown) + { + _stack.Push(j); + } + // Found a loop, reset and propagate rejection. + else if (jState == NodeState.Discovered) + { + Reset(); + + return false; + } + } + } + } + } + } + + return true; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs new file mode 100644 index 000000000..bde32a709 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + public enum PerformanceDetailType : byte + { + Unknown, + PcmInt16, + Adpcm, + VolumeRamp, + BiquadFilter, + Mix, + Delay, + Aux, + Reverb, + Reverb3d, + PcmFloat, + Limiter, + CaptureBuffer, + Compressor, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs b/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs new file mode 100644 index 000000000..e32095e62 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + public enum PerformanceEntryType : byte + { + Invalid, + Voice, + SubMix, + FinalMix, + Sink, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/PlayState.cs b/src/Ryujinx.Audio/Renderer/Common/PlayState.cs new file mode 100644 index 000000000..a83d16afb --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/PlayState.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Common play state. + /// + public enum PlayState : byte + { + /// + /// The user request the voice to be started. + /// + Start, + + /// + /// The user request the voice to be stopped. + /// + Stop, + + /// + /// The user request the voice to be paused. + /// + Pause, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs b/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs new file mode 100644 index 000000000..c7443cc49 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Early reverb reflection. + /// + public enum ReverbEarlyMode : uint + { + /// + /// Room early reflection. (small acoustic space, fast reflection) + /// + Room, + + /// + /// Chamber early reflection. (bigger than 's acoustic space, short reflection) + /// + Chamber, + + /// + /// Hall early reflection. (large acoustic space, warm reflection) + /// + Hall, + + /// + /// Cathedral early reflection. (very large acoustic space, pronounced bright reflection) + /// + Cathedral, + + /// + /// No early reflection. + /// + Disabled, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs b/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs new file mode 100644 index 000000000..78f91cf08 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs @@ -0,0 +1,38 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Late reverb reflection. + /// + public enum ReverbLateMode : uint + { + /// + /// Room late reflection. (small acoustic space, fast reflection) + /// + Room, + + /// + /// Hall late reflection. (large acoustic space, warm reflection) + /// + Hall, + + /// + /// Classic plate late reflection. (clean distinctive reverb) + /// + Plate, + + /// + /// Cathedral late reflection. (very large acoustic space, pronounced bright reflection) + /// + Cathedral, + + /// + /// Do not apply any delay. (max delay) + /// + NoDelay, + + /// + /// Max delay. (used for delay line limits) + /// + Limit = NoDelay, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/SinkType.cs b/src/Ryujinx.Audio/Renderer/Common/SinkType.cs new file mode 100644 index 000000000..5a08df4e1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/SinkType.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// The type of a sink. + /// + public enum SinkType : byte + { + /// + /// The sink is in an invalid state. + /// + Invalid, + + /// + /// The sink is a device. + /// + Device, + + /// + /// The sink is a circular buffer. + /// + CircularBuffer, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs new file mode 100644 index 000000000..7efe3b02b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs @@ -0,0 +1,36 @@ +using Ryujinx.Common.Memory; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Update data header used for input and output of . + /// + public struct UpdateDataHeader + { + public int Revision; + public uint BehaviourSize; + public uint MemoryPoolsSize; + public uint VoicesSize; + public uint VoiceResourcesSize; + public uint EffectsSize; + public uint MixesSize; + public uint SinksSize; + public uint PerformanceBufferSize; + public uint Unknown24; + public uint RenderInfoSize; + +#pragma warning disable IDE0051, CS0169 // Remove unused field + private Array4 _reserved; +#pragma warning restore IDE0051, CS0169 + + public uint TotalSize; + + public void Initialize(int revision) + { + Revision = revision; + + TotalSize = (uint)Unsafe.SizeOf(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs new file mode 100644 index 000000000..608381af1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs @@ -0,0 +1,104 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represent the update state of a voice. + /// + /// This is shared between the server and audio processor. + [StructLayout(LayoutKind.Sequential, Pack = Align)] + public struct VoiceUpdateState + { + public const int Align = 0x10; + public const int BiquadStateOffset = 0x0; + public const int BiquadStateSize = 0x10; + + /// + /// The state of the biquad filters of this voice. + /// + public Array2 BiquadFilterState; + + /// + /// The total amount of samples that was played. + /// + /// This is reset to 0 when a finishes playing and is set. + /// This is reset to 0 when looping while is set. + public ulong PlayedSampleCount; + + /// + /// The current sample offset in the pointed by . + /// + public int Offset; + + /// + /// The current index of the in use. + /// + public uint WaveBufferIndex; + + private WaveBufferValidArray _isWaveBufferValid; + + /// + /// The total amount of consumed. + /// + public uint WaveBufferConsumed; + + /// + /// Pitch used for Sample Rate Conversion. + /// + public Array8 Pitch; + + public float Fraction; + + /// + /// The ADPCM loop context when is in use. + /// + public AdpcmLoopContext LoopContext; + + /// + /// The last samples after a mix ramp. + /// + /// This is used for depop (to perform voice drop). + public Array24 LastSamples; + + /// + /// The current count of loop performed. + /// + public int LoopCount; + + [StructLayout(LayoutKind.Sequential, Size = 1 * Constants.VoiceWaveBufferCount, Pack = 1)] + private struct WaveBufferValidArray { } + + /// + /// Contains information of validity. + /// + public Span IsWaveBufferValid => SpanHelpers.AsSpan(ref _isWaveBufferValid); + + /// + /// Mark the current as played and switch to the next one. + /// + /// The current + /// The wavebuffer index. + /// The amount of wavebuffers consumed. + /// The total count of sample played. + public void MarkEndOfBufferWaveBufferProcessing(ref WaveBuffer waveBuffer, ref int waveBufferIndex, ref uint waveBufferConsumed, ref ulong playedSampleCount) + { + IsWaveBufferValid[waveBufferIndex++] = false; + LoopCount = 0; + waveBufferConsumed++; + + if (waveBufferIndex >= Constants.VoiceWaveBufferCount) + { + waveBufferIndex = 0; + } + + if (waveBuffer.IsEndOfStream) + { + playedSampleCount = 0; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs b/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs new file mode 100644 index 000000000..5109d3fa0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs @@ -0,0 +1,81 @@ +using System.Runtime.InteropServices; +using DspAddr = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// A wavebuffer used for data source commands. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WaveBuffer + { + /// + /// The DSP address of the sample data of the wavebuffer. + /// + public DspAddr Buffer; + + /// + /// The DSP address of the context of the wavebuffer. + /// + /// Only used by . + public DspAddr Context; + + /// + /// The size of the sample buffer data. + /// + public uint BufferSize; + + /// + /// The size of the context buffer. + /// + public uint ContextSize; + + /// + /// First sample to play on the wavebuffer. + /// + public uint StartSampleOffset; + + /// + /// Last sample to play on the wavebuffer. + /// + public uint EndSampleOffset; + + /// + /// First sample to play when looping the wavebuffer. + /// + /// + /// If or is equal to zero,, it will default to and . + /// + public uint LoopStartSampleOffset; + + /// + /// Last sample to play when looping the wavebuffer. + /// + /// + /// If or is equal to zero, it will default to and . + /// + public uint LoopEndSampleOffset; + + /// + /// The max loop count. + /// + public int LoopCount; + + /// + /// Set to true if the wavebuffer is looping. + /// + [MarshalAs(UnmanagedType.I1)] + public bool Looping; + + /// + /// Set to true if the wavebuffer is the end of stream. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Padding/Reserved. + /// + private readonly ushort _padding; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs b/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs new file mode 100644 index 000000000..54673f2f6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs @@ -0,0 +1,61 @@ +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + public class WorkBufferAllocator + { + public Memory BackingMemory { get; } + + public ulong Offset { get; private set; } + + public WorkBufferAllocator(Memory backingMemory) + { + BackingMemory = backingMemory; + } + + public Memory Allocate(ulong size, int align) + { + Debug.Assert(align != 0); + + if (size != 0) + { + ulong alignedOffset = BitUtils.AlignUp(Offset, (ulong)align); + + if (alignedOffset + size <= (ulong)BackingMemory.Length) + { + Memory result = BackingMemory.Slice((int)alignedOffset, (int)size); + + Offset = alignedOffset + size; + + // Clear the memory to be sure that is does not contain any garbage. + result.Span.Clear(); + + return result; + } + } + + return Memory.Empty; + } + + public Memory Allocate(ulong count, int align) where T : unmanaged + { + Memory allocatedMemory = Allocate((ulong)Unsafe.SizeOf() * count, align); + + if (allocatedMemory.IsEmpty) + { + return Memory.Empty; + } + + return SpanMemoryManager.Cast(allocatedMemory); + } + + public static ulong GetTargetSize(ulong currentSize, ulong count, int align) where T : unmanaged + { + return BitUtils.AlignUp(currentSize, (ulong)align) + (ulong)Unsafe.SizeOf() * count; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs new file mode 100644 index 000000000..91956fda6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs @@ -0,0 +1,89 @@ +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Device +{ + /// + /// Represents a virtual device used by IAudioDevice. + /// + public class VirtualDevice + { + /// + /// All the defined virtual devices. + /// + public static readonly VirtualDevice[] Devices = new VirtualDevice[5] + { + new("AudioStereoJackOutput", 2, true), + new("AudioBuiltInSpeakerOutput", 2, false), + new("AudioTvOutput", 6, false), + new("AudioUsbDeviceOutput", 2, true), + new("AudioExternalOutput", 6, true), + }; + + /// + /// The name of the . + /// + public string Name { get; } + + /// + /// The count of channels supported by the . + /// + public uint ChannelCount { get; } + + /// + /// The system master volume of the . + /// + public float MasterVolume { get; private set; } + + /// + /// Define if the is provided by an external interface. + /// + public bool IsExternalOutput { get; } + + /// + /// Create a new instance. + /// + /// The name of the . + /// The count of channels supported by the . + /// Indicate if the is provided by an external interface. + public VirtualDevice(string name, uint channelCount, bool isExternalOutput) + { + Name = name; + ChannelCount = channelCount; + IsExternalOutput = isExternalOutput; + } + + /// + /// Update the master volume of the . + /// + /// The new master volume. + public void UpdateMasterVolume(float volume) + { + Debug.Assert(volume >= 0.0f && volume <= 1.0f); + + MasterVolume = volume; + } + + /// + /// Check if the is a usb device. + /// + /// Returns true if the is a usb device. + public bool IsUsbDevice() + { + return Name.Equals("AudioUsbDeviceOutput"); + } + + /// + /// Get the output device name of the . + /// + /// The output device name of the . + public string GetOutputDeviceName() + { + if (IsExternalOutput) + { + return "AudioExternalOutput"; + } + + return Name; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs new file mode 100644 index 000000000..09fa71eda --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Audio.Renderer.Device +{ + /// + /// Represents a virtual device session used by IAudioDevice. + /// + public class VirtualDeviceSession + { + /// + /// The associated to this session. + /// + public VirtualDevice Device { get; } + + /// + /// The user volume of this session. + /// + public float Volume { get; set; } + + /// + /// Create a new instance. + /// + /// The associated to this session. + public VirtualDeviceSession(VirtualDevice virtualDevice) + { + Device = virtualDevice; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs new file mode 100644 index 000000000..4ad70619e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs @@ -0,0 +1,81 @@ +using Ryujinx.Audio.Integration; +using System.Collections.Generic; + +namespace Ryujinx.Audio.Renderer.Device +{ + /// + /// Represent an instance containing a registry of . + /// + public class VirtualDeviceSessionRegistry + { + /// + /// The session registry, used to store the sessions of a given AppletResourceId. + /// + private readonly Dictionary _sessionsRegistry = new(); + + /// + /// The default . + /// + /// This is used when the USB device is the default one on older revision. +#pragma warning disable CA1822 // Mark member as static + public VirtualDevice DefaultDevice => VirtualDevice.Devices[0]; +#pragma warning restore CA1822 + + /// + /// The current active . + /// + // TODO: make this configurable + public VirtualDevice ActiveDevice { get; } + + public VirtualDeviceSessionRegistry(IHardwareDeviceDriver driver) + { + uint channelCount; + + if (driver.GetRealDeviceDriver().SupportsChannelCount(6)) + { + channelCount = 6; + } + else + { + channelCount = 2; + } + + ActiveDevice = new VirtualDevice("AudioTvOutput", channelCount, false); + } + + /// + /// Get the associated from an AppletResourceId. + /// + /// The AppletResourceId used. + /// The associated from an AppletResourceId. + public VirtualDeviceSession[] GetSessionByAppletResourceId(ulong resourceAppletId) + { + if (_sessionsRegistry.TryGetValue(resourceAppletId, out VirtualDeviceSession[] result)) + { + return result; + } + + result = CreateSessionsFromBehaviourContext(); + + _sessionsRegistry.Add(resourceAppletId, result); + + return result; + } + + /// + /// Create a new array of sessions for each . + /// + /// A new array of sessions for each . + 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; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs new file mode 100644 index 000000000..5cb4509ff --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs @@ -0,0 +1,222 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class AdpcmHelper + { + private const int FixedPointPrecision = 11; + private const int SamplesPerFrame = 14; + private const int NibblesPerFrame = SamplesPerFrame + 2; + private const int BytesPerFrame = 8; +#pragma warning disable IDE0051 // Remove unused private member + private const int BitsPerFrame = BytesPerFrame * 8; +#pragma warning restore IDE0051 + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetAdpcmDataSize(int sampleCount) + { + Debug.Assert(sampleCount >= 0); + + int frames = sampleCount / SamplesPerFrame; + int extraSize = 0; + + if ((sampleCount % SamplesPerFrame) != 0) + { + extraSize = (sampleCount % SamplesPerFrame) / 2 + 1 + (sampleCount % 2); + } + + return (uint)(BytesPerFrame * frames + extraSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetAdpcmOffsetFromSampleOffset(int sampleOffset) + { + Debug.Assert(sampleOffset >= 0); + + return GetNibblesFromSampleCount(sampleOffset) / 2; + } + + public static int NibbleToSample(int nibble) + { + int frames = nibble / NibblesPerFrame; + int extraNibbles = nibble % NibblesPerFrame; + int samples = SamplesPerFrame * frames; + + return samples + extraNibbles - 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetNibblesFromSampleCount(int sampleCount) + { + byte headerSize = 0; + + if ((sampleCount % SamplesPerFrame) != 0) + { + headerSize = 2; + } + + return sampleCount % SamplesPerFrame + NibblesPerFrame * (sampleCount / SamplesPerFrame) + headerSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short Saturate(int value) + { + if (value > short.MaxValue) + { + value = short.MaxValue; + } + + if (value < short.MinValue) + { + value = short.MinValue; + } + + return (short)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short GetCoefficientAtIndex(ReadOnlySpan coefficients, int index) + { + if ((uint)index > (uint)coefficients.Length) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}"); + + return 0; + } + + return coefficients[index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan coefficients, ref AdpcmLoopContext loopContext) + { + if (input.IsEmpty || endSampleOffset < startSampleOffset) + { + return 0; + } + + byte predScale = (byte)loopContext.PredScale; + byte scale = (byte)(predScale & 0xF); + byte coefficientIndex = (byte)((predScale >> 4) & 0xF); + short history0 = loopContext.History0; + short history1 = loopContext.History1; + short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0); + short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1); + + int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset); + int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset); + int remaining = decodedCount; + int outputBufferIndex = 0; + int inputIndex = 0; + + ReadOnlySpan targetInput; + + targetInput = input[(nibbles / 2)..]; + + while (remaining > 0) + { + int samplesCount; + + if (((uint)nibbles % NibblesPerFrame) == 0) + { + predScale = targetInput[inputIndex++]; + + scale = (byte)(predScale & 0xF); + + coefficientIndex = (byte)((predScale >> 4) & 0xF); + + coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2); + coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1); + + nibbles += 2; + + samplesCount = Math.Min(remaining, SamplesPerFrame); + } + else + { + samplesCount = 1; + } + + int scaleFixedPoint = FixedPointHelper.ToFixed(1.0f, FixedPointPrecision) << scale; + + if (samplesCount < SamplesPerFrame) + { + for (int i = 0; i < samplesCount; i++) + { + int value = targetInput[inputIndex]; + + int sample; + + if ((nibbles & 1) != 0) + { + sample = (value << 28) >> 28; + + inputIndex++; + } + else + { + sample = (value << 24) >> 28; + } + + nibbles++; + + int prediction = coefficient0 * history0 + coefficient1 * history1; + + sample = FixedPointHelper.RoundUpAndToInt(sample * scaleFixedPoint + prediction, FixedPointPrecision); + + short saturatedSample = Saturate(sample); + + history1 = history0; + history0 = saturatedSample; + + output[outputBufferIndex++] = saturatedSample; + + remaining--; + } + } + else + { + for (int i = 0; i < SamplesPerFrame / 2; i++) + { + int value = targetInput[inputIndex]; + + int sample0; + int sample1; + + sample0 = (value << 24) >> 28; + sample1 = (value << 28) >> 28; + + inputIndex++; + + int prediction0 = coefficient0 * history0 + coefficient1 * history1; + sample0 = FixedPointHelper.RoundUpAndToInt(sample0 * scaleFixedPoint + prediction0, FixedPointPrecision); + short saturatedSample0 = Saturate(sample0); + + int prediction1 = coefficient0 * saturatedSample0 + coefficient1 * history0; + sample1 = FixedPointHelper.RoundUpAndToInt(sample1 * scaleFixedPoint + prediction1, FixedPointPrecision); + short saturatedSample1 = Saturate(sample1); + + history1 = saturatedSample0; + history0 = saturatedSample1; + + output[outputBufferIndex++] = saturatedSample0; + output[outputBufferIndex++] = saturatedSample1; + } + + nibbles += SamplesPerFrame; + remaining -= SamplesPerFrame; + } + } + + loopContext.PredScale = predScale; + loopContext.History0 = history0; + loopContext.History1 = history1; + + return decodedCount; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs new file mode 100644 index 000000000..9c885b2cf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs @@ -0,0 +1,275 @@ +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 _mailbox; + private RendererSession[] _sessionCommandList; + private Thread _workerThread; + + public IHardwareDevice[] OutputDevices { get; private set; } + + private long _lastTime; + private long _playbackEnds; + private readonly ManualResetEvent _event; + + private ManualResetEvent _pauseEvent; + + public AudioProcessor() + { + _event = new ManualResetEvent(false); + } + +#pragma warning disable IDE0051 // Remove unused private member + 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; + } + + // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible. + return 2; + } +#pragma warning restore IDE0051 + + public void Start(IHardwareDeviceDriver deviceDriver, float volume) + { + OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax]; + + uint channelCount = GetHardwareChannelCount(deviceDriver); + + 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(); + _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() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _event.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs new file mode 100644 index 000000000..1a51a1fbd --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs @@ -0,0 +1,83 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class BiquadFilterHelper + { + private const int FixedPointPrecisionForParameter = 14; + + /// + /// Apply a single biquad filter. + /// + /// This is implemented with a direct form 2. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to write the result + /// The count of samples to process + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount) + { + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.State0; + + state.State0 = input * a1 + output * b1 + state.State1; + state.State1 = input * a2 + output * b2; + + outputBuffer[i] = output; + } + } + + /// + /// Apply multiple biquad filter. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to write the result + /// The count of samples to process + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessBiquadFilter(ReadOnlySpan parameters, Span states, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount) + { + for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++) + { + BiquadFilterParameter parameter = parameters[stageIndex]; + + ref BiquadFilterState state = ref states[stageIndex]; + + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2; + + state.State1 = state.State0; + state.State0 = input; + state.State3 = state.State2; + state.State2 = output; + + outputBuffer[i] = output; + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs new file mode 100644 index 000000000..51a12b4e7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs @@ -0,0 +1,77 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; +using WaveBuffer = Ryujinx.Audio.Renderer.Common.WaveBuffer; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class AdpcmDataSourceCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.AdpcmDataSourceVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + + public ulong AdpcmParameter { get; } + public ulong AdpcmParameterSize { get; } + + public DecodingBehaviour DecodingBehaviour { get; } + + public AdpcmDataSourceCommandVersion1(ref VoiceState serverState, Memory state, ushort outputBufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + OutputBufferIndex = outputBufferIndex; + SampleRate = serverState.SampleRate; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); + } + + AdpcmParameter = serverState.DataSourceStateAddressInfo.GetReference(true); + AdpcmParameterSize = serverState.DataSourceStateAddressInfo.Size; + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new() + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.Adpcm, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = AdpcmParameter, + ExtraParameterSize = AdpcmParameterSize, + ChannelIndex = 0, + ChannelCount = 1, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs new file mode 100644 index 000000000..73d66dcf4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs @@ -0,0 +1,182 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class AuxiliaryBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.AuxiliaryBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public uint InputBufferIndex { get; } + public uint OutputBufferIndex { get; } + + public AuxiliaryBufferAddresses BufferInfo { get; } + + public CpuAddress InputBuffer { get; } + public CpuAddress OutputBuffer { get; } + public uint CountMax { get; } + public uint UpdateCount { get; } + public uint WriteOffset { get; } + + public bool IsEffectEnabled { get; } + + public AuxiliaryBufferCommand( + uint bufferOffset, + byte inputBufferOffset, + byte outputBufferOffset, + ref AuxiliaryBufferAddresses sendBufferInfo, + bool isEnabled, + uint countMax, + CpuAddress outputBuffer, + CpuAddress inputBuffer, + uint updateCount, + uint writeOffset, + int nodeId) + { + Enabled = true; + NodeId = nodeId; + InputBufferIndex = bufferOffset + inputBufferOffset; + OutputBufferIndex = bufferOffset + outputBufferOffset; + BufferInfo = sendBufferInfo; + InputBuffer = inputBuffer; + OutputBuffer = outputBuffer; + CountMax = countMax; + UpdateCount = updateCount; + WriteOffset = writeOffset; + IsEffectEnabled = isEnabled; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Read(IVirtualMemoryManager memoryManager, ulong bufferAddress, uint countMax, Span outBuffer, uint count, uint readOffset, uint updateCount) + { + if (countMax == 0 || bufferAddress == 0) + { + return 0; + } + + uint targetReadOffset = readOffset + AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo); + + if (targetReadOffset > countMax) + { + return 0; + } + + uint remaining = count; + + uint outBufferOffset = 0; + + while (remaining != 0) + { + uint countToWrite = Math.Min(countMax - targetReadOffset, remaining); + + memoryManager.Read(bufferAddress + targetReadOffset * sizeof(int), MemoryMarshal.Cast(outBuffer.Slice((int)outBufferOffset, (int)countToWrite))); + + targetReadOffset = (targetReadOffset + countToWrite) % countMax; + remaining -= countToWrite; + outBufferOffset += countToWrite; + } + + if (updateCount != 0) + { + uint newReadOffset = (AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo) + updateCount) % countMax; + + AuxiliaryBufferInfo.SetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo, newReadOffset); + } + + return count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan buffer, uint count, uint writeOffset, uint updateCount) + { + if (countMax == 0 || outBufferAddress == 0) + { + return 0; + } + + uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo); + + if (targetWriteOffset > countMax) + { + return 0; + } + + uint remaining = count; + + uint inBufferOffset = 0; + + while (remaining != 0) + { + uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining); + + memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast(buffer.Slice((int)inBufferOffset, (int)countToWrite))); + + targetWriteOffset = (targetWriteOffset + countToWrite) % countMax; + remaining -= countToWrite; + inBufferOffset += countToWrite; + } + + if (updateCount != 0) + { + uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo) + updateCount) % countMax; + + AuxiliaryBufferInfo.SetWriteOffset(memoryManager, BufferInfo.SendBufferInfo, newWriteOffset); + } + + return count; + } + + public void Process(CommandList context) + { + Span inputBuffer = context.GetBuffer((int)InputBufferIndex); + Span outputBuffer = context.GetBuffer((int)OutputBufferIndex); + + if (IsEffectEnabled) + { + Span inputBufferInt = MemoryMarshal.Cast(inputBuffer); + Span outputBufferInt = MemoryMarshal.Cast(outputBuffer); + + // Convert input data to the target format for user (int) + DataSourceHelper.ToInt(inputBufferInt, inputBuffer, inputBuffer.Length); + + // Send the input to the user + Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount); + + // Convert back to float just in case it's reused + DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length); + + // Retrieve the input from user + uint readResult = Read(context.MemoryManager, InputBuffer, CountMax, outputBufferInt, context.SampleCount, WriteOffset, UpdateCount); + + // Convert the outputBuffer back to the target format of the renderer (float) + DataSourceHelper.ToFloat(outputBuffer, outputBufferInt, outputBuffer.Length); + + if (readResult != context.SampleCount) + { + outputBuffer[(int)readResult..(int)context.SampleCount].Clear(); + } + } + else + { + AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.SendBufferInfo); + AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.ReturnBufferInfo); + + if (InputBufferIndex != OutputBufferIndex) + { + inputBuffer.CopyTo(outputBuffer); + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs new file mode 100644 index 000000000..ac1e581f6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs @@ -0,0 +1,58 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class BiquadFilterCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.BiquadFilter; + + public uint EstimatedProcessingTime { get; set; } + + public Memory BiquadFilterState { get; } + public int InputBufferIndex { get; } + public int OutputBufferIndex { get; } + public bool NeedInitialization { get; } + + private BiquadFilterParameter _parameter; + + public BiquadFilterCommand( + int baseIndex, + ref BiquadFilterParameter filter, + Memory biquadFilterStateMemory, + int inputBufferOffset, + int outputBufferOffset, + bool needInitialization, + int nodeId) + { + _parameter = filter; + BiquadFilterState = biquadFilterStateMemory; + InputBufferIndex = baseIndex + inputBufferOffset; + OutputBufferIndex = baseIndex + outputBufferOffset; + NeedInitialization = needInitialization; + + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + ref BiquadFilterState state = ref BiquadFilterState.Span[0]; + + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + if (NeedInitialization) + { + state = new BiquadFilterState(); + } + + BiquadFilterHelper.ProcessBiquadFilter(ref _parameter, ref state, outputBuffer, inputBuffer, context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs new file mode 100644 index 000000000..01bff1e7d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs @@ -0,0 +1,136 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CaptureBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.CaptureBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public uint InputBufferIndex { get; } + + public ulong CpuBufferInfoAddress { get; } + public ulong DspBufferInfoAddress { get; } + + public CpuAddress OutputBuffer { get; } + public uint CountMax { get; } + public uint UpdateCount { get; } + public uint WriteOffset { get; } + + public bool IsEffectEnabled { get; } + + public CaptureBufferCommand(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled, + uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + Enabled = true; + NodeId = nodeId; + InputBufferIndex = bufferOffset + inputBufferOffset; + CpuBufferInfoAddress = sendBufferInfo; + DspBufferInfoAddress = sendBufferInfo + (ulong)Unsafe.SizeOf(); + OutputBuffer = outputBuffer; + CountMax = countMax; + UpdateCount = updateCount; + WriteOffset = writeOffset; + IsEffectEnabled = isEnabled; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan buffer, uint count, uint writeOffset, uint updateCount) + { + if (countMax == 0 || outBufferAddress == 0) + { + return 0; + } + + uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress); + + if (targetWriteOffset > countMax) + { + return 0; + } + + uint remaining = count; + + uint inBufferOffset = 0; + + while (remaining != 0) + { + uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining); + + memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast(buffer.Slice((int)inBufferOffset, (int)countToWrite))); + + targetWriteOffset = (targetWriteOffset + countToWrite) % countMax; + remaining -= countToWrite; + inBufferOffset += countToWrite; + } + + if (updateCount != 0) + { + uint dspTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, DspBufferInfoAddress); + uint cpuTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, CpuBufferInfoAddress); + + uint totalSampleCountDiff = dspTotalSampleCount - cpuTotalSampleCount; + + if (totalSampleCountDiff >= countMax) + { + uint dspLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, DspBufferInfoAddress); + uint cpuLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, CpuBufferInfoAddress); + + uint lostSampleCountDiff = dspLostSampleCount - cpuLostSampleCount; + uint newLostSampleCount = lostSampleCountDiff + updateCount; + + if (lostSampleCountDiff > newLostSampleCount) + { + newLostSampleCount = cpuLostSampleCount - 1; + } + + AuxiliaryBufferInfo.SetLostSampleCount(memoryManager, DspBufferInfoAddress, newLostSampleCount); + } + + uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress) + updateCount) % countMax; + + AuxiliaryBufferInfo.SetWriteOffset(memoryManager, DspBufferInfoAddress, newWriteOffset); + + uint newTotalSampleCount = totalSampleCountDiff + newWriteOffset; + + AuxiliaryBufferInfo.SetTotalSampleCount(memoryManager, DspBufferInfoAddress, newTotalSampleCount); + } + + return count; + } + + public void Process(CommandList context) + { + Span inputBuffer = context.GetBuffer((int)InputBufferIndex); + + if (IsEffectEnabled) + { + Span inputBufferInt = MemoryMarshal.Cast(inputBuffer); + + // Convert input data to the target format for user (int) + DataSourceHelper.ToInt(inputBufferInt, inputBuffer, inputBuffer.Length); + + // Send the input to the user + Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount); + + // Convert back to float + DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length); + } + else + { + AuxiliaryBufferInfo.Reset(context.MemoryManager, DspBufferInfoAddress); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs new file mode 100644 index 000000000..59ef70932 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CircularBufferSinkCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.CircularBufferSink; + + public uint EstimatedProcessingTime { get; set; } + + public ushort[] Input { get; } + public uint InputCount { get; } + + public ulong CircularBuffer { get; } + public ulong CircularBufferSize { get; } + public ulong CurrentOffset { get; } + + public CircularBufferSinkCommand(uint bufferOffset, ref CircularBufferParameter parameter, ref AddressInfo circularBufferAddressInfo, uint currentOffset, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + Input = new ushort[Constants.ChannelCountMax]; + InputCount = parameter.InputCount; + + for (int i = 0; i < InputCount; i++) + { + Input[i] = (ushort)(bufferOffset + parameter.Input[i]); + } + + CircularBuffer = circularBufferAddressInfo.GetReference(true); + CircularBufferSize = parameter.BufferSize; + CurrentOffset = currentOffset; + + Debug.Assert(CircularBuffer != 0); + } + + public void Process(CommandList context) + { + const int TargetChannelCount = 2; + + ulong currentOffset = CurrentOffset; + + if (CircularBufferSize > 0) + { + for (int i = 0; i < InputCount; i++) + { + unsafe + { + float* inputBuffer = (float*)context.GetBufferPointer(Input[i]); + + ulong targetOffset = CircularBuffer + currentOffset; + + for (int y = 0; y < context.SampleCount; y++) + { + context.MemoryManager.Write(targetOffset + (ulong)y * TargetChannelCount, PcmHelper.Saturate(inputBuffer[y])); + } + + currentOffset += context.SampleCount * TargetChannelCount; + + if (currentOffset >= CircularBufferSize) + { + currentOffset = 0; + } + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs new file mode 100644 index 000000000..f0f85b0b2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class ClearMixBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.ClearMixBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public ClearMixBufferCommand(int nodeId) + { + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + context.ClearBuffers(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs new file mode 100644 index 000000000..3fe106ddf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs @@ -0,0 +1,156 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CommandList : IDisposable + { + public ulong StartTime { get; private set; } + public ulong EndTime { get; private set; } + public uint SampleCount { get; } + public uint SampleRate { get; } + + public Memory Buffers { get; } + public uint BufferCount { get; } + + public List Commands { get; } + + public IVirtualMemoryManager MemoryManager { get; } + + public IHardwareDevice OutputDevice { get; private set; } + + private readonly int _sampleCount; + private readonly int _buffersEntryCount; + private readonly MemoryHandle _buffersMemoryHandle; + + public CommandList(AudioRenderSystem renderSystem) : this(renderSystem.MemoryManager, + renderSystem.GetMixBuffer(), + renderSystem.GetSampleCount(), + renderSystem.GetSampleRate(), + renderSystem.GetMixBufferCount(), + renderSystem.GetVoiceChannelCountMax()) + { + } + + public CommandList(IVirtualMemoryManager memoryManager, Memory mixBuffer, uint sampleCount, uint sampleRate, uint mixBufferCount, uint voiceChannelCountMax) + { + SampleCount = sampleCount; + _sampleCount = (int)SampleCount; + SampleRate = sampleRate; + BufferCount = mixBufferCount + voiceChannelCountMax; + Buffers = mixBuffer; + Commands = new List(); + MemoryManager = memoryManager; + + _buffersEntryCount = Buffers.Length; + _buffersMemoryHandle = Buffers.Pin(); + } + + public void AddCommand(ICommand command) + { + Commands.Add(command); + } + + public void AddCommand(T command) where T : unmanaged, ICommand + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe IntPtr GetBufferPointer(int index) + { + if (index >= 0 && index < _buffersEntryCount) + { + return (IntPtr)((float*)_buffersMemoryHandle.Pointer + index * _sampleCount); + } + + throw new ArgumentOutOfRangeException(nameof(index), index, null); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ClearBuffer(int index) + { + Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount * sizeof(float)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ClearBuffers() + { + Unsafe.InitBlock(_buffersMemoryHandle.Pointer, 0, (uint)_buffersEntryCount * sizeof(float)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void CopyBuffer(int outputBufferIndex, int inputBufferIndex) + { + Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount * sizeof(float)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetBuffer(int index) + { + if (index < 0 || index >= _buffersEntryCount) + { + return Span.Empty; + } + + unsafe + { + return new Span((float*)_buffersMemoryHandle.Pointer + index * _sampleCount, _sampleCount); + } + } + + public ulong GetTimeElapsedSinceDspStartedProcessing() + { + return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime; + } + + public void Process(IHardwareDevice outputDevice) + { + OutputDevice = outputDevice; + + StartTime = (ulong)PerformanceCounter.ElapsedNanoseconds; + + foreach (ICommand command in Commands) + { + if (command.Enabled) + { + bool shouldMeter = command.ShouldMeter(); + + long startTime = 0; + + if (shouldMeter) + { + startTime = PerformanceCounter.ElapsedNanoseconds; + } + + command.Process(this); + + if (shouldMeter) + { + ulong effectiveElapsedTime = (ulong)(PerformanceCounter.ElapsedNanoseconds - startTime); + + if (effectiveElapsedTime > command.EstimatedProcessingTime) + { + Logger.Warning?.Print(LogClass.AudioRenderer, $"Command {command.GetType().Name} took {effectiveElapsedTime}ns (expected {command.EstimatedProcessingTime}ns)"); + } + } + } + } + + EndTime = (ulong)PerformanceCounter.ElapsedNanoseconds; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _buffersMemoryHandle.Dispose(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs new file mode 100644 index 000000000..098a04a04 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public enum CommandType : byte + { + Invalid, + PcmInt16DataSourceVersion1, + PcmInt16DataSourceVersion2, + PcmFloatDataSourceVersion1, + PcmFloatDataSourceVersion2, + AdpcmDataSourceVersion1, + AdpcmDataSourceVersion2, + Volume, + VolumeRamp, + BiquadFilter, + Mix, + MixRamp, + MixRampGrouped, + DepopPrepare, + DepopForMixBuffers, + Delay, + Upsample, + DownMixSurroundToStereo, + AuxiliaryBuffer, + DeviceSink, + CircularBufferSink, + Reverb, + Reverb3d, + Performance, + ClearMixBuffer, + CopyMixBuffer, + LimiterVersion1, + LimiterVersion2, + GroupedBiquadFilter, + CaptureBuffer, + Compressor, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs new file mode 100644 index 000000000..09f415d20 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs @@ -0,0 +1,174 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; + +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 State { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private CompressorParameter _parameter; + + public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory 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 == UsageState.Invalid) + { + state = new CompressorState(ref _parameter); + } + else if (_parameter.Status == 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 inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span 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 = 1.0f; + + bool unknown10OutOfRange = y >= state.Unknown10; + + if (newMean < 1.0e-10f) + { + y = -100.0f; + + unknown10OutOfRange = state.Unknown10 <= -100.0f; + } + + if (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.DecibelToLinear(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]); + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs new file mode 100644 index 000000000..3f6aa8390 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CopyMixBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.CopyMixBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public CopyMixBufferCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + } + + public void Process(CommandList context) + { + context.CopyBuffer(OutputBufferIndex, InputBufferIndex); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs new file mode 100644 index 000000000..e82d403bf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs @@ -0,0 +1,106 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; +using WaveBuffer = Ryujinx.Audio.Renderer.Common.WaveBuffer; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DataSourceVersion2Command : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType { get; } + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + + public ulong ExtraParameter { get; } + public ulong ExtraParameterSize { get; } + + public uint ChannelIndex { get; } + + public uint ChannelCount { get; } + + public DecodingBehaviour DecodingBehaviour { get; } + + public SampleFormat SampleFormat { get; } + + public SampleRateConversionQuality SrcQuality { get; } + + public DataSourceVersion2Command(ref VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + ChannelIndex = channelIndex; + ChannelCount = serverState.ChannelsCount; + SampleFormat = serverState.SampleFormat; + SrcQuality = serverState.SrcQuality; + CommandType = GetCommandTypeBySampleFormat(SampleFormat); + + OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex); + SampleRate = serverState.SampleRate; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(2); + } + + if (SampleFormat == SampleFormat.Adpcm) + { + ExtraParameter = serverState.DataSourceStateAddressInfo.GetReference(true); + ExtraParameterSize = serverState.DataSourceStateAddressInfo.Size; + } + + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + private static CommandType GetCommandTypeBySampleFormat(SampleFormat sampleFormat) + { + return sampleFormat switch + { + SampleFormat.Adpcm => CommandType.AdpcmDataSourceVersion2, + SampleFormat.PcmInt16 => CommandType.PcmInt16DataSourceVersion2, + SampleFormat.PcmFloat => CommandType.PcmFloatDataSourceVersion2, + _ => throw new NotImplementedException($"{sampleFormat}"), + }; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new() + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = ExtraParameter, + ExtraParameterSize = ExtraParameterSize, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + SrcQuality = SrcQuality, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs new file mode 100644 index 000000000..6fa3777f4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs @@ -0,0 +1,280 @@ +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 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 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, Parameter.ChannelCount); + DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices, Parameter.ChannelCount); + } + + [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 outputBuffers, ReadOnlySpan 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(delayFeedbackBaseGain, delayFeedbackCrossGain, + delayFeedbackCrossGain, delayFeedbackBaseGain); + + for (int i = 0; i < sampleCount; i++) + { + Vector2 channelInput = new() + { + X = *((float*)inputBuffers[0] + i) * 64, + Y = *((float*)inputBuffers[1] + i) * 64, + }; + + Vector2 delayLineValues = new() + { + 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(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 outputBuffers, ReadOnlySpan 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(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() + { + 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() + { + 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(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 outputBuffers, ReadOnlySpan 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(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() + { + 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() + { + 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(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 inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span 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); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs new file mode 100644 index 000000000..ff38f38ca --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs @@ -0,0 +1,90 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DepopForMixBuffersCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DepopForMixBuffers; + + public uint EstimatedProcessingTime { get; set; } + + public uint MixBufferOffset { get; } + + public uint MixBufferCount { get; } + + public float Decay { get; } + + public Memory DepopBuffer { get; } + + public DepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint mixBufferCount, int nodeId, uint sampleRate) + { + Enabled = true; + NodeId = nodeId; + MixBufferOffset = bufferOffset; + MixBufferCount = mixBufferCount; + DepopBuffer = depopBuffer; + + if (sampleRate == 48000) + { + Decay = 0.962189f; + } + else // if (sampleRate == 32000) + { + Decay = 0.943695f; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe float ProcessDepopMix(float* buffer, float depopValue, uint sampleCount) + { + if (depopValue < 0) + { + depopValue = -depopValue; + + for (int i = 0; i < sampleCount; i++) + { + depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue); + + buffer[i] -= depopValue; + } + + return -depopValue; + } + + for (int i = 0; i < sampleCount; i++) + { + depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue); + + buffer[i] += depopValue; + } + + return depopValue; + } + + public void Process(CommandList context) + { + Span depopBuffer = DepopBuffer.Span; + + uint bufferCount = Math.Min(MixBufferOffset + MixBufferCount, context.BufferCount); + + for (int i = (int)MixBufferOffset; i < bufferCount; i++) + { + float depopValue = depopBuffer[i]; + if (depopValue != 0) + { + unsafe + { + float* buffer = (float*)context.GetBufferPointer(i); + + depopBuffer[i] = ProcessDepopMix(buffer, depopValue, context.SampleCount); + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs new file mode 100644 index 000000000..c64bbdc57 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs @@ -0,0 +1,57 @@ +using Ryujinx.Audio.Renderer.Common; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DepopPrepareCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DepopPrepare; + + public uint EstimatedProcessingTime { get; set; } + + public uint MixBufferCount { get; } + + public ushort[] OutputBufferIndices { get; } + + public Memory State { get; } + public Memory DepopBuffer { get; } + + public DepopPrepareCommand(Memory state, Memory depopBuffer, uint mixBufferCount, uint bufferOffset, int nodeId, bool enabled) + { + Enabled = enabled; + NodeId = nodeId; + MixBufferCount = mixBufferCount; + + OutputBufferIndices = new ushort[Constants.MixBufferCountMax]; + + for (int i = 0; i < Constants.MixBufferCountMax; i++) + { + OutputBufferIndices[i] = (ushort)(bufferOffset + i); + } + + State = state; + DepopBuffer = depopBuffer; + } + + public void Process(CommandList context) + { + ref VoiceUpdateState state = ref State.Span[0]; + + Span depopBuffer = DepopBuffer.Span; + + for (int i = 0; i < MixBufferCount; i++) + { + if (state.LastSamples[i] != 0) + { + depopBuffer[OutputBufferIndices[i]] += state.LastSamples[i]; + + state.LastSamples[i] = 0; + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs new file mode 100644 index 000000000..19afc66f4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs @@ -0,0 +1,103 @@ +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 Buffers { get; } + + public DeviceSinkCommand(uint bufferOffset, DeviceSink sink, int sessionId, Memory 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 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; + + uint inputCount; + + // In case of upmixing to 5.1, we allocate the right amount. + if (bufferCount != channelCount && channelCount == 6) + { + inputCount = (uint)channelCount; + } + else + { + inputCount = bufferCount; + } + + short[] outputBuffer = new short[inputCount * SampleCount]; + + for (int i = 0; i < bufferCount; i++) + { + ReadOnlySpan 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(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs new file mode 100644 index 000000000..8997b0dbd --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DownMixSurroundToStereoCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DownMixSurroundToStereo; + + public uint EstimatedProcessingTime { get; set; } + + public ushort[] InputBufferIndices { get; } + public ushort[] OutputBufferIndices { get; } + + public float[] Coefficients { get; } + + public DownMixSurroundToStereoCommand(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, float[] downMixParameter, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Constants.VoiceChannelCountMax; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + outputBufferOffset[i]); + } + + Coefficients = downMixParameter; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float DownMixSurroundToStereo(ReadOnlySpan coefficients, float back, float lfe, float center, float front) + { + return FloatingPointHelper.RoundUp(coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front); + } + + public void Process(CommandList context) + { + ReadOnlySpan frontLeft = context.GetBuffer(InputBufferIndices[0]); + ReadOnlySpan frontRight = context.GetBuffer(InputBufferIndices[1]); + ReadOnlySpan frontCenter = context.GetBuffer(InputBufferIndices[2]); + ReadOnlySpan lowFrequency = context.GetBuffer(InputBufferIndices[3]); + ReadOnlySpan backLeft = context.GetBuffer(InputBufferIndices[4]); + ReadOnlySpan backRight = context.GetBuffer(InputBufferIndices[5]); + + Span stereoLeft = context.GetBuffer(OutputBufferIndices[0]); + Span stereoRight = context.GetBuffer(OutputBufferIndices[1]); + + for (int i = 0; i < context.SampleCount; i++) + { + stereoLeft[i] = DownMixSurroundToStereo(Coefficients, backLeft[i], lowFrequency[i], frontCenter[i], frontLeft[i]); + stereoRight[i] = DownMixSurroundToStereo(Coefficients, backRight[i], lowFrequency[i], frontCenter[i], frontRight[i]); + } + + context.ClearBuffer(OutputBufferIndices[2]); + context.ClearBuffer(OutputBufferIndices[3]); + context.ClearBuffer(OutputBufferIndices[4]); + context.ClearBuffer(OutputBufferIndices[5]); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs new file mode 100644 index 000000000..7af851bdc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class GroupedBiquadFilterCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.GroupedBiquadFilter; + + public uint EstimatedProcessingTime { get; set; } + + private readonly BiquadFilterParameter[] _parameters; + private readonly Memory _biquadFilterStates; + private readonly int _inputBufferIndex; + private readonly int _outputBufferIndex; + private readonly bool[] _isInitialized; + + public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) + { + _parameters = filters.ToArray(); + _biquadFilterStates = biquadFilterStateMemory; + _inputBufferIndex = baseIndex + inputBufferOffset; + _outputBufferIndex = baseIndex + outputBufferOffset; + _isInitialized = isInitialized.ToArray(); + + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + Span states = _biquadFilterStates.Span; + + ReadOnlySpan inputBuffer = context.GetBuffer(_inputBufferIndex); + Span outputBuffer = context.GetBuffer(_outputBufferIndex); + + for (int i = 0; i < _parameters.Length; i++) + { + if (!_isInitialized[i]) + { + states[i] = new BiquadFilterState(); + } + } + + // NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done. + // As such we currently only implement a generic path for simplicity for double biquad. + if (_parameters.Length == 1) + { + BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount); + } + else + { + BiquadFilterHelper.ProcessBiquadFilter(_parameters, states, outputBuffer, inputBuffer, context.SampleCount); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs new file mode 100644 index 000000000..34a62c58d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public interface ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType { get; } + + public uint EstimatedProcessingTime { get; } + + public void Process(CommandList context); + + public bool ShouldMeter() + { + return false; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs new file mode 100644 index 000000000..3ba0b5884 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs @@ -0,0 +1,145 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class LimiterCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.LimiterVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public LimiterParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private LimiterParameter _parameter; + + public LimiterCommandVersion1(uint bufferOffset, LimiterParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId) + { + 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]); + } + } + + public void Process(CommandList context) + { + ref LimiterState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == UsageState.Invalid) + { + state = new LimiterState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == UsageState.New) + { + LimiterState.UpdateParameter(ref _parameter); + } + } + + ProcessLimiter(context, ref state); + } + + private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span 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]); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex); + + float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain; + + float sampleInputMax = Math.Abs(inputSample); + + float inputCoefficient = Parameter.ReleaseCoefficient; + + if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) + { + inputCoefficient = Parameter.AttackCoefficient; + } + + float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); + float attenuation = 1.0f; + + if (detectorValue > Parameter.Threshold) + { + attenuation = Parameter.Threshold / detectorValue; + } + + float outputCoefficient = Parameter.ReleaseCoefficient; + + if (state.CompressionGainAverage[channelIndex].Read() > attenuation) + { + outputCoefficient = Parameter.AttackCoefficient; + } + + float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); + + ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; + + float outputSample = delayedSample * compressionGain * Parameter.OutputGain; + + *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue; + + delayedSample = inputSample; + + state.DelayedSampleBufferPosition[channelIndex]++; + + while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) + { + state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; + } + } + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs new file mode 100644 index 000000000..f6e1654dd --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs @@ -0,0 +1,171 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class LimiterCommandVersion2 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.LimiterVersion2; + + public uint EstimatedProcessingTime { get; set; } + + public LimiterParameter Parameter => _parameter; + public Memory State { get; } + public Memory ResultState { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private LimiterParameter _parameter; + + public LimiterCommandVersion2( + uint bufferOffset, + LimiterParameter parameter, + Memory state, + Memory resultState, + bool isEnabled, + ulong workBuffer, + int nodeId) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + ResultState = resultState; + 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]); + } + } + + public void Process(CommandList context) + { + ref LimiterState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == UsageState.Invalid) + { + state = new LimiterState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == UsageState.New) + { + LimiterState.UpdateParameter(ref _parameter); + } + } + + ProcessLimiter(context, ref state); + } + + private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + if (!ResultState.IsEmpty && Parameter.StatisticsReset) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0]; + + statistics.Reset(); + } + + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span 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]); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex); + + float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain; + + float sampleInputMax = Math.Abs(inputSample); + + float inputCoefficient = Parameter.ReleaseCoefficient; + + if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) + { + inputCoefficient = Parameter.AttackCoefficient; + } + + float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); + float attenuation = 1.0f; + + if (detectorValue > Parameter.Threshold) + { + attenuation = Parameter.Threshold / detectorValue; + } + + float outputCoefficient = Parameter.ReleaseCoefficient; + + if (state.CompressionGainAverage[channelIndex].Read() > attenuation) + { + outputCoefficient = Parameter.AttackCoefficient; + } + + float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); + + ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; + + float outputSample = delayedSample * compressionGain * Parameter.OutputGain; + + *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue; + + delayedSample = inputSample; + + state.DelayedSampleBufferPosition[channelIndex]++; + + while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) + { + state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; + } + + if (!ResultState.IsEmpty) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0]; + + statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax); + statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain); + } + } + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs new file mode 100644 index 000000000..c701f80eb --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MixCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Mix; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume { get; } + + public MixCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + Volume = volume; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixAvx(Span outputMix, ReadOnlySpan inputMix) + { + Vector256 volumeVec = Vector256.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix); + Span> outputVec = MemoryMarshal.Cast>(outputMix); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.Add(outputVec[i], Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec))); + } + + for (int i = sisdStart; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixSse41(Span outputMix, ReadOnlySpan inputMix) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix); + Span> outputVec = MemoryMarshal.Cast>(outputMix); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse.Add(outputVec[i], Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec))); + } + + for (int i = sisdStart; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixAdvSimd(Span outputMix, ReadOnlySpan inputMix) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix); + Span> outputVec = MemoryMarshal.Cast>(outputMix); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.Add(outputVec[i], AdvSimd.Ceiling(AdvSimd.Multiply(inputVec[i], volumeVec))); + } + + for (int i = sisdStart; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixSlowPath(Span outputMix, ReadOnlySpan inputMix) + { + for (int i = 0; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMix(Span outputMix, ReadOnlySpan inputMix) + { + if (Avx.IsSupported) + { + ProcessMixAvx(outputMix, inputMix); + } + else if (Sse41.IsSupported) + { + ProcessMixSse41(outputMix, inputMix); + } + else if (AdvSimd.IsSupported) + { + ProcessMixAdvSimd(outputMix, inputMix); + } + else + { + ProcessMixSlowPath(outputMix, inputMix); + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessMix(outputBuffer, inputBuffer); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs new file mode 100644 index 000000000..f77a233e1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs @@ -0,0 +1,68 @@ +using Ryujinx.Audio.Renderer.Common; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MixRampCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MixRamp; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public Memory State { get; } + + public int LastSampleIndex { get; } + + public MixRampCommand(float volume0, float volume1, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory state, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + Volume0 = volume0; + Volume1 = volume1; + + State = state; + LastSampleIndex = lastSampleIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float ProcessMixRamp(Span outputBuffer, ReadOnlySpan inputBuffer, int sampleCount) + { + float ramp = (Volume1 - Volume0) / sampleCount; + float volume = Volume0; + float state = 0; + + for (int i = 0; i < sampleCount; i++) + { + state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + + outputBuffer[i] += state; + volume += ramp; + } + + return state; + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + State.Span[0].LastSamples[LastSampleIndex] = ProcessMixRamp(outputBuffer, inputBuffer, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs new file mode 100644 index 000000000..3c7dd63b2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs @@ -0,0 +1,91 @@ +using Ryujinx.Audio.Renderer.Common; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MixRampGroupedCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MixRampGrouped; + + public uint EstimatedProcessingTime { get; set; } + + public uint MixBufferCount { get; } + + public ushort[] InputBufferIndices { get; } + public ushort[] OutputBufferIndices { get; } + + public float[] Volume0 { get; } + public float[] Volume1 { get; } + + public Memory State { get; } + + public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span volume0, Span volume1, Memory state, int nodeId) + { + Enabled = true; + MixBufferCount = mixBufferCount; + NodeId = nodeId; + + InputBufferIndices = new ushort[Constants.MixBufferCountMax]; + OutputBufferIndices = new ushort[Constants.MixBufferCountMax]; + Volume0 = new float[Constants.MixBufferCountMax]; + Volume1 = new float[Constants.MixBufferCountMax]; + + for (int i = 0; i < mixBufferCount; i++) + { + InputBufferIndices[i] = (ushort)inputBufferIndex; + OutputBufferIndices[i] = (ushort)(outputBufferIndex + i); + + Volume0[i] = volume0[i]; + Volume1[i] = volume1[i]; + } + + State = state; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ProcessMixRampGrouped(Span outputBuffer, ReadOnlySpan inputBuffer, float volume0, float volume1, int sampleCount) + { + float ramp = (volume1 - volume0) / sampleCount; + float volume = volume0; + float state = 0; + + for (int i = 0; i < sampleCount; i++) + { + state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + + outputBuffer[i] += state; + volume += ramp; + } + + return state; + } + + public void Process(CommandList context) + { + for (int i = 0; i < MixBufferCount; i++) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndices[i]); + Span outputBuffer = context.GetBuffer(OutputBufferIndices[i]); + + float volume0 = Volume0[i]; + float volume1 = Volume1[i]; + + ref VoiceUpdateState state = ref State.Span[0]; + + if (volume0 != 0 || volume1 != 0) + { + state.LastSamples[i] = ProcessMixRampGrouped(outputBuffer, inputBuffer, volume0, volume1, (int)context.SampleCount); + } + else + { + state.LastSamples[i] = 0; + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs new file mode 100644 index 000000000..585edc058 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; +using WaveBuffer = Ryujinx.Audio.Renderer.Common.WaveBuffer; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class PcmFloatDataSourceCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + public uint ChannelIndex { get; } + + public uint ChannelCount { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + public DecodingBehaviour DecodingBehaviour { get; } + + public PcmFloatDataSourceCommandVersion1(ref VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex); + SampleRate = serverState.SampleRate; + ChannelIndex = channelIndex; + ChannelCount = serverState.ChannelsCount; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); + } + + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new() + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.PcmFloat, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = 0, + ExtraParameterSize = 0, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs new file mode 100644 index 000000000..6f01219f3 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; +using WaveBuffer = Ryujinx.Audio.Renderer.Common.WaveBuffer; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class PcmInt16DataSourceCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + public uint ChannelIndex { get; } + + public uint ChannelCount { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + public DecodingBehaviour DecodingBehaviour { get; } + + public PcmInt16DataSourceCommandVersion1(ref VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex); + SampleRate = serverState.SampleRate; + ChannelIndex = channelIndex; + ChannelCount = serverState.ChannelsCount; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); + } + + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new() + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.PcmInt16, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = 0, + ExtraParameterSize = 0, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs new file mode 100644 index 000000000..d3d2ee306 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs @@ -0,0 +1,47 @@ +using Ryujinx.Audio.Renderer.Server.Performance; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class PerformanceCommand : ICommand + { + public enum Type + { + Invalid, + Start, + End, + } + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Performance; + + public uint EstimatedProcessingTime { get; set; } + + public PerformanceEntryAddresses PerformanceEntryAddresses { get; } + + public Type PerformanceType { get; set; } + + public PerformanceCommand(ref PerformanceEntryAddresses performanceEntryAddresses, Type performanceType, int nodeId) + { + Enabled = true; + PerformanceEntryAddresses = performanceEntryAddresses; + PerformanceType = performanceType; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + if (PerformanceType == Type.Start) + { + PerformanceEntryAddresses.SetStartTime(context.GetTimeElapsedSinceDspStartedProcessing()); + } + else if (PerformanceType == Type.End) + { + PerformanceEntryAddresses.SetProcessingTime(context.GetTimeElapsedSinceDspStartedProcessing()); + PerformanceEntryAddresses.IncrementEntryCount(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs new file mode 100644 index 000000000..8cdd4843b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs @@ -0,0 +1,254 @@ +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 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 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, Parameter.ChannelCount); + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices, Parameter.ChannelCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dMono(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, _outputEarlyIndicesTableMono, _targetEarlyDelayLineIndicesTableMono, _targetOutputFeedbackIndicesTableMono); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dStereo(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, _outputEarlyIndicesTableStereo, _targetEarlyDelayLineIndicesTableStereo, _targetOutputFeedbackIndicesTableStereo); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dQuadraphonic(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, _outputEarlyIndicesTableQuadraphonic, _targetEarlyDelayLineIndicesTableQuadraphonic, _targetOutputFeedbackIndicesTableQuadraphonic); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dSurround(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, _outputEarlyIndicesTableSurround, _targetEarlyDelayLineIndicesTableSurround, _targetOutputFeedbackIndicesTableSurround); + } + + private unsafe void ProcessReverb3dGeneric(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable) + { + const int DelayLineSampleIndexOffset = 1; + + bool isMono = Parameter.ChannelCount == 1; + bool isSurround = Parameter.ChannelCount == 6; + + Span outputValues = stackalloc float[Constants.ChannelCountMax]; + Span channelInput = stackalloc float[Parameter.ChannelCount]; + Span feedbackValues = stackalloc float[4]; + Span feedbackOutputValues = stackalloc float[4]; + Span values = stackalloc float[4]; + + for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + outputValues.Clear(); + + float tapOut = state.PreDelayLine.TapUnsafe(state.ReflectionDelayTime, DelayLineSampleIndexOffset); + + for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++) + { + int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i]; + int outputIndex = outputEarlyIndicesTable[earlyDelayIndex]; + + 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 inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span 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); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs new file mode 100644 index 000000000..874eb8e8b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs @@ -0,0 +1,284 @@ +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 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 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 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, Parameter.ChannelCount); + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices, Parameter.ChannelCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableMono, + _targetEarlyDelayLineIndicesTableMono, + _targetOutputFeedbackIndicesTableMono, + _outputIndicesTableMono); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableStereo, + _targetEarlyDelayLineIndicesTableStereo, + _targetOutputFeedbackIndicesTableStereo, + _outputIndicesTableStereo); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableQuadraphonic, + _targetEarlyDelayLineIndicesTableQuadraphonic, + _targetOutputFeedbackIndicesTableQuadraphonic, + _outputIndicesTableQuadraphonic); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableSurround, + _targetEarlyDelayLineIndicesTableSurround, + _targetOutputFeedbackIndicesTableSurround, + _outputIndicesTableSurround); + } + + private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable, ReadOnlySpan 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 outputValues = stackalloc float[Constants.ChannelCountMax]; + Span feedbackValues = stackalloc float[4]; + Span feedbackOutputValues = stackalloc float[4]; + Span channelInput = stackalloc float[Parameter.ChannelCount]; + + for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + outputValues.Clear(); + + 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 inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span 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 == UsageState.Invalid) + { + state = new ReverbState(ref _parameter, WorkBuffer, IsLongSizePreDelaySupported); + } + else if (Parameter.Status == UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessReverb(context, ref state); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs new file mode 100644 index 000000000..8882500cd --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs @@ -0,0 +1,70 @@ +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 OutBuffer { get; } + + public UpsampleCommand(uint bufferOffset, UpsamplerState info, uint inputCount, Span 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]); + } + + if (info.BufferStates?.Length != (int)inputCount) + { + // Keep state if possible. + info.BufferStates = new UpsamplerBufferState[(int)inputCount]; + } + + UpsamplerInfo = info; + } + + private Span GetBuffer(int index, int sampleCount) + { + return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount); + } + + public void Process(CommandList context) + { + uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount); + + for (int i = 0; i < bufferCount; i++) + { + Span inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]); + Span outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount); + + UpsamplerHelper.Upsample(outputBuffer, inputBuffer, (int)UpsamplerInfo.SampleCount, (int)InputSampleCount, ref UpsamplerInfo.BufferStates[i]); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs new file mode 100644 index 000000000..5deeb07f1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class VolumeCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Volume; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume { get; } + + public VolumeCommand(float volume, uint bufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)bufferIndex; + OutputBufferIndex = (ushort)bufferIndex; + + Volume = volume; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeAvx(Span outputBuffer, ReadOnlySpan inputBuffer) + { + Vector256 volumeVec = Vector256.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer); + Span> outputVec = MemoryMarshal.Cast>(outputBuffer); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec)); + } + + for (int i = sisdStart; i < inputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeSse41(Span outputBuffer, ReadOnlySpan inputBuffer) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer); + Span> outputVec = MemoryMarshal.Cast>(outputBuffer); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec)); + } + + for (int i = sisdStart; i < inputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeAdvSimd(Span outputBuffer, ReadOnlySpan inputBuffer) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer); + Span> outputVec = MemoryMarshal.Cast>(outputBuffer); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.Ceiling(AdvSimd.Multiply(inputVec[i], volumeVec)); + } + + for (int i = sisdStart; i < inputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolume(Span outputBuffer, ReadOnlySpan inputBuffer) + { + if (Avx.IsSupported) + { + ProcessVolumeAvx(outputBuffer, inputBuffer); + } + else if (Sse41.IsSupported) + { + ProcessVolumeSse41(outputBuffer, inputBuffer); + } + else if (AdvSimd.IsSupported) + { + ProcessVolumeAdvSimd(outputBuffer, inputBuffer); + } + else + { + ProcessVolumeSlowPath(outputBuffer, inputBuffer); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeSlowPath(Span outputBuffer, ReadOnlySpan inputBuffer) + { + for (int i = 0; i < outputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessVolume(outputBuffer, inputBuffer); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs new file mode 100644 index 000000000..bffbcbc63 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class VolumeRampCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.VolumeRamp; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public VolumeRampCommand(float volume0, float volume1, uint bufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)bufferIndex; + OutputBufferIndex = (ushort)bufferIndex; + + Volume0 = volume0; + Volume1 = volume1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeRamp(Span outputBuffer, ReadOnlySpan inputBuffer, int sampleCount) + { + float ramp = (Volume1 - Volume0) / sampleCount; + + float volume = Volume0; + + for (int i = 0; i < sampleCount; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + volume += ramp; + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessVolumeRamp(outputBuffer, inputBuffer, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs new file mode 100644 index 000000000..98657bd13 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs @@ -0,0 +1,466 @@ +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 outputBuffer, ref WaveBufferInformation info, Span wavebuffers, ref VoiceUpdateState voiceState, uint targetSampleRate, int sampleCount) + { + const int TempBufferSize = 0x3F00; + + Span 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()[..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(waveBuffer.Context); + } + + Span tempSpan = tempBuffer[(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 waveBufferAdpcm = ReadOnlySpan.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 coefficients = MemoryMarshal.Cast(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 waveBufferPcm16 = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount); + int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount; + + waveBufferPcm16 = MemoryMarshal.Cast(memoryManager.GetSpan(bufferOffset, bufferSize)); + } + + decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcm16, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount); + break; + case SampleFormat.PcmFloat: + ReadOnlySpan waveBufferPcmFloat = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount); + int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount; + + waveBufferPcmFloat = MemoryMarshal.Cast(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 outputSpanInt = MemoryMarshal.Cast(outputBuffer[i..]); + + if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion)) + { + for (int j = 0; j < y; j++) + { + outputBuffer[j] = tempBuffer[j]; + } + } + else + { + Span tempSpan = tempBuffer[(tempBufferIndex + y)..]; + + tempSpan[..(sampleCountToDecode - y)].Clear(); + + 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 output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(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 output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(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 output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(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 output, ReadOnlySpan input, int sampleCount) + { + for (int i = 0; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToFloat(Span output, ReadOnlySpan 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 output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(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 output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(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 output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(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 output, ReadOnlySpan input, int sampleCount) + { + for (int i = 0; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToInt(Span output, ReadOnlySpan 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 bufferIndices, uint channelCount) + { + if (!isSupported && channelCount == 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 bufferIndices, uint channelCount) + { + if (isSupported && channelCount == 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; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs new file mode 100644 index 000000000..7253fdc92 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs @@ -0,0 +1,52 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DecayDelay : IDelayLine + { + private readonly IDelayLine _delayLine; + + public uint CurrentSampleCount => _delayLine.CurrentSampleCount; + + public uint SampleCountMax => _delayLine.SampleCountMax; + + private float _decayRate; + + public DecayDelay(IDelayLine delayLine) + { + _decayRate = 0.0f; + _delayLine = delayLine; + } + + public void SetDecayRate(float decayRate) + { + _decayRate = decayRate; + } + + public float Update(float value) + { + float delayLineValue = _delayLine.Read(); + float processedValue = value - (_decayRate * delayLineValue); + + return _delayLine.Update(processedValue) + processedValue * _decayRate; + } + + public void SetDelay(float delayTime) + { + _delayLine.SetDelay(delayTime); + } + + public float Read() + { + return _delayLine.Read(); + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return _delayLine.TapUnsafe(sampleIndex, offset); + } + + public float Tap(uint sampleIndex) + { + return _delayLine.Tap(sampleIndex); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs new file mode 100644 index 000000000..8a3590a20 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs @@ -0,0 +1,78 @@ +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DelayLine : IDelayLine + { + private readonly float[] _workBuffer; + private readonly uint _sampleRate; + private uint _currentSampleIndex; + private uint _lastSampleIndex; + + public uint CurrentSampleCount { get; private set; } + public uint SampleCountMax { get; private set; } + + public DelayLine(uint sampleRate, float delayTimeMax) + { + _sampleRate = sampleRate; + SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax); + _workBuffer = new float[SampleCountMax + 1]; + + SetDelay(delayTimeMax); + } + + private void ConfigureDelay(uint targetSampleCount) + { + CurrentSampleCount = Math.Min(SampleCountMax, targetSampleCount); + _currentSampleIndex = 0; + + if (CurrentSampleCount == 0) + { + _lastSampleIndex = 0; + } + else + { + _lastSampleIndex = CurrentSampleCount - 1; + } + } + + public void SetDelay(float delayTime) + { + ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime)); + } + + public float Read() + { + return _workBuffer[_currentSampleIndex]; + } + + public float Update(float value) + { + float output = Read(); + + _workBuffer[_currentSampleIndex++] = value; + + if (_currentSampleIndex >= _lastSampleIndex) + { + _currentSampleIndex = 0; + } + + return output; + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return IDelayLine.Tap(_workBuffer, (int)_currentSampleIndex, (int)sampleIndex + offset, (int)CurrentSampleCount); + } + + public float Tap(uint sampleIndex) + { + if (sampleIndex >= CurrentSampleCount) + { + sampleIndex = CurrentSampleCount - 1; + } + + return TapUnsafe(sampleIndex, -1); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs new file mode 100644 index 000000000..ed8e7cfe0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs @@ -0,0 +1,76 @@ +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DelayLine3d : IDelayLine + { + private readonly float[] _workBuffer; + private readonly uint _sampleRate; + private uint _currentSampleIndex; + private uint _lastSampleIndex; + + public uint CurrentSampleCount { get; private set; } + public uint SampleCountMax { get; private set; } + + public DelayLine3d(uint sampleRate, float delayTimeMax) + { + _sampleRate = sampleRate; + SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax); + _workBuffer = new float[SampleCountMax + 1]; + + SetDelay(delayTimeMax); + } + + private void ConfigureDelay(uint targetSampleCount) + { + if (SampleCountMax >= targetSampleCount) + { + CurrentSampleCount = targetSampleCount; + _lastSampleIndex = (_currentSampleIndex + targetSampleCount) % (SampleCountMax + 1); + } + } + + public void SetDelay(float delayTime) + { + ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime)); + } + + public float Read() + { + return _workBuffer[_currentSampleIndex]; + } + + public float Update(float value) + { + Debug.Assert(!float.IsNaN(value) && !float.IsInfinity(value)); + + _workBuffer[_lastSampleIndex++] = value; + + float output = Read(); + + _currentSampleIndex++; + + if (_currentSampleIndex >= SampleCountMax) + { + _currentSampleIndex = 0; + } + + if (_lastSampleIndex >= SampleCountMax) + { + _lastSampleIndex = 0; + } + + return output; + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return IDelayLine.Tap(_workBuffer, (int)_lastSampleIndex, (int)sampleIndex + offset, (int)SampleCountMax + 1); + } + + public float Tap(uint sampleIndex) + { + return TapUnsafe(sampleIndex, -1); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs new file mode 100644 index 000000000..d44aec0ae --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public struct ExponentialMovingAverage + { + private float _mean; + + public ExponentialMovingAverage(float mean) + { + _mean = mean; + } + + public readonly float Read() + { + return _mean; + } + + public float Update(float value, float alpha) + { + _mean += alpha * (value - _mean); + + return _mean; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs new file mode 100644 index 000000000..b408e294d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public interface IDelayLine + { + uint CurrentSampleCount { get; } + uint SampleCountMax { get; } + + void SetDelay(float delayTime); + float Read(); + float Update(float value); + + float TapUnsafe(uint sampleIndex, int offset); + float Tap(uint sampleIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Tap(Span workBuffer, int baseIndex, int sampleIndex, int delaySampleCount) + { + int targetIndex = baseIndex - sampleIndex; + + if (targetIndex < 0) + { + targetIndex += delaySampleCount; + } + + return workBuffer[targetIndex]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetSampleCount(uint sampleRate, float delayTime) + { + return (uint)MathF.Round(sampleRate * delayTime); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs new file mode 100644 index 000000000..d519de333 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs @@ -0,0 +1,39 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class FixedPointHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToInt(long value, int qBits) + { + return (int)(value >> qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ToFloat(long value, int qBits) + { + return (float)value / (1 << qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ConvertFloat(float value, int qBits) + { + return value / (1 << qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToFixed(float value, int qBits) + { + return (int)(value * (1 << qBits)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int RoundUpAndToInt(long value, int qBits) + { + int half = 1 << (qBits - 1); + + return ToInt(value + half, qBits); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs new file mode 100644 index 000000000..415e1c195 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs @@ -0,0 +1,103 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class FloatingPointHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MultiplyRoundDown(float a, float b) + { + return RoundDown(a * b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RoundDown(float a) + { + return MathF.Round(a, 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RoundUp(float a) + { + return MathF.Round(a); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MultiplyRoundUp(float a, float b) + { + return RoundUp(a * b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow10(float x) + { + // NOTE: Nintendo implementation uses Q15 and a LUT for this, we don't. + // As such, we support the same ranges as Nintendo to avoid unexpected behaviours. + if (x >= 0.0f) + { + return 1.0f; + } + + if (x <= -5.3f) + { + return 0.0f; + } + + return MathF.Pow(10, x); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Log10(float x) + { + // NOTE: Nintendo uses an approximation of log10, we don't. + // As such, we support the same ranges as Nintendo to avoid unexpected behaviours. + return MathF.Log10(MathF.Max(x, 1.0e-10f)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MeanSquare(ReadOnlySpan inputs) + { + float res = 0.0f; + + foreach (float input in inputs) + { + float normInput = input * (1f / 32768f); + res += normInput * normInput; + } + + res /= inputs.Length; + + return res; + } + + /// + /// Map decibel to linear. + /// + /// The decibel value to convert + /// Converted linear value/returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DecibelToLinear(float db) + { + return MathF.Pow(10.0f, db / 20.0f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DegreesToRadians(float degrees) + { + return degrees * MathF.PI / 180.0f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Cos(float value) + { + return MathF.Cos(DegreesToRadians(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sin(float value) + { + return MathF.Sin(DegreesToRadians(value)); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs new file mode 100644 index 000000000..8134e6b77 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs @@ -0,0 +1,134 @@ +using System; +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(int startSampleOffset, int offset, int channelCount) where T : unmanaged + { + return (ulong)(Unsafe.SizeOf() * channelCount * (startSampleOffset + offset)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBufferSize(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged + { + if (endSampleOffset < startSampleOffset) + { + return 0; + } + + return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf(); + } + + [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 void ConvertSampleToPcm8(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + // Output most significant byte + output[i] = (sbyte)(input[i] >> 8); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcm24(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + output[i * 3 + 2] = (byte)(input[i] >> 8); + output[i * 3 + 1] = (byte)(input[i] & 0xff); + output[i * 3 + 0] = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcm32(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + output[i] = input[i] << 16; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcmFloat(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + output[i] = ConvertSampleToPcmFloat(input[i]); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan 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 output, ReadOnlySpan 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; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs new file mode 100644 index 000000000..e44e9f41e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs @@ -0,0 +1,599 @@ +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 readonly short[] _normalCurveLut0 = { + 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 readonly short[] _normalCurveLut1 = { + -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 readonly short[] _normalCurveLut2 = { + 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 readonly short[] _highCurveLut0 = { + -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 readonly short[] _highCurveLut1 = { + -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 readonly short[] _highCurveLut2 = { + 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 readonly float[] _normalCurveLut0F; + private static readonly float[] _normalCurveLut1F; + private static readonly float[] _normalCurveLut2F; + + private static readonly float[] _highCurveLut0F; + private static readonly float[] _highCurveLut1F; + private static readonly 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 outputBuffer, ReadOnlySpan 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 GetDefaultParameter(float ratio) + { + if (ratio <= 1.0f) + { + return _normalCurveLut1F; + } + + if (ratio > 1.333313f) + { + return _normalCurveLut0F; + } + + return _normalCurveLut2F; + } + + private unsafe static void ResampleDefaultQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount, bool needPitch) + { + ReadOnlySpan 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 parameter = Sse.LoadVector128(pParameters); + + for (; i < (sampleCount & ~3); i += 4) + { + Vector128 intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i); + Vector128 intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1); + Vector128 intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2); + Vector128 intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3); + + Vector128 input0 = Sse2.ConvertToVector128Single(intInput0); + Vector128 input1 = Sse2.ConvertToVector128Single(intInput1); + Vector128 input2 = Sse2.ConvertToVector128Single(intInput2); + Vector128 input3 = Sse2.ConvertToVector128Single(intInput3); + + Vector128 mix0 = Sse.Multiply(input0, parameter); + Vector128 mix1 = Sse.Multiply(input1, parameter); + Vector128 mix2 = Sse.Multiply(input2, parameter); + Vector128 mix3 = Sse.Multiply(input3, parameter); + + Vector128 mix01 = Sse3.HorizontalAdd(mix0, mix1); + Vector128 mix23 = Sse3.HorizontalAdd(mix2, mix3); + + Vector128 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 parameter0 = Sse.LoadVector128(pParameters + baseIndex0); + Vector128 parameter1 = Sse.LoadVector128(pParameters + baseIndex1); + Vector128 parameter2 = Sse.LoadVector128(pParameters + baseIndex2); + Vector128 parameter3 = Sse.LoadVector128(pParameters + baseIndex3); + + Vector128 intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0); + Vector128 intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1); + Vector128 intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2); + Vector128 intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3); + + Vector128 input0 = Sse2.ConvertToVector128Single(intInput0); + Vector128 input1 = Sse2.ConvertToVector128Single(intInput1); + Vector128 input2 = Sse2.ConvertToVector128Single(intInput2); + Vector128 input3 = Sse2.ConvertToVector128Single(intInput3); + + Vector128 mix0 = Sse.Multiply(input0, parameter0); + Vector128 mix1 = Sse.Multiply(input1, parameter1); + Vector128 mix2 = Sse.Multiply(input2, parameter2); + Vector128 mix3 = Sse.Multiply(input3, parameter3); + + Vector128 mix01 = Sse3.HorizontalAdd(mix0, mix1); + Vector128 mix23 = Sse3.HorizontalAdd(mix2, mix3); + + Vector128 mix0123 = Sse3.HorizontalAdd(mix01, mix23); + + Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123)); + } + } + } + } + + for (; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 4; + ReadOnlySpan parameter = parameters.Slice(baseIndex, 4); + ReadOnlySpan 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 GetHighParameter(float ratio) + { + if (ratio <= 1.0f) + { + return _highCurveLut1F; + } + + if (ratio > 1.333313f) + { + return _highCurveLut0F; + } + + return _highCurveLut2F; + } + + private static unsafe void ResampleHighQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount) + { + ReadOnlySpan parameters = GetHighParameter(ratio); + + int inputBufferIndex = 0; + + if (Avx2.IsSupported) + { + // Fast path; assumes 256-bit vectors for simplicity because the filter is 8 taps + fixed (short* pInput = inputBuffer) + fixed (float* pParameters = parameters) + { + for (int i = 0; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 8; + + Vector256 intInput = Avx2.ConvertToVector256Int32(pInput + inputBufferIndex); + Vector256 floatInput = Avx.ConvertToVector256Single(intInput); + Vector256 parameter = Avx.LoadVector256(pParameters + baseIndex); + Vector256 dp = Avx.DotProduct(floatInput, parameter, control: 0xFF); + + // avx2 does an 8-element dot product piecewise so we have to sum up 2 intermediate results + outputBuffer[i] = (float)Math.Round(dp[0] + dp[4]); + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + } + else + { + for (int i = 0; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 8; + ReadOnlySpan parameter = parameters.Slice(baseIndex, 8); + ReadOnlySpan 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 outputBuffer, ReadOnlySpan 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; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs new file mode 100644 index 000000000..f9ef201f2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6)] + public struct AdpcmLoopContext + { + public short PredScale; + public short History0; + public short History1; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs new file mode 100644 index 000000000..97bbc80cc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs @@ -0,0 +1,74 @@ +using Ryujinx.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x80)] + public struct AuxiliaryBufferHeader + { + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x40)] + public struct AuxiliaryBufferInfo + { + private const uint ReadOffsetPosition = 0x0; + private const uint WriteOffsetPosition = 0x4; + private const uint LostSampleCountPosition = 0x8; + private const uint TotalSampleCountPosition = 0xC; + + public uint ReadOffset; + public uint WriteOffset; + public uint LostSampleCount; + public uint TotalSampleCount; + private unsafe fixed uint _unknown[12]; + + public static uint GetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + ReadOffsetPosition); + } + + public static uint GetWriteOffset(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + WriteOffsetPosition); + } + + public static uint GetLostSampleCount(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + LostSampleCountPosition); + } + + public static uint GetTotalSampleCount(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + TotalSampleCountPosition); + } + + public static void SetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + ReadOffsetPosition, value); + } + + public static void SetWriteOffset(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + WriteOffsetPosition, value); + } + + public static void SetLostSampleCount(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + LostSampleCountPosition, value); + } + + public static void SetTotalSampleCount(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + TotalSampleCountPosition, value); + } + + public static void Reset(IVirtualMemoryManager manager, ulong bufferAddress) + { + // NOTE: Lost sample count is never reset, since REV10. + manager.Write(bufferAddress + ReadOffsetPosition, 0UL); + manager.Write(bufferAddress + TotalSampleCountPosition, 0); + } + } + + public AuxiliaryBufferInfo CpuBufferInfo; + public AuxiliaryBufferInfo DspBufferInfo; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs new file mode 100644 index 000000000..f9a32b3f9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct BiquadFilterState + { + public float State0; + public float State1; + public float State2; + public float State3; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs new file mode 100644 index 000000000..5ca8cd20a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs @@ -0,0 +1,51 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct CompressorState + { + public ExponentialMovingAverage InputMovingAverage; + public float Unknown4; + public ExponentialMovingAverage CompressionGainAverage; + public float CompressorGainReduction; + public float Unknown10; + public float Unknown14; + public float PreviousCompressionEmaAlpha; + public float MakeupGain; + public float OutputGain; + + public CompressorState(ref CompressorParameter parameter) + { + InputMovingAverage = new ExponentialMovingAverage(0.0f); + Unknown4 = 1.0f; + CompressionGainAverage = new ExponentialMovingAverage(1.0f); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref CompressorParameter parameter) + { + float threshold = parameter.Threshold; + float ratio = 1.0f / parameter.Ratio; + float attackCoefficient = parameter.AttackCoefficient; + float makeupGain; + + if (parameter.MakeupGainEnabled) + { + makeupGain = (threshold * 0.5f * (ratio - 1.0f)) - 3.0f; + } + else + { + makeupGain = 0.0f; + } + + PreviousCompressionEmaAlpha = attackCoefficient; + MakeupGain = makeupGain; + CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax; + Unknown10 = threshold - 1.5f; + Unknown14 = threshold + 1.5f; + OutputGain = FloatingPointHelper.DecibelToLinear(parameter.OutputGain + makeupGain); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs new file mode 100644 index 000000000..17ad2a40d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct DelayState + { + public DelayLine[] DelayLines { get; } + public float[] LowPassZ { get; set; } + public float FeedbackGain { get; private set; } + public float DelayFeedbackBaseGain { get; private set; } + public float DelayFeedbackCrossGain { get; private set; } + public float LowPassFeedbackGain { get; private set; } + public float LowPassBaseGain { get; private set; } + + private const int FixedPointPrecision = 14; + + public DelayState(ref DelayParameter parameter, ulong workBuffer) + { + DelayLines = new DelayLine[parameter.ChannelCount]; + LowPassZ = new float[parameter.ChannelCount]; + + uint sampleRate = (uint)FixedPointHelper.ToInt(parameter.SampleRate, FixedPointPrecision) / 1000; + + for (int i = 0; i < DelayLines.Length; i++) + { + DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax); + DelayLines[i].SetDelay(parameter.DelayTime); + } + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref DelayParameter parameter) + { + FeedbackGain = FixedPointHelper.ToFloat(parameter.FeedbackGain, FixedPointPrecision) * 0.98f; + + float channelSpread = FixedPointHelper.ToFloat(parameter.ChannelSpread, FixedPointPrecision); + + DelayFeedbackBaseGain = (1.0f - channelSpread) * FeedbackGain; + + if (parameter.ChannelCount == 4 || parameter.ChannelCount == 6) + { + DelayFeedbackCrossGain = channelSpread * 0.5f * FeedbackGain; + } + else + { + DelayFeedbackCrossGain = channelSpread * FeedbackGain; + } + + LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision); + LowPassBaseGain = 1.0f - LowPassFeedbackGain; + } + + public readonly void UpdateLowPassFilter(ref float tempRawRef, uint channelCount) + { + for (int i = 0; i < channelCount; i++) + { + float lowPassResult = LowPassFeedbackGain * LowPassZ[i] + Unsafe.Add(ref tempRawRef, i) * LowPassBaseGain; + + LowPassZ[i] = lowPassResult; + DelayLines[i].Update(lowPassResult); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs new file mode 100644 index 000000000..1388bfcef --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs @@ -0,0 +1,31 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct LimiterState + { + public ExponentialMovingAverage[] DetectorAverage; + public ExponentialMovingAverage[] CompressionGainAverage; + public float[] DelayedSampleBuffer; + public int[] DelayedSampleBufferPosition; + + public LimiterState(ref LimiterParameter parameter, ulong workBuffer) + { + DetectorAverage = new ExponentialMovingAverage[parameter.ChannelCount]; + CompressionGainAverage = new ExponentialMovingAverage[parameter.ChannelCount]; + DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax]; + DelayedSampleBufferPosition = new int[parameter.ChannelCount]; + + DetectorAverage.AsSpan().Fill(new ExponentialMovingAverage(0.0f)); + CompressionGainAverage.AsSpan().Fill(new ExponentialMovingAverage(1.0f)); + DelayedSampleBufferPosition.AsSpan().Clear(); + DelayedSampleBuffer.AsSpan().Clear(); + + UpdateParameter(ref parameter); + } + + public static void UpdateParameter(ref LimiterParameter parameter) { } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs new file mode 100644 index 000000000..e83e0d5fc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs @@ -0,0 +1,119 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct Reverb3dState + { + private readonly float[] _fdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f }; + private readonly float[] _fdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f }; + private readonly float[] _decayDelayMaxTimes1 = new float[4] { 17.0f, 13.0f, 9.0f, 7.0f }; + private readonly float[] _decayDelayMaxTimes2 = new float[4] { 19.0f, 11.0f, 10.0f, 6.0f }; + private readonly float[] _earlyDelayTimes = new float[20] { 0.017136f, 0.059154f, 0.16173f, 0.39019f, 0.42526f, 0.45541f, 0.68974f, 0.74591f, 0.83384f, 0.8595f, 0.0f, 0.075024f, 0.16879f, 0.2999f, 0.33744f, 0.3719f, 0.59901f, 0.71674f, 0.81786f, 0.85166f }; + public readonly float[] EarlyGain = new float[20] { 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f }; + + public IDelayLine[] FdnDelayLines { get; } + public DecayDelay[] DecayDelays1 { get; } + public DecayDelay[] DecayDelays2 { get; } + public IDelayLine PreDelayLine { get; } + public IDelayLine FrontCenterDelayLine { get; } + public float DryGain { get; private set; } + public uint[] EarlyDelayTime { get; private set; } + public float PreviousPreDelayValue { get; set; } + public float PreviousPreDelayGain { get; private set; } + public float TargetPreDelayGain { get; private set; } + public float EarlyReflectionsGain { get; private set; } + public float LateReverbGain { get; private set; } + public uint ReflectionDelayTime { get; private set; } + public float EchoLateReverbDecay { get; private set; } + public float[] DecayDirectFdnGain { get; private set; } + public float[] DecayCurrentFdnGain { get; private set; } + public float[] DecayCurrentOutputGain { get; private set; } + public float[] PreviousFeedbackOutputDecayed { get; private set; } + + public Reverb3dState(ref Reverb3dParameter parameter, ulong workBuffer) + { + FdnDelayLines = new IDelayLine[4]; + DecayDelays1 = new DecayDelay[4]; + DecayDelays2 = new DecayDelay[4]; + DecayDirectFdnGain = new float[4]; + DecayCurrentFdnGain = new float[4]; + DecayCurrentOutputGain = new float[4]; + PreviousFeedbackOutputDecayed = new float[4]; + + uint sampleRate = parameter.SampleRate / 1000; + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i] = new DelayLine3d(sampleRate, _fdnDelayMaxTimes[i]); + DecayDelays1[i] = new DecayDelay(new DelayLine3d(sampleRate, _decayDelayMaxTimes1[i])); + DecayDelays2[i] = new DecayDelay(new DelayLine3d(sampleRate, _decayDelayMaxTimes2[i])); + } + + PreDelayLine = new DelayLine3d(sampleRate, 400); + FrontCenterDelayLine = new DelayLine3d(sampleRate, 5); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref Reverb3dParameter parameter) + { + uint sampleRate = parameter.SampleRate / 1000; + + EarlyDelayTime = new uint[20]; + DryGain = parameter.DryGain; + PreviousFeedbackOutputDecayed.AsSpan().Clear(); + PreviousPreDelayValue = 0; + + EarlyReflectionsGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReflectionsGain, 5000.0f) / 2000.0f); + LateReverbGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReverbGain, 5000.0f) / 2000.0f); + + float highFrequencyRoomGain = FloatingPointHelper.Pow10(parameter.RoomHf / 2000.0f); + + if (highFrequencyRoomGain < 1.0f) + { + float tempA = 1.0f - highFrequencyRoomGain; + float tempB = 2.0f - ((2.0f * highFrequencyRoomGain) * FloatingPointHelper.Cos(256.0f * parameter.HfReference / parameter.SampleRate)); + float tempC = MathF.Sqrt(MathF.Pow(tempB, 2) - (4.0f * (1.0f - highFrequencyRoomGain) * (1.0f - highFrequencyRoomGain))); + + PreviousPreDelayGain = (tempB - tempC) / (2.0f * tempA); + TargetPreDelayGain = 1.0f - PreviousPreDelayGain; + } + else + { + PreviousPreDelayGain = 0.0f; + TargetPreDelayGain = 1.0f; + } + + ReflectionDelayTime = IDelayLine.GetSampleCount(sampleRate, 1000.0f * (parameter.ReflectionDelay + parameter.ReverbDelayTime)); + EchoLateReverbDecay = 0.6f * parameter.Diffusion * 0.01f; + + for (int i = 0; i < FdnDelayLines.Length; i++) + { + FdnDelayLines[i].SetDelay(_fdnDelayMinTimes[i] + (parameter.Density / 100 * (_fdnDelayMaxTimes[i] - _fdnDelayMinTimes[i]))); + + uint tempSampleCount = FdnDelayLines[i].CurrentSampleCount + DecayDelays1[i].CurrentSampleCount + DecayDelays2[i].CurrentSampleCount; + + float tempA = (-60.0f * tempSampleCount) / (parameter.DecayTime * parameter.SampleRate); + float tempB = tempA / parameter.HfDecayRatio; + float tempC = FloatingPointHelper.Cos(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate) / FloatingPointHelper.Sin(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate); + float tempD = FloatingPointHelper.Pow10((tempB - tempA) / 40.0f); + float tempE = FloatingPointHelper.Pow10((tempB + tempA) / 40.0f) * 0.7071f; + + DecayDirectFdnGain[i] = tempE * ((tempD * tempC) + 1.0f) / (tempC + tempD); + DecayCurrentFdnGain[i] = tempE * (1.0f - (tempD * tempC)) / (tempC + tempD); + DecayCurrentOutputGain[i] = (tempC - tempD) / (tempC + tempD); + + DecayDelays1[i].SetDecayRate(EchoLateReverbDecay); + DecayDelays2[i].SetDecayRate(EchoLateReverbDecay * -0.9f); + } + + for (int i = 0; i < EarlyDelayTime.Length; i++) + { + uint sampleCount = Math.Min(IDelayLine.GetSampleCount(sampleRate, (parameter.ReflectionDelay * 1000.0f) + (_earlyDelayTimes[i] * 1000.0f * ((parameter.ReverbDelayTime * 0.9998f) + 0.02f))), PreDelayLine.SampleCountMax); + EarlyDelayTime[i] = sampleCount; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs new file mode 100644 index 000000000..f1927b718 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs @@ -0,0 +1,204 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct ReverbState + { + private static readonly float[] _fdnDelayTimes = new float[20] + { + // Room + 53.953247f, 79.192566f, 116.238770f, 130.615295f, + // Hall + 53.953247f, 79.192566f, 116.238770f, 170.615295f, + // Plate + 5f, 10f, 5f, 10f, + // Cathedral + 47.03f, 71f, 103f, 170f, + // Max delay (Hall is the one with the highest values so identical to Hall) + 53.953247f, 79.192566f, 116.238770f, 170.615295f, + }; + + private static readonly float[] _decayDelayTimes = new float[20] + { + // Room + 7f, 9f, 13f, 17f, + // Hall + 7f, 9f, 13f, 17f, + // Plate (no decay) + 1f, 1f, 1f, 1f, + // Cathedral + 7f, 7f, 13f, 9f, + // Max delay (Hall is the one with the highest values so identical to Hall) + 7f, 9f, 13f, 17f, + }; + + private static readonly float[] _earlyDelayTimes = new float[50] + { + // Room + 0.0f, 3.5f, 2.8f, 3.9f, 2.7f, 13.4f, 7.9f, 8.4f, 9.9f, 12.0f, + // Chamber + 0.0f, 11.8f, 5.5f, 11.2f, 10.4f, 38.1f, 22.2f, 29.6f, 21.2f, 24.8f, + // Hall + 0.0f, 41.5f, 20.5f, 41.3f, 0.0f, 29.5f, 33.8f, 45.2f, 46.8f, 0.0f, + // Cathedral + 33.1f, 43.3f, 22.8f, 37.9f, 14.9f, 35.3f, 17.9f, 34.2f, 0.0f, 43.3f, + // Disabled + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + }; + + private static readonly float[] _earlyGainBase = new float[50] + { + // Room + 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, + // Chamber + 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f, + // Hall + 0.50f, 0.70f, 0.70f, 0.68f, 0.50f, 0.68f, 0.68f, 0.70f, 0.68f, 0.00f, + // Cathedral + 0.93f, 0.92f, 0.87f, 0.86f, 0.94f, 0.81f, 0.80f, 0.77f, 0.76f, 0.65f, + // Disabled + 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, + }; + + private static readonly float[] _preDelayTimes = new float[5] + { + // Room + 12.5f, + // Chamber + 40.0f, + // Hall + 50.0f, + // Cathedral + 50.0f, + // Disabled + 0.0f, + }; + + public DelayLine[] FdnDelayLines { get; } + public DecayDelay[] DecayDelays { get; } + public DelayLine PreDelayLine { get; } + public DelayLine FrontCenterDelayLine { get; } + public uint[] EarlyDelayTime { get; } + public float[] EarlyGain { get; } + public uint PreDelayLineDelayTime { get; private set; } + + public float[] HighFrequencyDecayDirectGain { get; } + public float[] HighFrequencyDecayPreviousGain { get; } + public float[] PreviousFeedbackOutput { get; } + + public const int EarlyModeCount = 10; + + private const int FixedPointPrecision = 14; + + private static ReadOnlySpan GetFdnDelayTimesByLateMode(ReverbLateMode lateMode) + { + return _fdnDelayTimes.AsSpan((int)lateMode * 4, 4); + } + + private static ReadOnlySpan GetDecayDelayTimesByLateMode(ReverbLateMode lateMode) + { + return _decayDelayTimes.AsSpan((int)lateMode * 4, 4); + } + + public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported) + { + FdnDelayLines = new DelayLine[4]; + DecayDelays = new DecayDelay[4]; + EarlyDelayTime = new uint[EarlyModeCount]; + EarlyGain = new float[EarlyModeCount]; + HighFrequencyDecayDirectGain = new float[4]; + HighFrequencyDecayPreviousGain = new float[4]; + PreviousFeedbackOutput = new float[4]; + + ReadOnlySpan fdnDelayTimes = GetFdnDelayTimesByLateMode(ReverbLateMode.Limit); + ReadOnlySpan decayDelayTimes = GetDecayDelayTimesByLateMode(ReverbLateMode.Limit); + + uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision); + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i] = new DelayLine(sampleRate, fdnDelayTimes[i]); + DecayDelays[i] = new DecayDelay(new DelayLine(sampleRate, decayDelayTimes[i])); + } + + float preDelayTimeMax = 150.0f; + + if (isLongSizePreDelaySupported) + { + preDelayTimeMax = 350.0f; + } + + PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax); + FrontCenterDelayLine = new DelayLine(sampleRate, 5.0f); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref ReverbParameter parameter) + { + uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision); + + float preDelayTimeInMilliseconds = FixedPointHelper.ToFloat(parameter.PreDelayTime, FixedPointPrecision); + float earlyGain = FixedPointHelper.ToFloat(parameter.EarlyGain, FixedPointPrecision); + float coloration = FixedPointHelper.ToFloat(parameter.Coloration, FixedPointPrecision); + float decayTime = FixedPointHelper.ToFloat(parameter.DecayTime, FixedPointPrecision); + + for (int i = 0; i < 10; i++) + { + EarlyDelayTime[i] = Math.Min(IDelayLine.GetSampleCount(sampleRate, _earlyDelayTimes[i] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax) + 1; + EarlyGain[i] = _earlyGainBase[i] * earlyGain; + } + + if (parameter.ChannelCount == 2) + { + EarlyGain[4] = EarlyGain[4] * 0.5f; + EarlyGain[5] = EarlyGain[5] * 0.5f; + } + + PreDelayLineDelayTime = Math.Min(IDelayLine.GetSampleCount(sampleRate, _preDelayTimes[(int)parameter.EarlyMode] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax); + + ReadOnlySpan fdnDelayTimes = GetFdnDelayTimesByLateMode(parameter.LateMode); + ReadOnlySpan decayDelayTimes = GetDecayDelayTimesByLateMode(parameter.LateMode); + + float highFrequencyDecayRatio = FixedPointHelper.ToFloat(parameter.HighFrequencyDecayRatio, FixedPointPrecision); + float highFrequencyUnknownValue = FloatingPointHelper.Cos(1280.0f / sampleRate); + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i].SetDelay(fdnDelayTimes[i]); + DecayDelays[i].SetDelay(decayDelayTimes[i]); + + float tempA = -3 * (DecayDelays[i].CurrentSampleCount + FdnDelayLines[i].CurrentSampleCount); + float tempB = tempA / (decayTime * sampleRate); + float tempC; + float tempD; + + if (highFrequencyDecayRatio < 0.995f) + { + float tempE = FloatingPointHelper.Pow10((((1.0f / highFrequencyDecayRatio) - 1.0f) * 2) / 100 * (tempB / 10)); + float tempF = 1.0f - tempE; + float tempG = 2.0f - (tempE * 2 * highFrequencyUnknownValue); + float tempH = MathF.Sqrt((tempG * tempG) - (tempF * tempF * 4)); + + tempC = (tempG - tempH) / (tempF * 2); + tempD = 1.0f - tempC; + } + else + { + // no high frequency decay ratio + tempC = 0.0f; + tempD = 1.0f; + } + + HighFrequencyDecayDirectGain[i] = FloatingPointHelper.Pow10(tempB / 1000) * tempD * 0.7071f; + HighFrequencyDecayPreviousGain[i] = tempC; + PreviousFeedbackOutput[i] = 0.0f; + + DecayDelays[i].SetDecayRate(0.6f * (1.0f - coloration)); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs new file mode 100644 index 000000000..5732cdb21 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs @@ -0,0 +1,192 @@ +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Common.Memory; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public class UpsamplerHelper + { + private const int HistoryLength = UpsamplerBufferState.HistoryLength; + private const int FilterBankLength = 20; + // Bank0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + private const int Bank0CenterIndex = 9; + private static readonly Array20 _bank1 = PrecomputeFilterBank(1.0f / 6.0f); + private static readonly Array20 _bank2 = PrecomputeFilterBank(2.0f / 6.0f); + private static readonly Array20 _bank3 = PrecomputeFilterBank(3.0f / 6.0f); + private static readonly Array20 _bank4 = PrecomputeFilterBank(4.0f / 6.0f); + private static readonly Array20 _bank5 = PrecomputeFilterBank(5.0f / 6.0f); + + private static Array20 PrecomputeFilterBank(float offset) + { + float Sinc(float x) + { + if (x == 0) + { + return 1.0f; + } + return (MathF.Sin(MathF.PI * x) / (MathF.PI * x)); + } + + float BlackmanWindow(float x) + { + const float A = 0.18f; + const float A0 = 0.5f - 0.5f * A; + const float A1 = -0.5f; + const float A2 = 0.5f * A; + return A0 + A1 * MathF.Cos(2 * MathF.PI * x) + A2 * MathF.Cos(4 * MathF.PI * x); + } + + Array20 result = new(); + + for (int i = 0; i < FilterBankLength; i++) + { + float x = (Bank0CenterIndex - i) + offset; + result[i] = Sinc(x) * BlackmanWindow(x / FilterBankLength + 0.5f); + } + + return result; + } + + // Polyphase upsampling algorithm + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Upsample(Span outputBuffer, ReadOnlySpan inputBuffer, int outputSampleCount, int inputSampleCount, ref UpsamplerBufferState state) + { + if (!state.Initialized) + { + state.Scale = inputSampleCount switch + { + 40 => 6.0f, + 80 => 3.0f, + 160 => 1.5f, + _ => throw new ArgumentOutOfRangeException(nameof(inputSampleCount), inputSampleCount, null), + }; + state.Initialized = true; + } + + if (outputSampleCount == 0) + { + return; + } + + float DoFilterBank(ref UpsamplerBufferState state, in Array20 bank) + { + float result = 0.0f; + + Debug.Assert(state.History.Length == HistoryLength); + Debug.Assert(bank.Length == FilterBankLength); + + int curIdx = 0; + if (Vector.IsHardwareAccelerated) + { + // Do SIMD-accelerated block operations where possible. + // Only about a 2x speedup since filter bank length is short + int stopIdx = FilterBankLength - (FilterBankLength % Vector.Count); + while (curIdx < stopIdx) + { + result += Vector.Dot( + new Vector(bank.AsSpan().Slice(curIdx, Vector.Count)), + new Vector(state.History.AsSpan().Slice(curIdx, Vector.Count))); + curIdx += Vector.Count; + } + } + + while (curIdx < FilterBankLength) + { + result += bank[curIdx] * state.History[curIdx]; + curIdx++; + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void NextInput(ref UpsamplerBufferState state, float input) + { + state.History.AsSpan()[1..].CopyTo(state.History.AsSpan()); + state.History[HistoryLength - 1] = input; + } + + int inputBufferIndex = 0; + + switch (state.Scale) + { + case 6.0f: + for (int i = 0; i < outputSampleCount; i++) + { + switch (state.Phase) + { + case 0: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = state.History[Bank0CenterIndex]; + break; + case 1: + outputBuffer[i] = DoFilterBank(ref state, _bank1); + break; + case 2: + outputBuffer[i] = DoFilterBank(ref state, _bank2); + break; + case 3: + outputBuffer[i] = DoFilterBank(ref state, _bank3); + break; + case 4: + outputBuffer[i] = DoFilterBank(ref state, _bank4); + break; + case 5: + outputBuffer[i] = DoFilterBank(ref state, _bank5); + break; + } + + state.Phase = (state.Phase + 1) % 6; + } + break; + case 3.0f: + for (int i = 0; i < outputSampleCount; i++) + { + switch (state.Phase) + { + case 0: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = state.History[Bank0CenterIndex]; + break; + case 1: + outputBuffer[i] = DoFilterBank(ref state, _bank2); + break; + case 2: + outputBuffer[i] = DoFilterBank(ref state, _bank4); + break; + } + + state.Phase = (state.Phase + 1) % 3; + } + break; + case 1.5f: + // Upsample by 3 then decimate by 2. + for (int i = 0; i < outputSampleCount; i++) + { + switch (state.Phase) + { + case 0: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = state.History[Bank0CenterIndex]; + break; + case 1: + outputBuffer[i] = DoFilterBank(ref state, _bank4); + break; + case 2: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = DoFilterBank(ref state, _bank2); + break; + } + + state.Phase = (state.Phase + 1) % 3; + } + break; + default: + throw new ArgumentOutOfRangeException(nameof(state), state.Scale, null); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs b/src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs new file mode 100644 index 000000000..491a05c86 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs @@ -0,0 +1,99 @@ +using Ryujinx.Audio.Renderer.Server.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Audio Renderer user configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioRendererConfiguration + { + /// + /// The target sample rate of the user. + /// + /// Only 32000Hz and 48000Hz are considered valid, other sample rates will cause undefined behaviour. + public uint SampleRate; + + /// + /// The target sample count per updates. + /// + public uint SampleCount; + + /// + /// The maximum mix buffer count. + /// + public uint MixBufferCount; + + /// + /// The maximum amount of sub mixes that could be used by the user. + /// + public uint SubMixBufferCount; + + /// + /// The maximum amount of voices that could be used by the user. + /// + public uint VoiceCount; + + /// + /// The maximum amount of sinks that could be used by the user. + /// + public uint SinkCount; + + /// + /// The maximum amount of effects that could be used by the user. + /// + public uint EffectCount; + + /// + /// The maximum amount of performance metric frames that could be used by the user. + /// + public uint PerformanceMetricFramesCount; + + /// + /// Set to true if the user allows the to drop voices. + /// + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropEnabled; + + /// + /// Reserved/unused + /// + private readonly byte _reserved; + + /// + /// The target rendering device. + /// + /// Must be + public AudioRendererRenderingDevice RenderingDevice; + + /// + /// The target execution mode. + /// + /// Must be + public AudioRendererExecutionMode ExecutionMode; + + /// + /// The maximum amount of splitters that could be used by the user. + /// + public uint SplitterCount; + + /// + /// The maximum amount of splitters destinations that could be used by the user. + /// + public uint SplitterDestinationCount; + + /// + /// The size of the external context. + /// + /// This is a leftover of the old "codec" interface system that was present between 1.0.0 and 3.0.0. This was entirely removed from the server side with REV8. + public uint ExternalContextSize; + + /// + /// The user audio revision + /// + /// + public int Revision; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs new file mode 100644 index 000000000..5a0565dc6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs @@ -0,0 +1,30 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for behaviour. + /// + /// This is used to report errors to the user during processing. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BehaviourErrorInfoOutStatus + { + /// + /// The reported errors. + /// + public Array10 ErrorInfos; + + /// + /// The amount of error that got reported. + /// + public uint ErrorInfosCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs new file mode 100644 index 000000000..f1492b0b1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs @@ -0,0 +1,34 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Biquad filter parameters. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 1)] + public struct BiquadFilterParameter + { + /// + /// Set to true if the biquad filter is active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool Enable; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved; + + /// + /// Biquad filter numerator (b0, b1, b2). + /// + public Array3 Numerator; + + /// + /// Biquad filter denominator (a1, a2). + /// + /// a0 = 1 + public Array2 Denominator; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs new file mode 100644 index 000000000..65f265a32 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs @@ -0,0 +1,84 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for and . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AuxiliaryBufferParameter + { + /// + /// The input channel indices that will be used by the to write data to . + /// + public Array24 Input; + + /// + /// The output channel indices that will be used by the to read data from . + /// + public Array24 Output; + + /// + /// The total channel count used. + /// + public uint ChannelCount; + + /// + /// The target sample rate. + /// + public uint SampleRate; + + /// + /// The buffer storage total size. + /// + public uint BufferStorageSize; + + /// + /// The maximum number of channels supported. + /// + /// This is unused. + public uint ChannelCountMax; + + /// + /// The address of the start of the region containing two followed by the data that will be written by the . + /// + public ulong SendBufferInfoAddress; + + /// + /// The address of the start of the region containling data that will be written by the . + /// + /// This is unused. + public ulong SendBufferStorageAddress; + + /// + /// The address of the start of the region containing two followed by the data that will be read by the . + /// + /// Unused with . + public ulong ReturnBufferInfoAddress; + + /// + /// The address of the start of the region containling data that will be read by the . + /// + /// This is unused. + public ulong ReturnBufferStorageAddress; + + /// + /// Size of a sample of the mix buffer. + /// + /// This is unused. + public uint MixBufferSampleSize; + + /// + /// The total count of sample that can be stored. + /// + /// This is unused. + public uint TotalSampleCount; + + /// + /// The count of sample of the mix buffer. + /// + /// This is unused. + public uint MixBufferSampleCount; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs new file mode 100644 index 000000000..b12a941a5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs @@ -0,0 +1,44 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BiquadFilterEffectParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// Biquad filter numerator (b0, b1, b2). + /// + public Array3 Numerator; + + /// + /// Biquad filter denominator (a1, a2). + /// + /// a0 = 1 + public Array2 Denominator; + + /// + /// The total channel count used. + /// + public byte ChannelCount; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs new file mode 100644 index 000000000..49b70e501 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BufferMixParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array24 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array24 Output; + + /// + /// The output volumes of the mixes. + /// + public Array24 Volumes; + + /// + /// The total count of mixes used. + /// + public uint MixesCount; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs new file mode 100644 index 000000000..b403f1370 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs @@ -0,0 +1,115 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CompressorParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The threshold. + /// + public float Threshold; + + /// + /// The compressor ratio. + /// + public float Ratio; + + /// + /// The attack time. + /// This is in microseconds. + /// + public int AttackTime; + + /// + /// The release time. + /// This is in microseconds. + /// + public int ReleaseTime; + + /// + /// The input gain. + /// + public float InputGain; + + /// + /// The attack coefficient. + /// + public float AttackCoefficient; + + /// + /// The release coefficient. + /// + public float ReleaseCoefficient; + + /// + /// The output gain. + /// + public float OutputGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Indicate if the makeup gain should be used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool MakeupGainEnabled; + + /// + /// Reserved/padding. + /// + private Array2 _reserved; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs new file mode 100644 index 000000000..99c97d9d9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs @@ -0,0 +1,101 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DelayParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The maximum delay time in milliseconds. + /// + public uint DelayTimeMax; + + /// + /// The delay time in milliseconds. + /// + public uint DelayTime; + + /// + /// The target sample rate. (Q15) + /// + public uint SampleRate; + + /// + /// The input gain. (Q15) + /// + public uint InGain; + + /// + /// The feedback gain. (Q15) + /// + public uint FeedbackGain; + + /// + /// The output gain. (Q15) + /// + public uint OutGain; + + /// + /// The dry gain. (Q15) + /// + public uint DryGain; + + /// + /// The channel spread of the . (Q15) + /// + public uint ChannelSpread; + + /// + /// The low pass amount. (Q15) + /// + public uint LowPassAmount; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs new file mode 100644 index 000000000..23ccb8c88 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs @@ -0,0 +1,138 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LimiterParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The look ahead max time. + /// This is in microseconds. + /// + public int LookAheadTimeMax; + + /// + /// The attack time. + /// This is in microseconds. + /// + public int AttackTime; + + /// + /// The release time. + /// This is in microseconds. + /// + public int ReleaseTime; + + /// + /// The look ahead time. + /// This is in microseconds. + /// + public int LookAheadTime; + + /// + /// The attack coefficient. + /// + public float AttackCoefficient; + + /// + /// The release coefficient. + /// + public float ReleaseCoefficient; + + /// + /// The threshold. + /// + public float Threshold; + + /// + /// The input gain. + /// + public float InputGain; + + /// + /// The output gain. + /// + public float OutputGain; + + /// + /// The minimum samples stored in the delay buffer. + /// + public int DelayBufferSampleCountMin; + + /// + /// The maximum samples stored in the delay buffer. + /// + public int DelayBufferSampleCountMax; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Indicate if the limiter effect should output statistics. + /// + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsEnabled; + + /// + /// Indicate to the DSP that the user did a statistics reset. + /// + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsReset; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs new file mode 100644 index 000000000..97e2f39fc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs @@ -0,0 +1,31 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// Effect result state for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LimiterStatistics + { + /// + /// The max input sample value recorded by the limiter. + /// + public Array6 InputMax; + + /// + /// Compression gain min value. + /// + public Array6 CompressionGainMin; + + /// + /// Reset the statistics. + /// + public void Reset() + { + InputMax.AsSpan().Clear(); + CompressionGainMin.AsSpan().Fill(1.0f); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs new file mode 100644 index 000000000..d2cd78707 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs @@ -0,0 +1,127 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct Reverb3dParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// Reserved/unused. + /// + private readonly uint _reserved; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public uint SampleRate; + + /// + /// Gain of the room high-frequency effect. + /// + public float RoomHf; + + /// + /// Reference high frequency. + /// + public float HfReference; + + /// + /// Reverberation decay time at low frequencies. + /// + public float DecayTime; + + /// + /// Ratio of the decay time at high frequencies to the decay time at low frequencies. + /// + public float HfDecayRatio; + + /// + /// Gain of the room effect. + /// + public float RoomGain; + + /// + /// Gain of the early reflections relative to . + /// + public float ReflectionsGain; + + /// + /// Gain of the late reverberation relative to . + /// + public float ReverbGain; + + /// + /// Echo density in the late reverberation decay. + /// + public float Diffusion; + + /// + /// Modal density in the late reverberation decay. + /// + public float ReflectionDelay; + + /// + /// Time limit between the early reflections and the late reverberation relative to the time of the first reflection. + /// + public float ReverbDelayTime; + + /// + /// Modal density in the late reverberation decay. + /// + public float Density; + + /// + /// The dry gain. + /// + public float DryGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState ParameterStatus; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs new file mode 100644 index 000000000..51ab156d2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs @@ -0,0 +1,119 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ReverbParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. (Q15) + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The early mode to use. + /// + public ReverbEarlyMode EarlyMode; + + /// + /// The gain to apply to the result of the early reflection. (Q15) + /// + public int EarlyGain; + + /// + /// The pre-delay time in milliseconds. (Q15) + /// + public int PreDelayTime; + + /// + /// The late mode to use. + /// + public ReverbLateMode LateMode; + + /// + /// The gain to apply to the result of the late reflection. (Q15) + /// + public int LateGain; + + /// + /// The decay time. (Q15) + /// + public int DecayTime; + + /// + /// The high frequency decay ratio. (Q15) + /// + /// If >= 0.995f, it is considered disabled. + public int HighFrequencyDecayRatio; + + /// + /// The coloration of the decay. (Q15) + /// + public int Coloration; + + /// + /// The reverb gain. (Q15) + /// + public int ReverbGain; + + /// + /// The output gain. (Q15) + /// + public int OutGain; + + /// + /// The dry gain. (Q15) + /// + public int DryGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs new file mode 100644 index 000000000..46686e3b4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs @@ -0,0 +1,97 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for an effect version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectInParameterVersion1 : IEffectInParameter + { + /// + /// Type of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the effect must be active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEnabled; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved1; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferBase; + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferSize; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Reserved/padding. + /// + private readonly uint _reserved2; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)] + private struct SpecificDataStruct { } + + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + + readonly EffectType IEffectInParameter.Type => Type; + + readonly bool IEffectInParameter.IsNew => IsNew; + + readonly bool IEffectInParameter.IsEnabled => IsEnabled; + + readonly int IEffectInParameter.MixId => MixId; + + readonly ulong IEffectInParameter.BufferBase => BufferBase; + + readonly ulong IEffectInParameter.BufferSize => BufferSize; + + readonly uint IEffectInParameter.ProcessingOrder => ProcessingOrder; + + /// + /// Check if the given channel count is valid. + /// + /// The channel count to check + /// Returns true if the channel count is valid. + public static bool IsChannelCountValid(int channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs new file mode 100644 index 000000000..3854c7148 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs @@ -0,0 +1,97 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for an effect version 2. (added with REV9) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectInParameterVersion2 : IEffectInParameter + { + /// + /// Type of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the effect must be active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEnabled; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved1; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferBase; + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferSize; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Reserved/padding. + /// + private readonly uint _reserved2; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)] + private struct SpecificDataStruct { } + + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + + readonly EffectType IEffectInParameter.Type => Type; + + readonly bool IEffectInParameter.IsNew => IsNew; + + readonly bool IEffectInParameter.IsEnabled => IsEnabled; + + readonly int IEffectInParameter.MixId => MixId; + + readonly ulong IEffectInParameter.BufferBase => BufferBase; + + readonly ulong IEffectInParameter.BufferSize => BufferSize; + + readonly uint IEffectInParameter.ProcessingOrder => ProcessingOrder; + + /// + /// Check if the given channel count is valid. + /// + /// The channel count to check + /// Returns true if the channel count is valid. + public static bool IsChannelCountValid(int channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs new file mode 100644 index 000000000..3c3e95538 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for an effect version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectOutStatusVersion1 : IEffectOutStatus + { + /// + /// Current effect state. + /// + public EffectState State; + + /// + /// Unused/Reserved. + /// + private unsafe fixed byte _reserved[15]; + + EffectState IEffectOutStatus.State { readonly get => State; set => State = value; } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs new file mode 100644 index 000000000..ee058d3ae --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for an effect version 2. (added with REV9) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectOutStatusVersion2 : IEffectOutStatus + { + /// + /// Current effect state. + /// + public EffectState State; + + /// + /// Unused/Reserved. + /// + private unsafe fixed byte _reserved[15]; + + /// + /// Current result state. + /// + public EffectResultState ResultState; + + EffectState IEffectOutStatus.State { readonly get => State; set => State = value; } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs new file mode 100644 index 000000000..b3a4bae12 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs @@ -0,0 +1,26 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Effect result state (added in REV9). + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectResultState + { + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 1)] + private struct SpecificDataStruct { } + + /// + /// Specific data changing depending of the type of effect. See also the namespace. + /// + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs new file mode 100644 index 000000000..c4d06f122 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// The state of an effect. + /// + public enum EffectState : byte + { + /// + /// The effect is enabled. + /// + Enabled = 3, + + /// + /// The effect is disabled. + /// + Disabled = 4, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs new file mode 100644 index 000000000..703c3e6db --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs @@ -0,0 +1,53 @@ +using Ryujinx.Audio.Renderer.Common; +using System; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface to represent input information for an effect. + /// + public interface IEffectInParameter + { + /// + /// Type of the effect. + /// + EffectType Type { get; } + + /// + /// Set to true if the effect is new. + /// + bool IsNew { get; } + + /// + /// Set to true if the effect must be active. + /// + bool IsEnabled { get; } + + /// + /// The target mix id of the effect. + /// + int MixId { get; } + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + ulong BufferBase { get; } + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + ulong BufferSize { get; } + + /// + /// Position of the effect while processing effects. + /// + uint ProcessingOrder { get; } + + /// + /// Specific data changing depending of the . See also the namespace. + /// + Span SpecificData { get; } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs new file mode 100644 index 000000000..74d132209 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface to represent output information for an effect. + /// + public interface IEffectOutStatus + { + /// + /// Current effect state. + /// + EffectState State { get; set; } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs new file mode 100644 index 000000000..602508589 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs @@ -0,0 +1,33 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MemoryPoolInParameter + { + /// + /// The CPU address used by the memory pool. + /// + public CpuAddress CpuAddress; + + /// + /// The size used by the memory pool. + /// + public ulong Size; + + /// + /// The target state the user wants. + /// + public MemoryPoolUserState State; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs new file mode 100644 index 000000000..a78937d01 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs @@ -0,0 +1,22 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MemoryPoolOutStatus + { + /// + /// The current server memory pool state. + /// + public MemoryPoolUserState State; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs b/src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs new file mode 100644 index 000000000..733b5ad76 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs @@ -0,0 +1,27 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information header for mix updates on REV7 and later + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MixInParameterDirtyOnlyUpdate + { + /// + /// Magic of the header + /// + /// Never checked on hardware. + public uint Magic; + + /// + /// The count of following this header. + /// + public uint MixCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed byte _reserved[24]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs new file mode 100644 index 000000000..2eec04a21 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs @@ -0,0 +1,95 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a mix. + /// + /// Also used on the client side for mix tracking. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MixParameter + { + /// + /// Base volume of the mix. + /// + public float Volume; + + /// + /// Target sample rate of the mix. + /// + public uint SampleRate; + + /// + /// Target buffer count. + /// + public uint BufferCount; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if it was changed. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsDirty; + + /// + /// Reserved/padding. + /// + private readonly ushort _reserved1; + + /// + /// The id of the mix. + /// + public int MixId; + + /// + /// The effect count. (client side) + /// + public uint EffectCount; + + /// + /// The mix node id. + /// + public int NodeId; + + /// + /// Reserved/padding. + /// + private readonly ulong _reserved2; + + /// + /// Mix buffer volumes storage. + /// + private MixVolumeArray _mixBufferVolumeArray; + + /// + /// The mix to output the result of this mix. + /// + public int DestinationMixId; + + /// + /// The splitter to output the result of this mix. + /// + public uint DestinationSplitterId; + + /// + /// Reserved/padding. + /// + private readonly uint _reserved3; + + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax * Constants.MixBufferCountMax, Pack = 1)] + private struct MixVolumeArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when no splitter id is specified. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolumeArray); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs new file mode 100644 index 000000000..806f7fa89 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Performance +{ + /// + /// Input information for performance monitoring. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PerformanceInParameter + { + /// + /// The target node id to monitor performance on. + /// + public int TargetNodeId; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs new file mode 100644 index 000000000..839d6eb6b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Performance +{ + /// + /// Output information for performance monitoring. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PerformanceOutStatus + { + /// + /// Indicates the total size output to the performance buffer. + /// + public uint HistorySize; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs new file mode 100644 index 000000000..c97ce2965 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Renderer output information on REV5 and later. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RendererInfoOutStatus + { + /// + /// The count of updates sent to the . + /// + public ulong ElapsedFrameCount; + + /// + /// Reserved/Unused. + /// + private readonly ulong _reserved; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs new file mode 100644 index 000000000..0d4b276ef --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs @@ -0,0 +1,61 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Parameter.Sink +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CircularBufferParameter + { + /// + /// The CPU address of the user circular buffer. + /// + public CpuAddress BufferAddress; + + /// + /// The size of the user circular buffer. + /// + public uint BufferSize; + + /// + /// The total count of channels to output to the circular buffer. + /// + public uint InputCount; + + /// + /// The target sample count to output per update in the circular buffer. + /// + public uint SampleCount; + + /// + /// Last read offset on the CPU side. + /// + public uint LastReadOffset; + + /// + /// The target . + /// + /// Only is supported. + public SampleFormat SampleFormat; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved1[3]; + + /// + /// The input channels index that will be used. + /// + public Array6 Input; + + /// + /// Reserved/padding. + /// + private readonly ushort _reserved2; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs new file mode 100644 index 000000000..652d02a63 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs @@ -0,0 +1,58 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Sink +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DeviceParameter + { + /// + /// Device name storage. + /// + private DeviceNameStruct _deviceName; + + /// + /// Reserved/padding. + /// + private readonly byte _padding; + + /// + /// The total count of channels to output to the device. + /// + public uint InputCount; + + /// + /// The input channels index that will be used. + /// + public Array6 Input; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved; + + /// + /// Set to true if the user controls Surround to Stereo downmixing coefficients. + /// + [MarshalAs(UnmanagedType.I1)] + public bool DownMixParameterEnabled; + + /// + /// The user Surround to Stereo downmixing coefficients. + /// + public Array4 DownMixParameter; + + [StructLayout(LayoutKind.Sequential, Size = 0xFF, Pack = 1)] + private struct DeviceNameStruct { } + + /// + /// The output device name. + /// + public Span DeviceName => SpanHelpers.AsSpan(ref _deviceName); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs new file mode 100644 index 000000000..3c1ac09c0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs @@ -0,0 +1,53 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a sink. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SinkInParameter + { + /// + /// Type of the sink. + /// + public SinkType Type; + + /// + /// Set to true if the sink is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private readonly ushort _reserved1; + + /// + /// The node id of the sink. + /// + public int NodeId; + + /// + /// Reserved/padding. + /// + private unsafe fixed ulong _reserved2[3]; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0x120, Pack = 1)] + private struct SpecificDataStruct { } + + /// + /// Specific data changing depending of the . See also the namespace. + /// + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs new file mode 100644 index 000000000..dd0f867b5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for a sink. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SinkOutStatus + { + /// + /// Last written offset if the sink type is . + /// + public uint LastWrittenOffset; + + /// + /// Reserved/padding. + /// + private readonly uint _padding; + + /// + /// Reserved/padding. + /// + private unsafe fixed ulong _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs new file mode 100644 index 000000000..b74b67be0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs @@ -0,0 +1,67 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter destination update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterDestinationInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter destination data id. + /// + public int Id; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mixBufferVolume; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved[3]; + + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume); + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x44444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public readonly bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs new file mode 100644 index 000000000..2567b15a8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs @@ -0,0 +1,46 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter state update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter id. + /// + public int Id; + + /// + /// Target sample rate to use on the splitter. + /// + public uint SampleRate; + + /// + /// Count of splitter destinations. + /// + /// Splitter destination ids are defined right after this header. + public int DestinationCount; + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x49444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public readonly bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs new file mode 100644 index 000000000..10fa866e7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs @@ -0,0 +1,45 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for splitter update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterInParameterHeader + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// The count of after the header. + /// + public uint SplitterCount; + + /// + /// The count of splitter destinations after the header and splitter info. + /// + public uint SplitterDestinationCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[5]; + + /// + /// The expected constant of any input splitter header. + /// + private const uint ValidMagic = 0x48444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public readonly bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs new file mode 100644 index 000000000..6cff1a251 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs @@ -0,0 +1,28 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a voice channel resources. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)] + public struct VoiceChannelResourceInParameter + { + /// + /// The id of the voice channel resource. + /// + public uint Id; + + /// + /// Mix volumes for the voice channel resource. + /// + public Array24 Mix; + + /// + /// Indicate if the voice channel resource is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs new file mode 100644 index 000000000..f33d82aa0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs @@ -0,0 +1,332 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a voice. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)] + public struct VoiceInParameter + { + /// + /// Id of the voice. + /// + public int Id; + + /// + /// Node id of the voice. + /// + public int NodeId; + + /// + /// Set to true if the voice is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the voice is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool InUse; + + /// + /// The voice wanted by the user. + /// + public PlayState PlayState; + + /// + /// The of the voice. + /// + public SampleFormat SampleFormat; + + /// + /// The sample rate of the voice. + /// + public uint SampleRate; + + /// + /// The priority of the voice. + /// + public uint Priority; + + /// + /// Target sorting position of the voice. (Used to sort voices with the same ) + /// + public uint SortingOrder; + + /// + /// The total channel count used. + /// + public uint ChannelCount; + + /// + /// The pitch used on the voice. + /// + public float Pitch; + + /// + /// The output volume of the voice. + /// + public float Volume; + + /// + /// Biquad filters to apply to the output of the voice. + /// + public Array2 BiquadFilters; + + /// + /// Total count of of the voice. + /// + public uint WaveBuffersCount; + + /// + /// Current playing of the voice. + /// + public uint WaveBuffersIndex; + + /// + /// Reserved/unused. + /// + private readonly uint _reserved1; + + /// + /// User state address required by the data source. + /// + /// Only used for as the address of the GC-ADPCM coefficients. + public ulong DataSourceStateAddress; + + /// + /// User state size required by the data source. + /// + /// Only used for as the size of the GC-ADPCM coefficients. + public ulong DataSourceStateSize; + + /// + /// The target mix id of the voice. + /// + public int MixId; + + /// + /// The target splitter id of the voice. + /// + public uint SplitterId; + + /// + /// The wavebuffer parameters of this voice. + /// + public Array4 WaveBuffers; + + /// + /// The channel resource ids associated to the voice. + /// + public Array6 ChannelResourceIds; + + /// + /// Reset the voice drop flag during voice server update. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ResetVoiceDropFlag; + + /// + /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. + /// + /// This was added on REV5. + public byte FlushWaveBufferCount; + + /// + /// Reserved/unused. + /// + private readonly ushort _reserved2; + + /// + /// Change the behaviour of the voice. + /// + /// This was added on REV5. + public DecodingBehaviour DecodingBehaviourFlags; + + /// + /// Change the Sample Rate Conversion (SRC) quality of the voice. + /// + /// This was added on REV8. + public SampleRateConversionQuality SrcQuality; + + /// + /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3. + /// + public uint ExternalContext; + + /// + /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3. + /// + public uint ExternalContextSize; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved3[2]; + + /// + /// Input information for a voice wavebuffer. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)] + public struct WaveBufferInternal + { + /// + /// Address of the wavebuffer data. + /// + public ulong Address; + + /// + /// Size of the wavebuffer data. + /// + public ulong Size; + + /// + /// Offset of the first sample to play. + /// + public uint StartSampleOffset; + + /// + /// Offset of the last sample to play. + /// + public uint EndSampleOffset; + + /// + /// If set to true, the wavebuffer will loop when reaching . + /// + /// + /// Starting with REV8, you can specify how many times to loop the wavebuffer () and where it should start and end when looping ( and ) + /// + [MarshalAs(UnmanagedType.I1)] + public bool ShouldLoop; + + /// + /// Indicates that this is the last wavebuffer to play of the voice. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Indicates if the server should update its internal state. + /// + [MarshalAs(UnmanagedType.I1)] + public bool SentToServer; + + /// + /// Reserved/unused. + /// + private readonly byte _reserved; + + /// + /// If set to anything other than 0, specifies how many times to loop the wavebuffer. + /// + /// This was added in REV8. + public int LoopCount; + + /// + /// Address of the context used by the sample decoder. + /// + /// This is only currently used by . + public ulong ContextAddress; + + /// + /// Size of the context used by the sample decoder. + /// + /// This is only currently used by . + public ulong ContextSize; + + /// + /// If set to anything other than 0, specifies the offset of the first sample to play when looping. + /// + /// This was added in REV8. + public uint LoopFirstSampleOffset; + + /// + /// If set to anything other than 0, specifies the offset of the last sample to play when looping. + /// + /// This was added in REV8. + public uint LoopLastSampleOffset; + + /// + /// Check if the sample offsets are in a valid range for generic PCM. + /// + /// The PCM sample type + /// Returns true if the sample offset are in range of the size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly bool IsSampleOffsetInRangeForPcm() where T : unmanaged + { + uint dataTypeSize = (uint)Unsafe.SizeOf(); + + return (ulong)StartSampleOffset * dataTypeSize <= Size && + (ulong)EndSampleOffset * dataTypeSize <= Size; + } + + /// + /// Check if the sample offsets are in a valid range for the given . + /// + /// The target + /// Returns true if the sample offset are in range of the size. + public readonly bool IsSampleOffsetValid(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt16 => IsSampleOffsetInRangeForPcm(), + SampleFormat.PcmFloat => IsSampleOffsetInRangeForPcm(), + SampleFormat.Adpcm => AdpcmHelper.GetAdpcmDataSize((int)StartSampleOffset) <= Size && AdpcmHelper.GetAdpcmDataSize((int)EndSampleOffset) <= Size, + _ => throw new NotImplementedException($"{format} not implemented!"), + }; + } + } + + /// + /// Flag altering the behaviour of wavebuffer decoding. + /// + [Flags] + public enum DecodingBehaviour : ushort + { + /// + /// Default decoding behaviour. + /// + Default = 0, + + /// + /// Reset the played samples accumulator when looping. + /// + PlayedSampleCountResetWhenLooping = 1, + + /// + /// Skip pitch and Sample Rate Conversion (SRC). + /// + SkipPitchAndSampleRateConversion = 2, + } + + /// + /// Specify the quality to use during Sample Rate Conversion (SRC) and pitch handling. + /// + /// This was added in REV8. + public enum SampleRateConversionQuality : byte + { + /// + /// Resample interpolating 4 samples per output sample. + /// + Default, + + /// + /// Resample interpolating 8 samples per output sample. + /// + High, + + /// + /// Resample interpolating 1 samples per output sample. + /// + Low, + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs new file mode 100644 index 000000000..a7c749835 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs @@ -0,0 +1,35 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information about a voice. + /// + /// See + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct VoiceOutStatus + { + /// + /// The total amount of samples that was played. + /// + /// This is reset to 0 when a finishes playing and is set. + /// This is reset to 0 when looping while is set. + public ulong PlayedSampleCount; + + /// + /// The total amount of consumed. + /// + public uint PlayedWaveBuffersCount; + + /// + /// If set to true, the voice was dropped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropFlag; + + /// + /// Reserved/unused. + /// + private unsafe fixed byte _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs new file mode 100644 index 000000000..7bb8ae5ba --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs @@ -0,0 +1,871 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Parameter; +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.Types; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Threading; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class AudioRenderSystem : IDisposable + { + private readonly object _lock = new(); + + private AudioRendererRenderingDevice _renderingDevice; + private AudioRendererExecutionMode _executionMode; + private readonly IWritableEvent _systemEvent; + private MemoryPoolState _dspMemoryPoolState; + private readonly VoiceContext _voiceContext; + private readonly MixContext _mixContext; + private readonly SinkContext _sinkContext; + private readonly SplitterContext _splitterContext; + private readonly EffectContext _effectContext; + private PerformanceManager _performanceManager; + private UpsamplerManager _upsamplerManager; + private bool _isActive; + private BehaviourContext _behaviourContext; +#pragma warning disable IDE0052 // Remove unread private member + private ulong _totalElapsedTicksUpdating; + private ulong _totalElapsedTicks; +#pragma warning restore IDE0052 + private int _sessionId; + private Memory _memoryPools; + + private uint _sampleRate; + private uint _sampleCount; + private uint _mixBufferCount; + private uint _voiceChannelCountMax; + private uint _upsamplerCount; + private uint _memoryPoolCount; + private uint _processHandle; + private ulong _appletResourceId; + + private MemoryHandle _workBufferMemoryPin; + + private Memory _mixBuffer; + private Memory _depopBuffer; + + private uint _renderingTimeLimitPercent; + private bool _voiceDropEnabled; + private uint _voiceDropCount; + private float _voiceDropParameter; + private bool _isDspRunningBehind; + + private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; + + private Memory _performanceBuffer; + + public IVirtualMemoryManager MemoryManager { get; private set; } + + private ulong _elapsedFrameCount; + private ulong _renderingStartTick; + + private readonly AudioRendererManager _manager; + + private int _disposeState; + + public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent) + { + _manager = manager; + _dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + _voiceContext = new VoiceContext(); + _mixContext = new MixContext(); + _sinkContext = new SinkContext(); + _splitterContext = new SplitterContext(); + _effectContext = new EffectContext(); + + _commandProcessingTimeEstimator = null; + _systemEvent = systemEvent; + _behaviourContext = new BehaviourContext(); + + _totalElapsedTicksUpdating = 0; + _sessionId = 0; + _voiceDropParameter = 1.0f; + } + + public ResultCode Initialize( + ref AudioRendererConfiguration parameter, + uint processHandle, + Memory workBufferMemory, + CpuAddress workBuffer, + ulong workBufferSize, + int sessionId, + ulong appletResourceId, + IVirtualMemoryManager memoryManager) + { + if (!BehaviourContext.CheckValidRevision(parameter.Revision)) + { + return ResultCode.OperationFailed; + } + + if (GetWorkBufferSize(ref parameter) > workBufferSize) + { + return ResultCode.WorkBufferTooSmall; + } + + Debug.Assert(parameter.RenderingDevice == AudioRendererRenderingDevice.Dsp && parameter.ExecutionMode == AudioRendererExecutionMode.Auto); + + Logger.Info?.Print(LogClass.AudioRenderer, $"Initializing with REV{BehaviourContext.GetRevisionNumber(parameter.Revision)}"); + + _behaviourContext.SetUserRevision(parameter.Revision); + + _sampleRate = parameter.SampleRate; + _sampleCount = parameter.SampleCount; + _mixBufferCount = parameter.MixBufferCount; + _voiceChannelCountMax = Constants.VoiceChannelCountMax; + _upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount; + _appletResourceId = appletResourceId; + _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount; + _renderingDevice = parameter.RenderingDevice; + _executionMode = parameter.ExecutionMode; + _sessionId = sessionId; + MemoryManager = memoryManager; + + if (memoryManager is IRefCounted rc) + { + rc.IncrementReferenceCount(); + } + + WorkBufferAllocator workBufferAllocator; + + workBufferMemory.Span.Clear(); + _workBufferMemoryPin = workBufferMemory.Pin(); + + workBufferAllocator = new WorkBufferAllocator(workBufferMemory); + + PoolMapper poolMapper = new(processHandle, false); + poolMapper.InitializeSystemPool(ref _dspMemoryPoolState, workBuffer, workBufferSize); + + _mixBuffer = workBufferAllocator.Allocate(_sampleCount * (_voiceChannelCountMax + _mixBufferCount), 0x10); + + if (_mixBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + Memory upSamplerWorkBuffer = workBufferAllocator.Allocate(Constants.TargetSampleCount * (_voiceChannelCountMax + _mixBufferCount) * _upsamplerCount, 0x10); + + if (upSamplerWorkBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _depopBuffer = workBufferAllocator.Allocate(BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment); + + if (_depopBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Invalidate DSP cache on what was currently allocated with workBuffer. + AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset); + + Debug.Assert((workBufferAllocator.Offset % Constants.BufferAlignment) == 0); + + Memory voices = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceState.Alignment); + + if (voices.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + foreach (ref VoiceState voice in voices.Span) + { + voice.Initialize(); + } + + // A pain to handle as we can't have VoiceState*, use indices to be a bit more safe + Memory sortedVoices = workBufferAllocator.Allocate(parameter.VoiceCount, 0x10); + + if (sortedVoices.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Clear memory (use -1 as it's an invalid index) + sortedVoices.Span.Fill(-1); + + Memory voiceChannelResources = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceChannelResource.Alignment); + + if (voiceChannelResources.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + for (uint id = 0; id < voiceChannelResources.Length; id++) + { + ref VoiceChannelResource voiceChannelResource = ref voiceChannelResources.Span[(int)id]; + + voiceChannelResource.Id = id; + voiceChannelResource.IsUsed = false; + } + + Memory voiceUpdateStates = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceUpdateState.Align); + + if (voiceUpdateStates.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + uint mixesCount = parameter.SubMixBufferCount + 1; + + Memory mixes = workBufferAllocator.Allocate(mixesCount, MixState.Alignment); + + if (mixes.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + if (parameter.EffectCount == 0) + { + foreach (ref MixState mix in mixes.Span) + { + mix = new MixState(Memory.Empty, ref _behaviourContext); + } + } + else + { + Memory effectProcessingOrderArray = workBufferAllocator.Allocate(parameter.EffectCount * mixesCount, 0x10); + + foreach (ref MixState mix in mixes.Span) + { + mix = new MixState(effectProcessingOrderArray[..(int)parameter.EffectCount], ref _behaviourContext); + + effectProcessingOrderArray = effectProcessingOrderArray[(int)parameter.EffectCount..]; + } + } + + // Initialize the final mix id + mixes.Span[0].MixId = Constants.FinalMixId; + + Memory sortedMixesState = workBufferAllocator.Allocate(mixesCount, 0x10); + + if (sortedMixesState.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Clear memory (use -1 as it's an invalid index) + sortedMixesState.Span.Fill(-1); + + Memory nodeStatesWorkBuffer = Memory.Empty; + Memory edgeMatrixWorkBuffer = Memory.Empty; + + if (_behaviourContext.IsSplitterSupported()) + { + nodeStatesWorkBuffer = workBufferAllocator.Allocate((uint)NodeStates.GetWorkBufferSize((int)mixesCount), 1); + edgeMatrixWorkBuffer = workBufferAllocator.Allocate((uint)EdgeMatrix.GetWorkBufferSize((int)mixesCount), 1); + + if (nodeStatesWorkBuffer.IsEmpty || edgeMatrixWorkBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + } + + _mixContext.Initialize(sortedMixesState, mixes, nodeStatesWorkBuffer, edgeMatrixWorkBuffer); + + _memoryPools = workBufferAllocator.Allocate(_memoryPoolCount, MemoryPoolState.Alignment); + + if (_memoryPools.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + foreach (ref MemoryPoolState state in _memoryPools.Span) + { + state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + } + + if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator)) + { + return ResultCode.WorkBufferTooSmall; + } + + _processHandle = processHandle; + + _upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount); + + _effectContext.Initialize(parameter.EffectCount, _behaviourContext.IsEffectInfoVersion2Supported() ? parameter.EffectCount : 0); + _sinkContext.Initialize(parameter.SinkCount); + + Memory voiceUpdateStatesDsp = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceUpdateState.Align); + + if (voiceUpdateStatesDsp.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _voiceContext.Initialize(sortedVoices, voices, voiceChannelResources, voiceUpdateStates, voiceUpdateStatesDsp, parameter.VoiceCount); + + if (parameter.PerformanceMetricFramesCount > 0) + { + ulong performanceBufferSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref _behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; + + _performanceBuffer = workBufferAllocator.Allocate(performanceBufferSize, Constants.BufferAlignment); + + if (_performanceBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _performanceManager = PerformanceManager.Create(_performanceBuffer, ref parameter, _behaviourContext); + } + else + { + _performanceManager = null; + } + + _totalElapsedTicksUpdating = 0; + _totalElapsedTicks = 0; + _renderingTimeLimitPercent = 100; + _voiceDropEnabled = parameter.VoiceDropEnabled && _executionMode == AudioRendererExecutionMode.Auto; + + AudioProcessorMemoryManager.InvalidateDataCache(workBuffer, workBufferSize); + + _processHandle = processHandle; + _elapsedFrameCount = 0; + _voiceDropParameter = 1.0f; + + _commandProcessingTimeEstimator = _behaviourContext.GetCommandProcessingTimeEstimatorVersion() switch + { + 1 => new CommandProcessingTimeEstimatorVersion1(_sampleCount, _mixBufferCount), + 2 => new CommandProcessingTimeEstimatorVersion2(_sampleCount, _mixBufferCount), + 3 => new CommandProcessingTimeEstimatorVersion3(_sampleCount, _mixBufferCount), + 4 => new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount), + 5 => new CommandProcessingTimeEstimatorVersion5(_sampleCount, _mixBufferCount), + _ => throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}."), + }; + + return ResultCode.Success; + } + + public void Start() + { + Logger.Info?.Print(LogClass.AudioRenderer, $"Starting renderer id {_sessionId}"); + + lock (_lock) + { + _elapsedFrameCount = 0; + _isActive = true; + } + } + + public void Stop() + { + Logger.Info?.Print(LogClass.AudioRenderer, $"Stopping renderer id {_sessionId}"); + + lock (_lock) + { + _isActive = false; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}"); + } + + public void Disable() + { + lock (_lock) + { + _isActive = false; + } + } + + public ResultCode Update(Memory output, Memory performanceOutput, ReadOnlyMemory input) + { + lock (_lock) + { + ulong updateStartTicks = GetSystemTicks(); + + output.Span.Clear(); + + StateUpdater stateUpdater = new(input, output, _processHandle, _behaviourContext); + + ResultCode result; + + result = stateUpdater.UpdateBehaviourContext(); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateMemoryPools(_memoryPools.Span); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateVoiceChannelResources(_voiceContext); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools); + + if (result != ResultCode.Success) + { + return result; + } + + if (_behaviourContext.IsSplitterSupported()) + { + result = stateUpdater.UpdateSplitter(_splitterContext); + + if (result != ResultCode.Success) + { + return result; + } + } + + result = stateUpdater.UpdateMixes(_mixContext, GetMixBufferCount(), _effectContext, _splitterContext); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdatePerformanceBuffer(_performanceManager, performanceOutput.Span); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateErrorInfo(); + + if (result != ResultCode.Success) + { + return result; + } + + if (_behaviourContext.IsElapsedFrameCountSupported()) + { + result = stateUpdater.UpdateRendererInfo(_elapsedFrameCount); + + if (result != ResultCode.Success) + { + return result; + } + } + + result = stateUpdater.CheckConsumedSize(); + + if (result != ResultCode.Success) + { + return result; + } + + _systemEvent.Clear(); + + ulong updateEndTicks = GetSystemTicks(); + + _totalElapsedTicksUpdating += (updateEndTicks - updateStartTicks); + + return result; + } + } + + private ulong GetSystemTicks() + { + return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency); + } + + private uint ComputeVoiceDrop(CommandBuffer commandBuffer, uint voicesEstimatedTime, long deltaTimeDsp) + { + int i; + + for (i = 0; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand command = commandBuffer.CommandList.Commands[i]; + + CommandType commandType = command.CommandType; + + if (commandType == CommandType.AdpcmDataSourceVersion1 || + commandType == CommandType.AdpcmDataSourceVersion2 || + commandType == CommandType.PcmInt16DataSourceVersion1 || + commandType == CommandType.PcmInt16DataSourceVersion2 || + commandType == CommandType.PcmFloatDataSourceVersion1 || + commandType == CommandType.PcmFloatDataSourceVersion2 || + commandType == CommandType.Performance) + { + break; + } + } + + uint voiceDropped = 0; + + for (; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand targetCommand = commandBuffer.CommandList.Commands[i]; + + int targetNodeId = targetCommand.NodeId; + + if (voicesEstimatedTime <= deltaTimeDsp || NodeIdHelper.GetType(targetNodeId) != NodeIdType.Voice) + { + break; + } + + ref VoiceState voice = ref _voiceContext.GetState(NodeIdHelper.GetBase(targetNodeId)); + + if (voice.Priority == Constants.VoiceHighestPriority) + { + break; + } + + // We can safely drop this voice, disable all associated commands while activating depop preparation commands. + voiceDropped++; + voice.VoiceDropFlag = true; + + Logger.Warning?.Print(LogClass.AudioRenderer, $"Dropping voice {voice.NodeId}"); + + for (; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand command = commandBuffer.CommandList.Commands[i]; + + if (command.NodeId != targetNodeId) + { + break; + } + + if (command.CommandType == CommandType.DepopPrepare) + { + command.Enabled = true; + } + else if (command.CommandType == CommandType.Performance || !command.Enabled) + { + continue; + } + else + { + command.Enabled = false; + + voicesEstimatedTime -= (uint)(_voiceDropParameter * command.EstimatedProcessingTime); + } + } + } + + return voiceDropped; + } + + private void GenerateCommandList(out CommandList commandList) + { + Debug.Assert(_executionMode == AudioRendererExecutionMode.Auto); + + PoolMapper.ClearUsageState(_memoryPools); + + ulong startTicks = GetSystemTicks(); + + commandList = new CommandList(this); + + if (_performanceManager != null) + { + _performanceManager.TapFrame(_isDspRunningBehind, _voiceDropCount, _renderingStartTick); + + _isDspRunningBehind = false; + _voiceDropCount = 0; + _renderingStartTick = 0; + } + + CommandBuffer commandBuffer = new(commandList, _commandProcessingTimeEstimator); + + CommandGenerator commandGenerator = new(commandBuffer, GetContext(), _voiceContext, _mixContext, _effectContext, _sinkContext, _splitterContext, _performanceManager); + + _voiceContext.Sort(); + commandGenerator.GenerateVoices(); + + uint voicesEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime); + + commandGenerator.GenerateSubMixes(); + commandGenerator.GenerateFinalMixes(); + commandGenerator.GenerateSinks(); + + uint totalEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime); + + if (_voiceDropEnabled) + { + long maxDspTime = GetMaxAllocatedTimeForDsp(); + + long restEstimateTime = totalEstimatedTime - voicesEstimatedTime; + + long deltaTimeDsp = Math.Max(maxDspTime - restEstimateTime, 0); + + _voiceDropCount = ComputeVoiceDrop(commandBuffer, voicesEstimatedTime, deltaTimeDsp); + } + + _voiceContext.UpdateForCommandGeneration(); + + if (_behaviourContext.IsEffectInfoVersion2Supported()) + { + _effectContext.UpdateResultStateForCommandGeneration(); + } + + ulong endTicks = GetSystemTicks(); + + _totalElapsedTicks = endTicks - startTicks; + + _renderingStartTick = GetSystemTicks(); + _elapsedFrameCount++; + } + + private int GetMaxAllocatedTimeForDsp() + { + return (int)(Constants.AudioProcessorMaxUpdateTimePerSessions * _behaviourContext.GetAudioRendererProcessingTimeLimit() * (GetRenderingTimeLimit() / 100.0f)); + } + + public void SendCommands() + { + lock (_lock) + { + if (_isActive) + { + if (!_manager.Processor.HasRemainingCommands(_sessionId)) + { + GenerateCommandList(out CommandList commands); + + _manager.Processor.Send(_sessionId, + commands, + GetMaxAllocatedTimeForDsp(), + _appletResourceId); + + _systemEvent.Signal(); + } + else + { + _isDspRunningBehind = true; + } + } + } + } + + public uint GetMixBufferCount() + { + return _mixBufferCount; + } + + public void SetRenderingTimeLimitPercent(uint percent) + { + Debug.Assert(percent <= 100); + + _renderingTimeLimitPercent = percent; + } + + public uint GetRenderingTimeLimit() + { + return _renderingTimeLimitPercent; + } + + public Memory GetMixBuffer() + { + return _mixBuffer; + } + + public uint GetSampleCount() + { + return _sampleCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public uint GetVoiceChannelCountMax() + { + return _voiceChannelCountMax; + } + + public bool IsActive() + { + return _isActive; + } + + private RendererSystemContext GetContext() + { + return new RendererSystemContext + { + ChannelCount = _manager.Processor.OutputDevices[_sessionId].GetChannelCount(), + BehaviourContext = _behaviourContext, + DepopBuffer = _depopBuffer, + MixBufferCount = GetMixBufferCount(), + SessionId = _sessionId, + UpsamplerManager = _upsamplerManager, + }; + } + + public int GetSessionId() + { + return _sessionId; + } + + public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(parameter.Revision); + + uint mixesCount = parameter.SubMixBufferCount + 1; + + uint memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount; + + ulong size = 0; + + // Mix Buffers + size = WorkBufferAllocator.GetTargetSize(size, parameter.SampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount), 0x10); + + // Upsampler workbuffer + size = WorkBufferAllocator.GetTargetSize(size, Constants.TargetSampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10); + + // Depop buffer + size = WorkBufferAllocator.GetTargetSize(size, BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment); + + // Voice + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, 0x10); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceChannelResource.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align); + + // Mix + size = WorkBufferAllocator.GetTargetSize(size, mixesCount, MixState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.EffectCount * mixesCount, 0x10); + size = WorkBufferAllocator.GetTargetSize(size, mixesCount, 0x10); + + if (behaviourContext.IsSplitterSupported()) + { + size += (ulong)BitUtils.AlignUp(NodeStates.GetWorkBufferSize((int)mixesCount) + EdgeMatrix.GetWorkBufferSize((int)mixesCount), 0x10); + } + + // Memory Pool + size = WorkBufferAllocator.GetTargetSize(size, memoryPoolCount, MemoryPoolState.Alignment); + + // Splitter + size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter); + + // DSP Voice + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align); + + // Performance + if (parameter.PerformanceMetricFramesCount > 0) + { + ulong performanceMetricsPerFramesSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; + + size += BitUtils.AlignUp(performanceMetricsPerFramesSize, Constants.PerformanceMetricsPerFramesSizeAlignment); + } + + return BitUtils.AlignUp(size, Constants.WorkBufferAlignment); + } + + public ResultCode QuerySystemEvent(out IWritableEvent systemEvent) + { + systemEvent = default; + + if (_executionMode == AudioRendererExecutionMode.Manual) + { + return ResultCode.UnsupportedOperation; + } + + systemEvent = _systemEvent; + + return ResultCode.Success; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_isActive) + { + Stop(); + } + + PoolMapper mapper = new(_processHandle, false); + mapper.Unmap(ref _dspMemoryPoolState); + + PoolMapper.ClearUsageState(_memoryPools); + + for (int i = 0; i < _memoryPoolCount; i++) + { + ref MemoryPoolState memoryPool = ref _memoryPools.Span[i]; + + if (memoryPool.IsMapped()) + { + mapper.Unmap(ref memoryPool); + } + } + + _manager.Unregister(this); + _workBufferMemoryPin.Dispose(); + + if (MemoryManager is IRefCounted rc) + { + rc.DecrementReferenceCount(); + + MemoryManager = null; + } + } + } + + public void SetVoiceDropParameter(float voiceDropParameter) + { + _voiceDropParameter = Math.Clamp(voiceDropParameter, 0.0f, 2.0f); + } + + public float GetVoiceDropParameter() + { + return _voiceDropParameter; + } + + public ResultCode ExecuteAudioRendererRendering() + { + if (_executionMode == AudioRendererExecutionMode.Manual && _renderingDevice == AudioRendererRenderingDevice.Cpu) + { + // NOTE: Here Nintendo aborts with this error code, we don't want that. + return ResultCode.InvalidExecutionContextOperation; + } + + return ResultCode.UnsupportedOperation; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs new file mode 100644 index 000000000..0dbbd26c8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs @@ -0,0 +1,407 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// The audio renderer manager. + /// + public class AudioRendererManager : IDisposable + { + /// + /// Lock used for session allocation. + /// + private readonly object _sessionLock = new(); + + /// + /// Lock used to control the running state. + /// + private readonly object _audioProcessorLock = new(); + + /// + /// The session ids allocation table. + /// + private readonly int[] _sessionIds; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsSystemEvent; + + /// + /// The sessions instances. + /// + private readonly AudioRenderSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The worker thread used to run . + /// + private Thread _workerThread; + + /// + /// Indicate if the worker thread and are running. + /// + private bool _isRunning; + + /// + /// The audio device driver to create audio outputs. + /// + private IHardwareDeviceDriver _deviceDriver; + + /// + /// Tick source used to measure elapsed time. + /// + public ITickSource TickSource { get; } + + /// + /// The instance associated to this manager. + /// + public AudioProcessor Processor { get; } + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + /// Tick source used to measure elapsed time. + public AudioRendererManager(ITickSource tickSource) + { + Processor = new AudioProcessor(); + TickSource = tickSource; + _sessionIds = new int[Constants.AudioRendererSessionCountMax]; + _sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The events associated to each session. + /// The device driver to use to create audio outputs. + public void Initialize(IWritableEvent[] sessionSystemEvents, IHardwareDeviceDriver deviceDriver) + { + _sessionsSystemEvent = sessionSystemEvents; + _deviceDriver = deviceDriver; + } + + /// + /// Get the work buffer size required by a session. + /// + /// The user configuration + /// The work buffer size required by a session. + public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + return AudioRenderSystem.GetWorkBufferSize(ref parameter); + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new renderer ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered renderer ({sessionId})"); + } + + /// + /// Check if there is any audio renderer active. + /// + /// Returns true if there is any audio renderer active. + private bool HasAnyActiveRendererLocked() + { + foreach (AudioRenderSystem renderer in _sessions) + { + if (renderer != null) + { + return true; + } + } + + return false; + } + + /// + /// Start the and worker thread. + /// + private void StartLocked(float volume) + { + _isRunning = true; + + // TODO: virtual device mapping (IAudioDevice) + Processor.Start(_deviceDriver, volume); + + _workerThread = new Thread(SendCommands) + { + Name = "AudioRendererManager.Worker", + }; + + _workerThread.Start(); + } + + /// + /// Stop the and worker thread. + /// + private void StopLocked() + { + _isRunning = false; + + _workerThread.Join(); + Processor.Stop(); + + Logger.Info?.Print(LogClass.AudioRenderer, "Stopped audio renderer"); + } + + /// + /// Stop sending commands to the without stopping the worker thread. + /// + public void StopSendingCommands() + { + lock (_sessionLock) + { + foreach (AudioRenderSystem renderer in _sessions) + { + renderer?.Disable(); + } + } + + lock (_audioProcessorLock) + { + if (_isRunning) + { + StopLocked(); + } + } + } + + /// + /// Worker main function. This is used to dispatch audio renderer commands to the . + /// + private void SendCommands() + { + Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio renderer"); + Processor.Wait(); + + while (_isRunning) + { + lock (_sessionLock) + { + foreach (AudioRenderSystem renderer in _sessions) + { + renderer?.SendCommands(); + } + } + + Processor.Signal(); + Processor.Wait(); + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioRenderSystem renderer, float volume) + { + lock (_sessionLock) + { + _sessions[renderer.GetSessionId()] = renderer; + } + + lock (_audioProcessorLock) + { + if (!_isRunning) + { + StartLocked(volume); + } + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioRenderSystem renderer) + { + lock (_sessionLock) + { + int sessionId = renderer.GetSessionId(); + + _sessions[renderer.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + + lock (_audioProcessorLock) + { + if (_isRunning && !HasAnyActiveRendererLocked()) + { + StopLocked(); + } + } + } + + /// + /// Open a new + /// + /// The new + /// The memory manager that will be used for all guest memory operations. + /// The user configuration + /// The applet resource user id of the application. + /// The guest work buffer address. + /// The guest work buffer size. + /// The process handle of the application. + /// A reporting an error or a success. + public ResultCode OpenAudioRenderer( + out AudioRenderSystem renderer, + IVirtualMemoryManager memoryManager, + ref AudioRendererConfiguration parameter, + ulong appletResourceUserId, + ulong workBufferAddress, + ulong workBufferSize, + uint processHandle, + float volume) + { + int sessionId = AcquireSessionId(); + + AudioRenderSystem audioRenderer = new(this, _sessionsSystemEvent[sessionId]); + + // TODO: Eventually, we should try to use the guest supplied work buffer instead of allocating + // our own. However, it was causing problems on some applications that would unmap the memory + // before the audio renderer was fully disposed. + Memory workBufferMemory = GC.AllocateArray((int)workBufferSize, pinned: true); + + ResultCode result = audioRenderer.Initialize( + ref parameter, + processHandle, + workBufferMemory, + workBufferAddress, + workBufferSize, + sessionId, + appletResourceUserId, + memoryManager); + + if (result == ResultCode.Success) + { + renderer = audioRenderer; + + Register(renderer, volume); + } + else + { + ReleaseSessionId(sessionId); + + renderer = null; + } + + return result; + } + + public float GetVolume() + { + if (Processor != null) + { + return Processor.GetVolume(); + } + + return 0f; + } + + public void SetVolume(float volume) + { + Processor?.SetVolume(volume); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Clone the sessions array to dispose them outside the lock. + AudioRenderSystem[] sessions; + + lock (_sessionLock) + { + sessions = _sessions.ToArray(); + } + + foreach (AudioRenderSystem renderer in sessions) + { + renderer?.Dispose(); + } + + lock (_audioProcessorLock) + { + if (_isRunning) + { + StopLocked(); + } + } + + Processor.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs new file mode 100644 index 000000000..3297b5d9f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -0,0 +1,452 @@ +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Behaviour context. + /// + /// This handles features based on the audio renderer revision provided by the user. + public class BehaviourContext + { + /// + /// The base magic of the Audio Renderer revision. + /// + public const int BaseRevisionMagic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24); + + /// + /// REV1: first revision. + /// + public const int Revision1 = 1 << 24; + + /// + /// REV2: Added support for splitter and fix GC-ADPCM context not being provided to the DSP. + /// + /// This was added in system update 2.0.0 + public const int Revision2 = 2 << 24; + + /// + /// REV3: Incremented the max pre-delay from 150 to 350 for the reverb command and removed the (unused) codec system. + /// + /// This was added in system update 3.0.0 + public const int Revision3 = 3 << 24; + + /// + /// REV4: Added USB audio device support and incremented the rendering limit percent to 75%. + /// + /// This was added in system update 4.0.0 + public const int Revision4 = 4 << 24; + + /// + /// REV5: , were added to voice. + /// A new performance frame format (version 2) was added with support for more information about DSP timing. + /// was added to supply the count of update done sent to the DSP. + /// A new version of the command estimator was added to address timing changes caused by the voice changes. + /// Additionally, the rendering limit percent was incremented to 80%. + /// + /// + /// This was added in system update 6.0.0 + public const int Revision5 = 5 << 24; + + /// + /// REV6: This fixed a bug in the biquad filter command not clearing up with usage state. + /// + /// This was added in system update 6.1.0 + public const int Revision6 = 6 << 24; + + /// + /// REV7: Client side (finally) doesn't send all the mix client state to the server and can do partial updates. + /// + /// This was added in system update 8.0.0 + public const int Revision7 = 7 << 24; + + /// + /// REV8: + /// Wavebuffer was changed to support more control over loop (you can now specify where to start and end a loop, and how many times to loop). + /// was added (see for more info). + /// Final leftovers of the codec system were removed. + /// support was added. + /// A new version of the command estimator was added to address timing changes caused by the voice and command changes. + /// + /// This was added in system update 9.0.0 + public const int Revision8 = 8 << 24; + + /// + /// REV9: + /// EffectInfo parameters were revisited with a new revision (version 2) allowing more data control between the client and server. + /// A new effect was added: Limiter. This effect is effectively implemented with a DRC while providing statistics on the processing on . + /// + /// This was added in system update 12.0.0 + public const int Revision9 = 9 << 24; + + /// + /// REV10: + /// Added Bluetooth audio device support and removed the unused "GetAudioSystemMasterVolumeSetting" audio device API. + /// A new effect was added: Capture. This effect allows the client side to capture audio buffers of a mix. + /// A new command was added for double biquad filters on voices. This is implemented using a direct form 1 (instead of the usual direct form 2). + /// A new version of the command estimator was added to support the new commands. + /// + /// This was added in system update 13.0.0 + public const int Revision10 = 10 << 24; + + /// + /// REV11: + /// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer. + /// A new effect was added: Compressor. This effect is effectively implemented with a DRC. + /// A new version of the command estimator was added to address timing changes caused by the legacy effects changes. + /// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands. + /// + /// This was added in system update 14.0.0 but some changes were made in 15.0.0 + public const int Revision11 = 11 << 24; + + /// + /// Last revision supported by the implementation. + /// + public const int LastRevision = Revision11; + + /// + /// Target revision magic supported by the implementation. + /// + public const int ProcessRevision = BaseRevisionMagic + LastRevision; + + /// + /// Get the revision number from the revision magic. + /// + /// The revision magic. + /// The revision number. + public static int GetRevisionNumber(int revision) => (revision - BaseRevisionMagic) >> 24; + + /// + /// Current active revision. + /// + public int UserRevision { get; private set; } + + /// + /// Error storage. + /// + private readonly ErrorInfo[] _errorInfos; + + /// + /// Current position in the array. + /// + private uint _errorIndex; + + /// + /// Current flags of the . + /// + private ulong _flags; + + /// + /// Create a new instance of . + /// + public BehaviourContext() + { + UserRevision = 0; + _errorInfos = new ErrorInfo[Constants.MaxErrorInfos]; + _errorIndex = 0; + } + + /// + /// Set the active revision. + /// + /// The active revision. + public void SetUserRevision(int userRevision) + { + UserRevision = userRevision; + } + + /// + /// Update flags of the . + /// + /// The new flags. + public void UpdateFlags(ulong flags) + { + _flags = flags; + } + + /// + /// Check if a given revision is valid/supported. + /// + /// The revision magic to check. + /// Returns true if the given revision is valid/supported + public static bool CheckValidRevision(int revision) + { + return GetRevisionNumber(revision) <= GetRevisionNumber(ProcessRevision); + } + + /// + /// Check if the given revision is greater than or equal the supported revision. + /// + /// The revision magic to check. + /// The revision magic of the supported revision. + /// Returns true if the given revision is greater than or equal the supported revision. + public static bool CheckFeatureSupported(int revision, int supportedRevision) + { + int revA = GetRevisionNumber(revision); + int revB = GetRevisionNumber(supportedRevision); + + if (revA > LastRevision) + { + revA = 1; + } + + if (revB > LastRevision) + { + revB = 1; + } + + return revA >= revB; + } + + /// + /// Check if the memory pool mapping bypass flag is active. + /// + /// True if the memory pool mapping bypass flag is active. + public bool IsMemoryPoolForceMappingEnabled() + { + return (_flags & 1) != 0; + } + + /// + /// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP. + /// + /// True if if the audio renderer should fix it. + public bool IsAdpcmLoopContextBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2); + } + + /// + /// Check if the audio renderer should accept splitters. + /// + /// True if the audio renderer should accept splitters. + public bool IsSplitterSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2); + } + + /// + /// Check if the audio renderer should use a max pre-delay of 350 instead of 150. + /// + /// True if the max pre-delay must be 350. + public bool IsLongSizePreDelaySupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision3); + } + + /// + /// Check if the audio renderer should expose USB audio device. + /// + /// True if the audio renderer should expose USB audio device. + public bool IsAudioUsbDeviceOutputSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4); + } + + /// + /// Get the percentage allocated to the audio renderer on the DSP for processing. + /// + /// The percentage allocated to the audio renderer on the DSP for processing. + public float GetAudioRendererProcessingTimeLimit() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 0.80f; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4)) + { + return 0.75f; + } + + return 0.70f; + } + + /// + /// Check if the audio render should support voice flushing. + /// + /// True if the audio render should support voice flushing. + public bool IsFlushVoiceWaveBuffersSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should trust the user destination count in . + /// + /// True if the audio renderer should trust the user destination count. + public bool IsSplitterBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should supply the elapsed frame count to the user when updating. + /// + /// True if the audio renderer should supply the elapsed frame count to the user when updating. + public bool IsElapsedFrameCountSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Get the performance metric data format version. + /// + /// The performance metric data format version. + public uint GetPerformanceMetricsDataFormat() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 2; + } + + return 1; + } + + /// + /// Check if the audio renderer should support . + /// + /// True if the audio renderer should support . + public bool IsDecodingBehaviourFlagSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should fix the biquad filter command not clearing up with usage state. + /// + /// True if the biquad filter state should be cleared. + public bool IsBiquadFilterEffectStateClearBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision6); + } + + /// + /// Check if the audio renderer should accept partial mix updates. + /// + /// True if the audio renderer should accept partial mix updates. + public bool IsMixInParameterDirtyOnlyUpdateSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision7); + } + + /// + /// Check if the audio renderer should use the new wavebuffer format. + /// + /// True if the audio renderer should use the new wavebuffer format. + public bool IsWaveBufferVersion2Supported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8); + } + + /// + /// Check if the audio renderer should use the new effect info format. + /// + /// True if the audio renderer should use the new effect info format. + public bool IsEffectInfoVersion2Supported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9); + } + + /// + /// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice. + /// + /// True if the audio renderer should use the optimization. + public bool IsBiquadFilterGroupedOptimizationSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10); + } + + /// + /// Check if the audio renderer should support new channel resource mapping for 5.1 on Delay, Reverb and Reverb 3D effects. + /// + /// True if the audio renderer support new channel resource mapping for 5.1. + public bool IsNewEffectChannelMappingSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11); + } + + /// + /// Get the version of the . + /// + /// The version of the . + public int GetCommandProcessingTimeEstimatorVersion() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11)) + { + return 5; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10)) + { + return 4; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8)) + { + return 3; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 2; + } + + return 1; + } + + /// + /// Append a new to the error array. + /// + /// The new to add. + public void AppendError(ref ErrorInfo errorInfo) + { + Debug.Assert(errorInfo.ErrorCode == ResultCode.Success); + + if (_errorIndex <= Constants.MaxErrorInfos - 1) + { + _errorInfos[_errorIndex++] = errorInfo; + } + } + + /// + /// Copy the internal array to the given and output the count copied. + /// + /// The output . + /// The output error count containing the count of copied. + public void CopyErrorInfo(Span errorInfos, out uint errorCount) + { + if (errorInfos.Length != Constants.MaxErrorInfos) + { + throw new ArgumentException("Invalid size of errorInfos span!"); + } + + errorCount = Math.Min(_errorIndex, Constants.MaxErrorInfos); + + for (int i = 0; i < Constants.MaxErrorInfos; i++) + { + if (i < errorCount) + { + errorInfos[i] = _errorInfos[i]; + } + else + { + errorInfos[i] = new ErrorInfo + { + ErrorCode = 0, + ExtraErrorInfo = 0, + }; + } + } + } + + /// + /// Clear the array. + /// + public void ClearError() + { + _errorIndex = 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs new file mode 100644 index 000000000..f4174a913 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs @@ -0,0 +1,568 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// An API to generate commands and aggregate them into a . + /// + public class CommandBuffer + { + /// + /// The command processing time estimator in use. + /// + private readonly ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; + + /// + /// The estimated total processing time. + /// + public uint EstimatedProcessingTime { get; set; } + + /// + /// The command list that is populated by the . + /// + public CommandList CommandList { get; } + + /// + /// Create a new . + /// + /// The command list that will store the generated commands. + /// The command processing time estimator to use. + public CommandBuffer(CommandList commandList, ICommandProcessingTimeEstimator commandProcessingTimeEstimator) + { + CommandList = commandList; + EstimatedProcessingTime = 0; + _commandProcessingTimeEstimator = commandProcessingTimeEstimator; + } + + /// + /// Add a new generated command to the . + /// + /// The command to add. + private void AddCommand(ICommand command) + { + EstimatedProcessingTime += command.EstimatedProcessingTime; + + CommandList.AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The node id associated to this command. + public void GenerateClearMixBuffer(int nodeId) + { + ClearMixBufferCommand command = new(nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The voice state associated. + /// The depop buffer. + /// The buffer count. + /// The target buffer offset. + /// The node id associated to this command. + /// Set to true if the voice was playing previously. + public void GenerateDepopPrepare(Memory state, Memory depopBuffer, uint bufferCount, uint bufferOffset, int nodeId, bool wasPlaying) + { + DepopPrepareCommand command = new(state, depopBuffer, bufferCount, bufferOffset, nodeId, wasPlaying); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The . + /// The performance operation to perform. + /// The node id associated to this command. + public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId) + { + PerformanceCommand command = new(ref performanceEntryAddresses, type, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The previous volume. + /// The new volume. + /// The index of the mix buffer to use. + /// The node id associated to this command. + public void GenerateVolumeRamp(float previousVolume, float volume, uint bufferIndex, int nodeId) + { + VolumeRampCommand command = new(previousVolume, volume, bufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GenerateDataSourceVersion2(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + DataSourceVersion2Command command = new(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmInt16DataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmInt16DataSourceCommandVersion1 command = new(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmFloatDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmFloatDataSourceCommandVersion1 command = new(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The node id associated to this command. + public void GenerateAdpcmDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, int nodeId) + { + AdpcmDataSourceCommandVersion1 command = new(ref voiceState, state, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The base index of the input and output buffer. + /// The biquad filter parameter. + /// The biquad state. + /// The input buffer offset. + /// The output buffer offset. + /// Set to true if the biquad filter state needs to be initialized. + /// The node id associated to this command. + public void GenerateBiquadFilter(int baseIndex, ref BiquadFilterParameter filter, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId) + { + BiquadFilterCommand command = new(baseIndex, ref filter, biquadFilterStateMemory, inputBufferOffset, outputBufferOffset, needInitialization, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The base index of the input and output buffer. + /// The biquad filter parameters. + /// The biquad states. + /// The input buffer offset. + /// The output buffer offset. + /// Set to true if the biquad filter state is initialized. + /// The node id associated to this command. + public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) + { + GroupedBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The mix buffer count. + /// The base input index. + /// The base output index. + /// The previous volume. + /// The new volume. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span previousVolume, Span volume, Memory state, int nodeId) + { + MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The previous volume. + /// The new volume. + /// The input buffer index. + /// The output buffer index. + /// The index in the array to store the ramped sample. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRamp(float previousVolume, float volume, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory state, int nodeId) + { + MixRampCommand command = new(previousVolume, volume, inputBufferIndex, outputBufferIndex, lastSampleIndex, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The depop buffer. + /// The target buffer offset. + /// The buffer count. + /// The node id associated to this command. + /// The target sample rate in use. + public void GenerateDepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate) + { + DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + public void GenerateCopyMixBuffer(uint inputBufferIndex, uint outputBufferIndex, int nodeId) + { + CopyMixBufferCommand command = new(inputBufferIndex, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + /// The mix volume. + public void GenerateMix(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume) + { + MixCommand command = new(inputBufferIndex, outputBufferIndex, nodeId, volume); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb parameter. + /// The reverb state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the long size pre-delay is supported. + /// If set to true, the new effect channel mapping for 5.1 is supported. + public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported) + { + if (parameter.IsChannelCountValid()) + { + ReverbCommand command = new(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb 3d parameter. + /// The reverb 3d state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the new effect channel mapping for 5.1 is supported. + public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + if (parameter.IsChannelCountValid()) + { + Reverb3dCommand command = new(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The delay parameter. + /// The delay state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the new effect channel mapping for 5.1 is supported. + public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + if (parameter.IsChannelCountValid()) + { + DelayCommand command = new(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The limiter parameter. + /// The limiter state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateLimiterEffectVersion1(uint bufferOffset, LimiterParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + LimiterCommandVersion1 command = new(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The limiter parameter. + /// The limiter state. + /// The DSP effect result state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateLimiterEffectVersion2(uint bufferOffset, LimiterParameter parameter, Memory state, Memory effectResultState, bool isEnabled, ulong workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + LimiterCommandVersion2 command = new(bufferOffset, parameter, state, effectResultState, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The input buffer offset. + /// The output buffer offset. + /// The aux state. + /// Set to true if the effect should be active. + /// The limit of the circular buffer. + /// The guest address of the output buffer. + /// The guest address of the input buffer. + /// The count to add on the offset after write/read operations. + /// The write offset. + /// The node id associated to this command. + public void GenerateAuxEffect(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, ref AuxiliaryBufferAddresses state, bool isEnabled, uint countMax, CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + if (state.SendBufferInfoBase != 0 && state.ReturnBufferInfoBase != 0) + { + AuxiliaryBufferCommand command = new(bufferOffset, inputBufferOffset, outputBufferOffset, ref state, isEnabled, countMax, outputBuffer, inputBuffer, updateCount, writeOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The input buffer offset. + /// The capture state. + /// Set to true if the effect should be active. + /// The limit of the circular buffer. + /// The guest address of the output buffer. + /// The count to add on the offset after write operations. + /// The write offset. + /// The node id associated to this command. + public void GenerateCaptureEffect(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled, uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + if (sendBufferInfo != 0) + { + CaptureBufferCommand command = new(bufferOffset, inputBufferOffset, sendBufferInfo, isEnabled, countMax, outputBuffer, updateCount, writeOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + CompressorCommand command = new(bufferOffset, parameter, state, isEnabled, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target volume to apply. + /// The offset of the mix buffer. + /// The node id associated to this command. + public void GenerateVolume(float volume, uint bufferOffset, int nodeId) + { + VolumeCommand command = new(volume, bufferOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the circular buffer. + /// The node id associated to this command. + public void GenerateCircularBuffer(uint bufferOffset, CircularBufferSink sink, int nodeId) + { + CircularBufferSinkCommand command = new(bufferOffset, ref sink.Parameter, ref sink.CircularBufferAddressInfo, sink.CurrentWriteOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The input buffer offset. + /// The output buffer offset. + /// The downmixer parameters to use. + /// The node id associated to this command. + public void GenerateDownMixSurroundToStereo(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, float[] downMixParameter, int nodeId) + { + DownMixSurroundToStereoCommand command = new(bufferOffset, inputBufferOffset, outputBufferOffset, downMixParameter, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The associated. + /// The total input count. + /// The input buffer mix offset. + /// The buffer count per sample. + /// The source sample count. + /// The source sample rate. + /// The node id associated to this command. + public void GenerateUpsample(uint bufferOffset, UpsamplerState upsampler, uint inputCount, Span inputBufferOffset, uint bufferCountPerSample, uint sampleCount, uint sampleRate, int nodeId) + { + UpsampleCommand command = new(bufferOffset, upsampler, inputCount, inputBufferOffset, bufferCountPerSample, sampleCount, sampleRate, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the device sink. + /// The current audio renderer session id. + /// The mix buffer in use. + /// The node id associated to this command. + public void GenerateDeviceSink(uint bufferOffset, DeviceSink sink, int sessionId, Memory buffer, int nodeId) + { + DeviceSinkCommand command = new(bufferOffset, sink, sessionId, buffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs new file mode 100644 index 000000000..ae8f699f3 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs @@ -0,0 +1,1030 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +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 System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class CommandGenerator + { + private readonly CommandBuffer _commandBuffer; + private readonly RendererSystemContext _rendererContext; + private readonly VoiceContext _voiceContext; + private readonly MixContext _mixContext; + private readonly EffectContext _effectContext; + private readonly SinkContext _sinkContext; + private readonly SplitterContext _splitterContext; + private readonly PerformanceManager _performanceManager; + + public CommandGenerator(CommandBuffer commandBuffer, RendererSystemContext rendererContext, VoiceContext voiceContext, MixContext mixContext, EffectContext effectContext, SinkContext sinkContext, SplitterContext splitterContext, PerformanceManager performanceManager) + { + _commandBuffer = commandBuffer; + _rendererContext = rendererContext; + _voiceContext = voiceContext; + _mixContext = mixContext; + _effectContext = effectContext; + _sinkContext = sinkContext; + _splitterContext = splitterContext; + _performanceManager = performanceManager; + + _commandBuffer.GenerateClearMixBuffer(Constants.InvalidNodeId); + } + + private void GenerateDataSource(ref VoiceState voiceState, Memory dspState, int channelIndex) + { + if (voiceState.MixId != Constants.UnusedMixId) + { + ref MixState mix = ref _mixContext.GetState(voiceState.MixId); + + _commandBuffer.GenerateDepopPrepare(dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); + } + else if (voiceState.SplitterId != Constants.UnusedSplitterId) + { + int destinationId = 0; + + while (true) + { + Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++); + + if (destinationSpan.IsEmpty) + { + break; + } + + ref SplitterDestination destination = ref destinationSpan[0]; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) + { + ref MixState mix = ref _mixContext.GetState(mixId); + + _commandBuffer.GenerateDepopPrepare(dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); + + destination.MarkAsNeedToUpdateInternalState(); + } + } + } + } + + if (!voiceState.WasPlaying) + { + Debug.Assert(voiceState.SampleFormat != SampleFormat.Adpcm || channelIndex == 0); + + if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported()) + { + _commandBuffer.GenerateDataSourceVersion2(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + } + else + { + switch (voiceState.SampleFormat) + { + case SampleFormat.PcmInt16: + _commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + break; + case SampleFormat.PcmFloat: + _commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + break; + case SampleFormat.Adpcm: + _commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + voiceState.NodeId); + break; + default: + throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}"); + } + } + } + } + + private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory state, int baseIndex, int bufferOffset, int nodeId) + { + bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported(); + + if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable) + { + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)]; + Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); + + _commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId); + } + else + { + for (int i = 0; i < voiceState.BiquadFilters.Length; i++) + { + ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i]; + + if (filter.Enable) + { + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)]; + + Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); + + _commandBuffer.GenerateBiquadFilter(baseIndex, + ref filter, + stateMemory.Slice(i, 1), + bufferOffset, + bufferOffset, + !voiceState.BiquadFilterNeedInitialization[i], + nodeId); + } + } + } + } + + private void GenerateVoiceMix(Span mixVolumes, Span previousMixVolumes, Memory state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId) + { + if (bufferCount > Constants.VoiceChannelCountMax) + { + _commandBuffer.GenerateMixRampGrouped(bufferCount, + bufferIndex, + bufferOffset, + previousMixVolumes, + mixVolumes, + state, + nodeId); + } + else + { + for (int i = 0; i < bufferCount; i++) + { + float previousMixVolume = previousMixVolumes[i]; + float mixVolume = mixVolumes[i]; + + if (mixVolume != 0.0f || previousMixVolume != 0.0f) + { + _commandBuffer.GenerateMixRamp(previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + nodeId); + } + } + } + } + + private void GenerateVoice(ref VoiceState voiceState) + { + int nodeId = voiceState.NodeId; + uint channelsCount = voiceState.ChannelsCount; + + for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++) + { + Memory dspStateMemory = _voiceContext.GetUpdateStateForDsp(voiceState.ChannelResourceIds[channelIndex]); + + ref VoiceChannelResource channelResource = ref _voiceContext.GetChannelResource(voiceState.ChannelResourceIds[channelIndex]); + + PerformanceDetailType dataSourceDetailType = PerformanceDetailType.Adpcm; + + if (voiceState.SampleFormat == SampleFormat.PcmInt16) + { + dataSourceDetailType = PerformanceDetailType.PcmInt16; + } + else if (voiceState.SampleFormat == SampleFormat.PcmFloat) + { + dataSourceDetailType = PerformanceDetailType.PcmFloat; + } + + bool performanceInitialized = false; + + PerformanceEntryAddresses performanceEntry = new(); + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, dataSourceDetailType, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateDataSource(ref voiceState, dspStateMemory, channelIndex); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + if (voiceState.WasPlaying) + { + voiceState.PreviousVolume = 0.0f; + } + else if (voiceState.HasAnyDestination()) + { + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.BiquadFilter, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateBiquadFilterForVoice(ref voiceState, dspStateMemory, (int)_rendererContext.MixBufferCount, channelIndex, nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + _commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume, + voiceState.Volume, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + voiceState.PreviousVolume = voiceState.Volume; + + if (voiceState.MixId == Constants.UnusedMixId) + { + if (voiceState.SplitterId != Constants.UnusedSplitterId) + { + int destinationId = channelIndex; + + while (true) + { + Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId); + + if (destinationSpan.IsEmpty) + { + break; + } + + ref SplitterDestination destination = ref destinationSpan[0]; + + destinationId += (int)channelsCount; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) + { + ref MixState mix = ref _mixContext.GetState(mixId); + + GenerateVoiceMix(destination.MixBufferVolume, + destination.PreviousMixBufferVolume, + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + destination.MarkAsNeedToUpdateInternalState(); + } + } + } + } + } + else + { + ref MixState mix = ref _mixContext.GetState(voiceState.MixId); + + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateVoiceMix(channelResource.Mix.AsSpan(), + channelResource.PreviousMix.AsSpan(), + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + channelResource.UpdateState(); + } + + for (int i = 0; i < voiceState.BiquadFilterNeedInitialization.Length; i++) + { + voiceState.BiquadFilterNeedInitialization[i] = voiceState.BiquadFilters[i].Enable; + } + } + } + } + + public void GenerateVoices() + { + for (int i = 0; i < _voiceContext.GetCount(); i++) + { + ref VoiceState sortedState = ref _voiceContext.GetSortedState(i); + + if (!sortedState.ShouldSkip() && sortedState.UpdateForCommandGeneration(_voiceContext)) + { + int nodeId = sortedState.NodeId; + + PerformanceEntryAddresses performanceEntry = new(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateVoice(ref sortedState); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + + _splitterContext.UpdateInternalState(); + } + + public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId) + { + _commandBuffer.GeneratePerformance(ref performanceEntryAddresses, type, nodeId); + } + + private void GenerateBufferMixerEffect(int bufferOffset, BufferMixEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.BufferMix); + + if (effect.IsEnabled) + { + for (int i = 0; i < effect.Parameter.MixesCount; i++) + { + if (effect.Parameter.Volumes[i] != 0.0f) + { + _commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i], + (uint)bufferOffset + effect.Parameter.Output[i], + nodeId, + effect.Parameter.Volumes[i]); + } + } + } + } + + private void GenerateAuxEffect(uint bufferOffset, AuxiliaryBufferEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.AuxiliaryBuffer); + + if (effect.IsEnabled) + { + effect.GetWorkBuffer(0); + effect.GetWorkBuffer(1); + } + + if (effect.State.SendBufferInfoBase != 0 && effect.State.ReturnBufferInfoBase != 0) + { + int i = 0; + uint writeOffset = 0; + for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--) + { + uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount; + + uint updateCount; + + if (channelIndex != 1) + { + updateCount = 0; + } + else + { + updateCount = newUpdateCount; + } + + _commandBuffer.GenerateAuxEffect(bufferOffset, + effect.Parameter.Input[i], + effect.Parameter.Output[i], + ref effect.State, + effect.IsEnabled, + effect.Parameter.BufferStorageSize, + effect.State.SendBufferInfoBase, + effect.State.ReturnBufferInfoBase, + updateCount, + writeOffset, + nodeId); + + writeOffset = newUpdateCount; + + i++; + } + } + } + + private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId, bool newEffectChannelMappingSupported) + { + Debug.Assert(effect.Type == EffectType.Delay); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + } + + private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported) + { + Debug.Assert(effect.Type == EffectType.Reverb); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported); + } + + private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId, bool newEffectChannelMappingSupported) + { + Debug.Assert(effect.Type == EffectType.Reverb3d); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + } + + private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.BiquadFilter); + + if (effect.IsEnabled) + { + bool needInitialization = effect.Parameter.Status == UsageState.Invalid || + (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + + BiquadFilterParameter parameter = new() + { + Enable = true, + }; + + effect.Parameter.Denominator.AsSpan().CopyTo(parameter.Denominator.AsSpan()); + effect.Parameter.Numerator.AsSpan().CopyTo(parameter.Numerator.AsSpan()); + + for (int i = 0; i < effect.Parameter.ChannelCount; i++) + { + _commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1), + effect.Parameter.Input[i], + effect.Parameter.Output[i], + needInitialization, + nodeId); + } + } + else + { + for (int i = 0; i < effect.Parameter.ChannelCount; i++) + { + uint inputBufferIndex = bufferOffset + effect.Parameter.Input[i]; + uint outputBufferIndex = bufferOffset + effect.Parameter.Output[i]; + + // If the input and output isn't the same, generate a command. + if (inputBufferIndex != outputBufferIndex) + { + _commandBuffer.GenerateCopyMixBuffer(inputBufferIndex, outputBufferIndex, nodeId); + } + } + } + } + + private void GenerateLimiterEffect(uint bufferOffset, LimiterEffect effect, int nodeId, int effectId) + { + Debug.Assert(effect.Type == EffectType.Limiter); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + if (_rendererContext.BehaviourContext.IsEffectInfoVersion2Supported()) + { + Memory dspResultState; + + if (effect.Parameter.StatisticsEnabled) + { + dspResultState = _effectContext.GetDspStateMemory(effectId); + } + else + { + dspResultState = Memory.Empty; + } + + _commandBuffer.GenerateLimiterEffectVersion2(bufferOffset, effect.Parameter, effect.State, dspResultState, effect.IsEnabled, workBuffer, nodeId); + } + else + { + _commandBuffer.GenerateLimiterEffectVersion1(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId); + } + } + + private void GenerateCaptureEffect(uint bufferOffset, CaptureBufferEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.CaptureBuffer); + + if (effect.IsEnabled) + { + effect.GetWorkBuffer(0); + } + + if (effect.State.SendBufferInfoBase != 0) + { + int i = 0; + uint writeOffset = 0; + + for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--) + { + uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount; + + uint updateCount; + + if (channelIndex != 1) + { + updateCount = 0; + } + else + { + updateCount = newUpdateCount; + } + + _commandBuffer.GenerateCaptureEffect(bufferOffset, + effect.Parameter.Input[i], + effect.State.SendBufferInfo, + effect.IsEnabled, + effect.Parameter.BufferStorageSize, + effect.State.SendBufferInfoBase, + updateCount, + writeOffset, + nodeId); + + writeOffset = newUpdateCount; + + i++; + } + } + } + + private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.Compressor); + + _commandBuffer.GenerateCompressorEffect(bufferOffset, + effect.Parameter, + effect.State, + effect.IsEnabled, + nodeId); + } + + private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect) + { + int nodeId = mix.NodeId; + + bool isFinalMix = mix.MixId == Constants.FinalMixId; + + PerformanceEntryAddresses performanceEntry = new(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(), + isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + switch (effect.Type) + { + case EffectType.BufferMix: + GenerateBufferMixerEffect((int)mix.BufferOffset, (BufferMixEffect)effect, nodeId); + break; + case EffectType.AuxiliaryBuffer: + GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId); + break; + case EffectType.Delay: + GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported()); + break; + case EffectType.Reverb: + GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported()); + break; + case EffectType.Reverb3d: + GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported()); + break; + case EffectType.BiquadFilter: + GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId); + break; + case EffectType.Limiter: + GenerateLimiterEffect(mix.BufferOffset, (LimiterEffect)effect, nodeId, effectId); + break; + case EffectType.CaptureBuffer: + GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId); + break; + case EffectType.Compressor: + GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId); + break; + default: + throw new NotImplementedException($"Unsupported effect type {effect.Type}"); + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + effect.UpdateForCommandGeneration(); + } + + private void GenerateEffects(ref MixState mix) + { + ReadOnlySpan effectProcessingOrderArray = mix.EffectProcessingOrderArray; + + Debug.Assert(_effectContext.GetCount() == 0 || !effectProcessingOrderArray.IsEmpty); + + for (int i = 0; i < _effectContext.GetCount(); i++) + { + int effectOrder = effectProcessingOrderArray[i]; + + if (effectOrder == Constants.InvalidProcessingOrder) + { + break; + } + + // BaseEffect is a class, we don't need to pass it by ref + BaseEffect effect = _effectContext.GetEffect(effectOrder); + + Debug.Assert(effect.Type != EffectType.Invalid); + Debug.Assert(effect.MixId == mix.MixId); + + if (!effect.ShouldSkip()) + { + GenerateEffect(ref mix, effectOrder, effect); + } + } + } + + private void GenerateMix(ref MixState mix) + { + if (mix.HasAnyDestination()) + { + Debug.Assert(mix.DestinationMixId != Constants.UnusedMixId || mix.DestinationSplitterId != Constants.UnusedSplitterId); + + if (mix.DestinationMixId == Constants.UnusedMixId) + { + if (mix.DestinationSplitterId != Constants.UnusedSplitterId) + { + int destinationId = 0; + + while (true) + { + int destinationIndex = destinationId++; + + Span destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex); + + if (destinationSpan.IsEmpty) + { + break; + } + + ref SplitterDestination destination = ref destinationSpan[0]; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) + { + ref MixState destinationMix = ref _mixContext.GetState(mixId); + + uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount); + + for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) + { + float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex); + + if (volume != 0.0f) + { + _commandBuffer.GenerateMix(inputBufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); + } + } + } + } + } + } + } + else + { + ref MixState destinationMix = ref _mixContext.GetState(mix.DestinationMixId); + + for (uint bufferIndex = 0; bufferIndex < mix.BufferCount; bufferIndex++) + { + for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) + { + float volume = mix.Volume * mix.GetMixBufferVolume((int)bufferIndex, (int)bufferDestinationIndex); + + if (volume != 0.0f) + { + _commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); + } + } + } + } + } + } + + private void GenerateSubMix(ref MixState subMix) + { + _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer, + subMix.BufferOffset, + subMix.BufferCount, + subMix.NodeId, + subMix.SampleRate); + + GenerateEffects(ref subMix); + + PerformanceEntryAddresses performanceEntry = new(); + + int nodeId = subMix.NodeId; + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateMix(ref subMix); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + public void GenerateSubMixes() + { + for (int id = 0; id < _mixContext.GetCount(); id++) + { + ref MixState sortedState = ref _mixContext.GetSortedState(id); + + if (sortedState.IsUsed && sortedState.MixId != Constants.FinalMixId) + { + int nodeId = sortedState.NodeId; + + PerformanceEntryAddresses performanceEntry = new(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateSubMix(ref sortedState); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + } + + private void GenerateFinalMix() + { + ref MixState finalMix = ref _mixContext.GetFinalState(); + + _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer, + finalMix.BufferOffset, + finalMix.BufferCount, + finalMix.NodeId, + finalMix.SampleRate); + + GenerateEffects(ref finalMix); + + PerformanceEntryAddresses performanceEntry = new(); + + int nodeId = finalMix.NodeId; + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.FinalMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + // Only generate volume command if the volume isn't 100%. + if (finalMix.Volume != 1.0f) + { + for (uint bufferIndex = 0; bufferIndex < finalMix.BufferCount; bufferIndex++) + { + bool performanceSubInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.FinalMix, nodeId)) + { + performanceSubInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + _commandBuffer.GenerateVolume(finalMix.Volume, + finalMix.BufferOffset + bufferIndex, + nodeId); + + if (performanceSubInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + public void GenerateFinalMixes() + { + int nodeId = _mixContext.GetFinalState().NodeId; + + PerformanceEntryAddresses performanceEntry = new(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.FinalMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateFinalMix(); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + private void GenerateCircularBuffer(CircularBufferSink sink, ref MixState finalMix) + { + _commandBuffer.GenerateCircularBuffer(finalMix.BufferOffset, sink, Constants.InvalidNodeId); + } + + private void GenerateDevice(DeviceSink sink, ref MixState finalMix) + { + if (_commandBuffer.CommandList.SampleRate != 48000 && sink.UpsamplerState == null) + { + sink.UpsamplerState = _rendererContext.UpsamplerManager.Allocate(); + } + + bool useCustomDownMixingCommand = _rendererContext.ChannelCount == 2 && sink.Parameter.DownMixParameterEnabled; + + if (useCustomDownMixingCommand) + { + _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset, + sink.Parameter.Input.AsSpan(), + sink.Parameter.Input.AsSpan(), + sink.DownMixCoefficients, + Constants.InvalidNodeId); + } + // NOTE: We do the downmixing at the DSP level as it's easier that way. + else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6) + { + _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset, + sink.Parameter.Input.AsSpan(), + sink.Parameter.Input.AsSpan(), + Constants.DefaultSurroundToStereoCoefficients, + Constants.InvalidNodeId); + } + + CommandList commandList = _commandBuffer.CommandList; + + if (sink.UpsamplerState != null) + { + _commandBuffer.GenerateUpsample(finalMix.BufferOffset, + sink.UpsamplerState, + sink.Parameter.InputCount, + sink.Parameter.Input.AsSpan(), + commandList.BufferCount, + commandList.SampleCount, + commandList.SampleRate, + Constants.InvalidNodeId); + } + + _commandBuffer.GenerateDeviceSink(finalMix.BufferOffset, + sink, + _rendererContext.SessionId, + commandList.Buffers, + Constants.InvalidNodeId); + } + + private void GenerateSink(BaseSink sink, ref MixState finalMix) + { + bool performanceInitialized = false; + + PerformanceEntryAddresses performanceEntry = new(); + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Sink, sink.NodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, sink.NodeId); + } + + if (!sink.ShouldSkip) + { + switch (sink.Type) + { + case SinkType.CircularBuffer: + GenerateCircularBuffer((CircularBufferSink)sink, ref finalMix); + break; + case SinkType.Device: + GenerateDevice((DeviceSink)sink, ref finalMix); + break; + default: + throw new NotImplementedException($"Unsupported sink type {sink.Type}"); + } + + sink.UpdateForCommandGeneration(); + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, sink.NodeId); + } + } + + public void GenerateSinks() + { + ref MixState finalMix = ref _mixContext.GetFinalState(); + + for (int i = 0; i < _sinkContext.GetCount(); i++) + { + // BaseSink is a class, we don't need to pass it by ref + BaseSink sink = _sinkContext.GetSink(i); + + if (sink.IsUsed) + { + GenerateSink(sink, ref finalMix); + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs new file mode 100644 index 000000000..d95e9aa71 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs @@ -0,0 +1,188 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 1. + /// + public class CommandProcessingTimeEstimatorVersion1 : ICommandProcessingTimeEstimator + { + private readonly uint _sampleCount; + private readonly uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion1(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + return 1454; + } + + public uint Estimate(ClearMixBufferCommand command) + { + return (uint)(_sampleCount * 0.83f * _bufferCount * 1.2f); + } + + public uint Estimate(BiquadFilterCommand command) + { + return (uint)(_sampleCount * 58.0f * 1.2f); + } + + public uint Estimate(MixRampGroupedCommand command) + { + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * 14.4f * 1.2f * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + return (uint)(_sampleCount * 14.4f * 1.2f); + } + + public uint Estimate(DepopPrepareCommand command) + { + return 1080; + } + + public uint Estimate(VolumeRampCommand command) + { + return (uint)(_sampleCount * 9.8f * 1.2f); + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + return (uint)(command.Pitch * 0.25f * 1.2f); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + return (uint)(command.Pitch * 0.46f * 1.2f); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + return (uint)(_sampleCount * 8.9f * command.MixBufferCount); + } + + public uint Estimate(CopyMixBufferCommand command) + { + // NOTE: Nintendo returns 0 here for some reasons even if it will generate a command like that on version 1.. maybe a mistake? + return 0; + } + + public uint Estimate(MixCommand command) + { + return (uint)(_sampleCount * 10.0f * 1.2f); + } + + public uint Estimate(DelayCommand command) + { + return (uint)(_sampleCount * command.Parameter.ChannelCount * 202.5f); + } + + public uint Estimate(ReverbCommand command) + { + Debug.Assert(command.Parameter.IsChannelCountValid()); + + if (command.Enabled) + { + return (uint)(750 * _sampleCount * command.Parameter.ChannelCount * 1.2f); + } + + return 0; + } + + public uint Estimate(Reverb3dCommand command) + { + if (command.Enabled) + { + return (uint)(530 * _sampleCount * command.Parameter.ChannelCount * 1.2f); + } + + return 0; + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + if (command.Enabled) + { + return 15956; + } + + return 3765; + } + + public uint Estimate(VolumeCommand command) + { + return (uint)(_sampleCount * 8.8f * 1.2f); + } + + public uint Estimate(CircularBufferSinkCommand command) + { + return 55; + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + return 16108; + } + + public uint Estimate(UpsampleCommand command) + { + return 357915; + } + + public uint Estimate(DeviceSinkCommand command) + { + return 10042; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + return 0; + } + + public uint Estimate(DataSourceVersion2Command command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion1 command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion2 command) + { + return 0; + } + + public uint Estimate(GroupedBiquadFilterCommand command) + { + return 0; + } + + public uint Estimate(CaptureBufferCommand command) + { + return 0; + } + + public uint Estimate(CompressorCommand command) + { + return 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs new file mode 100644 index 000000000..929aaf383 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs @@ -0,0 +1,480 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 2. (added with REV5) + /// + public class CommandProcessingTimeEstimatorVersion2 : ICommandProcessingTimeEstimator + { + private readonly uint _sampleCount; + private readonly uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion2(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)489.35f; + } + + return (uint)491.18f; + } + + public uint Estimate(ClearMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 668.8f; + float baseCost = 193.2f; + + if (_sampleCount == 160) + { + costPerBuffer = 260.4f; + baseCost = 139.65f; + } + + return (uint)(baseCost + costPerBuffer * _bufferCount); + } + + public uint Estimate(BiquadFilterCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)4813.2f; + } + + return (uint)6915.4f; + } + + public uint Estimate(MixRampGroupedCommand command) + { + const float CostPerSample = 7.245f; + + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * CostPerSample * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1859.0f; + } + + return (uint)2286.1f; + } + + public uint Estimate(DepopPrepareCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)306.62f; + } + + return (uint)293.22f; + } + + public uint Estimate(VolumeRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1403.9f; + } + + return (uint)1884.3f; + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 1195.5f; + float baseCost = 7797.0f; + + if (_sampleCount == 160) + { + costPerSample = 749.27f; + baseCost = 6138.9f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3564.1f; + float baseCost = 6225.5f; + + if (_sampleCount == 160) + { + costPerSample = 2125.6f; + baseCost = 9039.5f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)762.96f; + } + + return (uint)726.96f; + } + + public uint Estimate(CopyMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)836.32f; + } + + return (uint)1000.9f; + } + + public uint Estimate(MixCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1342.2f; + } + + return (uint)1833.2f; + } + + public uint Estimate(DelayCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)41636.0f, + 2 => (uint)97861.0f, + 4 => (uint)192520.0f, + 6 => (uint)301760.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)578.53f, + 2 => (uint)663.06f, + 4 => (uint)703.98f, + 6 => (uint)760.03f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)8770.3f, + 2 => (uint)25741.0f, + 4 => (uint)47551.0f, + 6 => (uint)81629.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)521.28f, + 2 => (uint)585.4f, + 4 => (uint)629.88f, + 6 => (uint)713.57f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public uint Estimate(ReverbCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)97192.0f, + 2 => (uint)103280.0f, + 4 => (uint)109580.0f, + 6 => (uint)115070.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)492.01f, + 2 => (uint)554.46f, + 4 => (uint)595.86f, + 6 => (uint)656.62f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)136460.0f, + 2 => (uint)145750.0f, + 4 => (uint)154800.0f, + 6 => (uint)161970.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)495.79f, + 2 => (uint)527.16f, + 4 => (uint)598.75f, + 6 => (uint)666.03f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public uint Estimate(Reverb3dCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)138840.0f, + 2 => (uint)135430.0f, + 4 => (uint)199180.0f, + 6 => (uint)247350.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)718.7f, + 2 => (uint)751.3f, + 4 => (uint)797.46f, + 6 => (uint)867.43f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)199950.0f, + 2 => (uint)195200.0f, + 4 => (uint)290580.0f, + 6 => (uint)363490.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)534.24f, + 2 => (uint)570.87f, + 4 => (uint)660.93f, + 6 => (uint)694.6f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return (uint)7177.9f; + } + + return (uint)489.16f; + } + + if (command.Enabled) + { + return (uint)9499.8f; + } + + return (uint)485.56f; + } + + public uint Estimate(VolumeCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1280.3f; + } + + return (uint)1737.8f; + } + + public uint Estimate(CircularBufferSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 1726.0f; + float baseCost = 1369.7f; + + if (_sampleCount == 160) + { + costPerBuffer = 853.63f; + baseCost = 1284.5f; + } + + return (uint)(baseCost + costPerBuffer * command.InputCount); + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)10009.0f; + } + + return (uint)14577.0f; + } + + public uint Estimate(UpsampleCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)292000.0f; + } + + return (uint)0.0f; + } + + public uint Estimate(DeviceSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + Debug.Assert(command.InputCount == 2 || command.InputCount == 6); + + if (command.InputCount == 2) + { + if (_sampleCount == 160) + { + return (uint)9261.5f; + } + + return (uint)9336.1f; + } + + if (_sampleCount == 160) + { + return (uint)9111.8f; + } + + return (uint)9566.7f; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + // NOTE: This was added between REV7 and REV8 and for some reasons the estimator v2 was changed... + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3490.9f; + float baseCost = 10091.0f; + + if (_sampleCount == 160) + { + costPerSample = 2310.4f; + baseCost = 7845.3f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DataSourceVersion2Command command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion1 command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion2 command) + { + return 0; + } + + public uint Estimate(GroupedBiquadFilterCommand command) + { + return 0; + } + + public uint Estimate(CaptureBufferCommand command) + { + return 0; + } + + public uint Estimate(CompressorCommand command) + { + return 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs new file mode 100644 index 000000000..8ae4bc059 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs @@ -0,0 +1,650 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 3. (added with REV8) + /// + public class CommandProcessingTimeEstimatorVersion3 : ICommandProcessingTimeEstimator + { + protected uint SampleCount; + protected uint BufferCount; + + public CommandProcessingTimeEstimatorVersion3(uint sampleCount, uint bufferCount) + { + SampleCount = sampleCount; + BufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)498.17f; + } + + return (uint)489.42f; + } + + public uint Estimate(ClearMixBufferCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerBuffer = 440.68f; + float baseCost = 0; + + if (SampleCount == 160) + { + costPerBuffer = 266.65f; + } + + return (uint)(baseCost + costPerBuffer * BufferCount); + } + + public uint Estimate(BiquadFilterCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)4173.2f; + } + + return (uint)5585.1f; + } + + public uint Estimate(MixRampGroupedCommand command) + { + float costPerSample = 6.4434f; + + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + costPerSample = 6.708f; + } + + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(SampleCount * costPerSample * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)1968.7f; + } + + return (uint)2459.4f; + } + + public uint Estimate(DepopPrepareCommand command) + { + return 0; + } + + public uint Estimate(VolumeRampCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)1425.3f; + } + + return (uint)1700.0f; + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerSample = 710.143f; + float baseCost = 7853.286f; + + if (SampleCount == 160) + { + costPerSample = 427.52f; + baseCost = 6329.442f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / SampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerSample = 3564.1f; + float baseCost = 9736.702f; + + if (SampleCount == 160) + { + costPerSample = 2125.6f; + baseCost = 7913.808f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / SampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)739.64f; + } + + return (uint)910.97f; + } + + public uint Estimate(CopyMixBufferCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)842.59f; + } + + return (uint)986.72f; + } + + public uint Estimate(MixCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)1402.8f; + } + + return (uint)1853.2f; + } + + public virtual uint Estimate(DelayCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)8929.04f, + 2 => (uint)25500.75f, + 4 => (uint)47759.62f, + 6 => (uint)82203.07f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)1295.20f, + 2 => (uint)1213.60f, + 4 => (uint)942.03f, + 6 => (uint)1001.55f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)11941.05f, + 2 => (uint)37197.37f, + 4 => (uint)69749.84f, + 6 => (uint)120042.40f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)997.67f, + 2 => (uint)977.63f, + 4 => (uint)792.30f, + 6 => (uint)875.43f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public virtual uint Estimate(ReverbCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)81475.05f, + 2 => (uint)84975.0f, + 4 => (uint)91625.15f, + 6 => (uint)95332.27f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)536.30f, + 2 => (uint)588.70f, + 4 => (uint)643.70f, + 6 => (uint)706.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)120174.47f, + 2 => (uint)25262.22f, + 4 => (uint)135751.23f, + 6 => (uint)141129.23f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)617.64f, + 2 => (uint)659.54f, + 4 => (uint)711.43f, + 6 => (uint)778.07f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public virtual uint Estimate(Reverb3dCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)116754.0f, + 2 => (uint)125912.05f, + 4 => (uint)146336.03f, + 6 => (uint)165812.66f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)734.0f, + 2 => (uint)766.62f, + 4 => (uint)797.46f, + 6 => (uint)867.43f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)170292.34f, + 2 => (uint)183875.63f, + 4 => (uint)214696.19f, + 6 => (uint)243846.77f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)508.47f, + 2 => (uint)582.45f, + 4 => (uint)626.42f, + 6 => (uint)682.47f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return (uint)7182.14f; + } + + return (uint)472.11f; + } + + if (command.Enabled) + { + return (uint)9435.96f; + } + + return (uint)462.62f; + } + + public uint Estimate(VolumeCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)1311.1f; + } + + return (uint)1713.6f; + } + + public uint Estimate(CircularBufferSinkCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerBuffer = 770.26f; + float baseCost = 0f; + + if (SampleCount == 160) + { + costPerBuffer = 531.07f; + } + + return (uint)(baseCost + costPerBuffer * command.InputCount); + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)9949.7f; + } + + return (uint)14679.0f; + } + + public uint Estimate(UpsampleCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)312990.0f; + } + + return (uint)0.0f; + } + + public uint Estimate(DeviceSinkCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + Debug.Assert(command.InputCount == 2 || command.InputCount == 6); + + if (command.InputCount == 2) + { + if (SampleCount == 160) + { + return (uint)8980.0f; + } + + return (uint)9221.9f; + } + + if (SampleCount == 160) + { + return (uint)9177.9f; + } + + return (uint)9725.9f; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerSample = 3490.9f; + float baseCost = 10090.9f; + + if (SampleCount == 160) + { + costPerSample = 2310.4f; + baseCost = 7845.25f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / SampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DataSourceVersion2Command command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + (float baseCost, float costPerSample) = GetCostByFormat(SampleCount, command.SampleFormat, command.SrcQuality); + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / SampleCount) * (command.Pitch * 0.000030518f) - 1.0f))); + } + + private static (float, float) GetCostByFormat(uint sampleCount, SampleFormat format, SampleRateConversionQuality quality) + { + Debug.Assert(sampleCount == 160 || sampleCount == 240); + + switch (format) + { + case SampleFormat.PcmInt16: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (6329.44f, 427.52f); + } + + return (7853.28f, 710.14f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (8049.42f, 371.88f); + } + + return (10138.84f, 610.49f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (5062.66f, 423.43f); + } + + return (5810.96f, 676.72f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + case SampleFormat.PcmFloat: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (7845.25f, 2310.4f); + } + + return (10090.9f, 3490.9f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (9446.36f, 2308.91f); + } + + return (12520.85f, 3480.61f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (9446.36f, 2308.91f); + } + + return (12520.85f, 3480.61f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + case SampleFormat.Adpcm: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (7913.81f, 1827.66f); + } + + return (9736.70f, 2756.37f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (9607.81f, 1829.29f); + } + + return (12154.38f, 2731.31f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (6517.48f, 1824.61f); + } + + return (7929.44f, 2732.15f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + default: + throw new NotImplementedException($"{format}"); + } + } + + private uint EstimateLimiterCommandCommon(LimiterParameter parameter, bool enabled) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (enabled) + { + return parameter.ChannelCount switch + { + 1 => (uint)21392.0f, + 2 => (uint)26829.0f, + 4 => (uint)32405.0f, + 6 => (uint)52219.0f, + _ => throw new NotImplementedException($"{parameter.ChannelCount}"), + }; + } + + return parameter.ChannelCount switch + { + 1 => (uint)897.0f, + 2 => (uint)931.55f, + 4 => (uint)975.39f, + 6 => (uint)1016.8f, + _ => throw new NotImplementedException($"{parameter.ChannelCount}"), + }; + } + + if (enabled) + { + return parameter.ChannelCount switch + { + 1 => (uint)30556.0f, + 2 => (uint)39011.0f, + 4 => (uint)48270.0f, + 6 => (uint)76712.0f, + _ => throw new NotImplementedException($"{parameter.ChannelCount}"), + }; + } + + return parameter.ChannelCount switch + { + 1 => (uint)874.43f, + 2 => (uint)921.55f, + 4 => (uint)945.26f, + 6 => (uint)992.26f, + _ => throw new NotImplementedException($"{parameter.ChannelCount}"), + }; + } + + public uint Estimate(LimiterCommandVersion1 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled); + } + + public uint Estimate(LimiterCommandVersion2 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (!command.Parameter.StatisticsEnabled || !command.IsEffectEnabled) + { + return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled); + } + + if (SampleCount == 160) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)23309.0f, + 2 => (uint)29954.0f, + 4 => (uint)35807.0f, + 6 => (uint)58340.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)33526.0f, + 2 => (uint)43549.0f, + 4 => (uint)52190.0f, + 6 => (uint)85527.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public virtual uint Estimate(GroupedBiquadFilterCommand command) + { + return 0; + } + + public virtual uint Estimate(CaptureBufferCommand command) + { + return 0; + } + + public virtual uint Estimate(CompressorCommand command) + { + return 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs new file mode 100644 index 000000000..25bc67cd9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs @@ -0,0 +1,47 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 4. (added with REV10) + /// + public class CommandProcessingTimeEstimatorVersion4 : CommandProcessingTimeEstimatorVersion3 + { + public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { } + + public override uint Estimate(GroupedBiquadFilterCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)7424.5f; + } + + return (uint)9730.4f; + } + + public override uint Estimate(CaptureBufferCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return (uint)435.2f; + } + + return (uint)4261.0f; + } + + if (command.Enabled) + { + return (uint)5858.26f; + } + + return (uint)435.2f; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs new file mode 100644 index 000000000..7135c1c4f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs @@ -0,0 +1,214 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 5. (added with REV11) + /// + public class CommandProcessingTimeEstimatorVersion5 : CommandProcessingTimeEstimatorVersion4 + { + public CommandProcessingTimeEstimatorVersion5(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { } + + public override uint Estimate(DelayCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 8929, + 2 => 25501, + 4 => 47760, + 6 => 82203, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)1295.20f, + 2 => (uint)1213.60f, + 4 => (uint)942.03f, + 6 => (uint)1001.6f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 11941, + 2 => 37197, + 4 => 69750, + 6 => 12004, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)997.67f, + 2 => (uint)977.63f, + 4 => (uint)792.31f, + 6 => (uint)875.43f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public override uint Estimate(ReverbCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 81475, + 2 => 84975, + 4 => 91625, + 6 => 95332, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)536.30f, + 2 => (uint)588.80f, + 4 => (uint)643.70f, + 6 => (uint)706.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 120170, + 2 => 125260, + 4 => 135750, + 6 => 141130, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)617.64f, + 2 => (uint)659.54f, + 4 => (uint)711.44f, + 6 => (uint)778.07f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public override uint Estimate(Reverb3dCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 116750, + 2 => 125910, + 4 => 146340, + 6 => 165810, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => 735, + 2 => (uint)766.62f, + 4 => (uint)834.07f, + 6 => (uint)875.44f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 170290, + 2 => 183880, + 4 => 214700, + 6 => 243850, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)508.47f, + 2 => (uint)582.45f, + 4 => (uint)626.42f, + 6 => (uint)682.47f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public override uint Estimate(CompressorCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 34431, + 2 => 44253, + 4 => 63827, + 6 => 83361, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)630.12f, + 2 => (uint)638.27f, + 4 => (uint)705.86f, + 6 => (uint)782.02f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 51095, + 2 => 65693, + 4 => 95383, + 6 => 124510, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)840.14f, + 2 => (uint)826.1f, + 4 => (uint)901.88f, + 6 => (uint)965.29f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs new file mode 100644 index 000000000..57ca266f4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs @@ -0,0 +1,85 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for an auxiliary buffer effect. + /// + public class AuxiliaryBufferEffect : BaseEffect + { + /// + /// The auxiliary buffer parameter. + /// + public AuxiliaryBufferParameter Parameter; + + /// + /// Auxiliary buffer state. + /// + public AuxiliaryBufferAddresses State; + + public override EffectType TargetEffectType => EffectType.AuxiliaryBuffer; + + public override DspAddress GetWorkBuffer(int index) + { + return WorkBuffers[index].GetReference(true); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (BufferUnmapped || parameter.IsNew) + { + ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf(); + + bool sendBufferUnmapped = !mapper.TryAttachBuffer(out _, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize); + bool returnBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[1], Parameter.ReturnBufferInfoAddress, bufferSize); + + BufferUnmapped = sendBufferUnmapped && returnBufferUnmapped; + + if (!BufferUnmapped) + { + DspAddress sendDspAddress = WorkBuffers[0].GetReference(false); + DspAddress returnDspAddress = WorkBuffers[1].GetReference(false); + + State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf(); + State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf(); + + State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf(); + State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf(); + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs new file mode 100644 index 000000000..a9716db2a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs @@ -0,0 +1,262 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Base class used as a server state for an effect. + /// + public class BaseEffect + { + /// + /// The of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect must be active. + /// + public bool IsEnabled; + + /// + /// Set to true if the internal effect work buffers used wasn't mapped. + /// + public bool BufferUnmapped; + + /// + /// The current state of the effect. + /// + public UsageState UsageState; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Array of all the work buffer used by the effect. + /// + protected AddressInfo[] WorkBuffers; + + /// + /// Create a new . + /// + public BaseEffect() + { + Type = TargetEffectType; + UsageState = UsageState.Invalid; + + IsEnabled = false; + BufferUnmapped = false; + MixId = Constants.UnusedMixId; + ProcessingOrder = uint.MaxValue; + + WorkBuffers = new AddressInfo[2]; + + foreach (ref AddressInfo info in WorkBuffers.AsSpan()) + { + info = AddressInfo.Create(); + } + } + + /// + /// The target handled by this . + /// + public virtual EffectType TargetEffectType => EffectType.Invalid; + + /// + /// Check if the sent by the user match the internal . + /// + /// The user parameter. + /// Returns true if the sent by the user matches the internal . + public bool IsTypeValid(ref T parameter) where T : unmanaged, IEffectInParameter + { + return parameter.Type == TargetEffectType; + } + + /// + /// Update the usage state during command generation. + /// + protected void UpdateUsageStateForCommandGeneration() + { + UsageState = IsEnabled ? UsageState.Enabled : UsageState.Disabled; + } + + /// + /// Update the internal common parameters from a user parameter. + /// + /// The user parameter. + protected void UpdateParameterBase(ref T parameter) where T : unmanaged, IEffectInParameter + { + MixId = parameter.MixId; + ProcessingOrder = parameter.ProcessingOrder; + } + + /// + /// Force unmap all the work buffers. + /// + /// The mapper to use. + public void ForceUnmapBuffers(PoolMapper mapper) + { + foreach (ref AddressInfo info in WorkBuffers.AsSpan()) + { + if (info.GetReference(false) != 0) + { + mapper.ForceUnmap(ref info); + } + } + } + + /// + /// Check if the effect needs to be skipped. + /// + /// Returns true if the effect needs to be skipped. + public bool ShouldSkip() + { + return BufferUnmapped; + } + + /// + /// Update the state during command generation. + /// + public virtual void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetEffectType); + } + + /// + /// Initialize the given result state. + /// + /// The state to initalize + public virtual void InitializeResultState(ref EffectResultState state) { } + + /// + /// Update the result state with . + /// + /// The destination result state + /// The source result state + public virtual void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) { } + + /// + /// Update the internal state from a user version 1 parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + updateErrorInfo = new ErrorInfo(); + } + + /// + /// Update the internal state from a user version 2 parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + updateErrorInfo = new ErrorInfo(); + } + + /// + /// Get the work buffer DSP address at the given index. + /// + /// The index of the work buffer + /// The work buffer DSP address at the given index. + public virtual DspAddress GetWorkBuffer(int index) + { + throw new InvalidOperationException(); + } + + /// + /// Get the first work buffer DSP address. + /// + /// The first work buffer DSP address. + protected DspAddress GetSingleBuffer() + { + if (IsEnabled) + { + return WorkBuffers[0].GetReference(true); + } + + if (UsageState != UsageState.Disabled) + { + DspAddress address = WorkBuffers[0].GetReference(false); + ulong size = WorkBuffers[0].Size; + + if (address != 0 && size != 0) + { + AudioProcessorMemoryManager.InvalidateDataCache(address, size); + } + } + + return 0; + } + + /// + /// Store the output status to the given user output. + /// + /// The given user output. + /// If set to true, the is active. + public void StoreStatus(ref T outStatus, bool isAudioRendererActive) where T : unmanaged, IEffectOutStatus + { + if (isAudioRendererActive) + { + if (UsageState == UsageState.Disabled) + { + outStatus.State = EffectState.Disabled; + } + else + { + outStatus.State = EffectState.Enabled; + } + } + else if (UsageState == UsageState.New) + { + outStatus.State = EffectState.Enabled; + } + else + { + outStatus.State = EffectState.Disabled; + } + } + + /// + /// Get the associated to the of this effect. + /// + /// The associated to the of this effect. + public PerformanceDetailType GetPerformanceDetailType() + { + return Type switch + { + EffectType.BiquadFilter => PerformanceDetailType.BiquadFilter, + EffectType.AuxiliaryBuffer => PerformanceDetailType.Aux, + EffectType.Delay => PerformanceDetailType.Delay, + EffectType.Reverb => PerformanceDetailType.Reverb, + EffectType.Reverb3d => PerformanceDetailType.Reverb3d, + EffectType.BufferMix => PerformanceDetailType.Mix, + EffectType.Limiter => PerformanceDetailType.Limiter, + EffectType.CaptureBuffer => PerformanceDetailType.CaptureBuffer, + EffectType.Compressor => PerformanceDetailType.Compressor, + _ => throw new NotImplementedException($"{Type}"), + }; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs new file mode 100644 index 000000000..b987f7c85 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a biquad filter effect. + /// + public class BiquadFilterEffect : BaseEffect + { + /// + /// The biquad filter parameter. + /// + public BiquadFilterEffectParameter Parameter; + + /// + /// The biquad filter state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public BiquadFilterEffect() + { + Parameter = new BiquadFilterEffectParameter(); + State = new BiquadFilterState[Constants.ChannelCountMax]; + } + + public override EffectType TargetEffectType => EffectType.BiquadFilter; + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs new file mode 100644 index 000000000..d6cb9cfa3 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs @@ -0,0 +1,49 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a buffer mix effect. + /// + public class BufferMixEffect : BaseEffect + { + /// + /// The buffer mix parameter. + /// + public BufferMixParameter Parameter; + + public override EffectType TargetEffectType => EffectType.BufferMix; + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs new file mode 100644 index 000000000..5be4b4ed5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs @@ -0,0 +1,82 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for an capture buffer effect. + /// + public class CaptureBufferEffect : BaseEffect + { + /// + /// The capture buffer parameter. + /// + public AuxiliaryBufferParameter Parameter; + + /// + /// Capture buffer state. + /// + public AuxiliaryBufferAddresses State; + + public override EffectType TargetEffectType => EffectType.CaptureBuffer; + + public override DspAddress GetWorkBuffer(int index) + { + return WorkBuffers[index].GetReference(true); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (BufferUnmapped || parameter.IsNew) + { + ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf(); + + bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize); + + BufferUnmapped = sendBufferUnmapped; + + if (!BufferUnmapped) + { + DspAddress sendDspAddress = WorkBuffers[0].GetReference(false); + + // NOTE: Nintendo directly interact with the CPU side structure in the processing of the DSP command. + State.SendBufferInfo = sendDspAddress; + State.SendBufferInfoBase = sendDspAddress + (ulong)Unsafe.SizeOf(); + State.ReturnBufferInfo = 0; + State.ReturnBufferInfoBase = 0; + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs new file mode 100644 index 000000000..826c32cb0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a compressor effect. + /// + public class CompressorEffect : BaseEffect + { + /// + /// The compressor parameter. + /// + public CompressorParameter Parameter; + + /// + /// The compressor state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + 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(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs new file mode 100644 index 000000000..43cabb7db --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs @@ -0,0 +1,93 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a delay effect. + /// + public class DelayEffect : BaseEffect + { + /// + /// The delay parameter. + /// + public DelayParameter Parameter; + + /// + /// The delay state. + /// + public Memory State { get; } + + public DelayEffect() + { + State = new DelayState[1]; + } + + public override EffectType TargetEffectType => EffectType.Delay; + + public override DspAddress GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref DelayParameter delayParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (delayParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(ref parameter); + + UsageState oldParameterStatus = Parameter.Status; + + Parameter = delayParameter; + + if (delayParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.Status = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs new file mode 100644 index 000000000..619f31100 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs @@ -0,0 +1,123 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Effect context. + /// + public class EffectContext + { + /// + /// Storage for . + /// + private BaseEffect[] _effects; + + /// + /// The total effect count. + /// + private uint _effectCount; + + private EffectResultState[] _resultStatesCpu; + private EffectResultState[] _resultStatesDsp; + + /// + /// Create a new . + /// + public EffectContext() + { + _effects = null; + _effectCount = 0; + } + + /// + /// Initialize the . + /// + /// The total effect count. + /// The total result state count. + public void Initialize(uint effectCount, uint resultStateCount) + { + _effectCount = effectCount; + _effects = new BaseEffect[effectCount]; + + for (int i = 0; i < _effectCount; i++) + { + _effects[i] = new BaseEffect(); + } + + _resultStatesCpu = new EffectResultState[resultStateCount]; + _resultStatesDsp = new EffectResultState[resultStateCount]; + } + + /// + /// Get the total effect count. + /// + /// The total effect count. + public uint GetCount() + { + return _effectCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref BaseEffect GetEffect(int index) + { + Debug.Assert(index >= 0 && index < _effectCount); + + return ref _effects[index]; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + /// The returned should only be used when updating the server state. + public ref EffectResultState GetState(int index) + { + Debug.Assert(index >= 0 && index < _resultStatesCpu.Length); + + return ref _resultStatesCpu[index]; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + /// The returned should only be used in the context of processing on the . + public ref EffectResultState GetDspState(int index) + { + Debug.Assert(index >= 0 && index < _resultStatesDsp.Length); + + return ref _resultStatesDsp[index]; + } + + /// + /// Get a memory instance to a at the given . + /// + /// The index to use. + /// A memory instance to a at the given . + /// The returned should only be used in the context of processing on the . + public Memory GetDspStateMemory(int index) + { + return SpanIOHelper.GetMemory(_resultStatesDsp.AsMemory(), index, (uint)_resultStatesDsp.Length); + } + + /// + /// Update internal state during command generation. + /// + public void UpdateResultStateForCommandGeneration() + { + for (int index = 0; index < _resultStatesCpu.Length; index++) + { + _effects[index].UpdateResultState(ref _resultStatesCpu[index], ref _resultStatesDsp[index]); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs new file mode 100644 index 000000000..3e2f7326d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs @@ -0,0 +1,95 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a limiter effect. + /// + public class LimiterEffect : BaseEffect + { + /// + /// The limiter parameter. + /// + public LimiterParameter Parameter; + + /// + /// The limiter state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public LimiterEffect() + { + State = new LimiterState[1]; + } + + public override EffectType TargetEffectType => EffectType.Limiter; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + UpdateParameterBase(ref parameter); + + Parameter = limiterParameter; + + IsEnabled = parameter.IsEnabled; + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + Parameter.StatisticsReset = false; + } + + public override void InitializeResultState(ref EffectResultState state) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(state.SpecificData)[0]; + + statistics.Reset(); + } + + public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) + { + destState = srcState; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs new file mode 100644 index 000000000..f9d7f4943 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs @@ -0,0 +1,92 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a 3D reverberation effect. + /// + public class Reverb3dEffect : BaseEffect + { + /// + /// The 3D reverberation parameter. + /// + public Reverb3dParameter Parameter; + + /// + /// The 3D reverberation state. + /// + public Memory State { get; } + + public Reverb3dEffect() + { + State = new Reverb3dState[1]; + } + + public override EffectType TargetEffectType => EffectType.Reverb3d; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (reverbParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(ref parameter); + + UsageState oldParameterStatus = Parameter.ParameterStatus; + + Parameter = reverbParameter; + + if (reverbParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.ParameterStatus = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.ParameterStatus = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.ParameterStatus = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs new file mode 100644 index 000000000..6fdf8fc23 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs @@ -0,0 +1,95 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a reverberation effect. + /// + public class ReverbEffect : BaseEffect + { + /// + /// The reverberation parameter. + /// + public ReverbParameter Parameter; + + /// + /// The reverberation state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public ReverbEffect() + { + State = new ReverbState[1]; + } + + public override EffectType TargetEffectType => EffectType.Reverb; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (reverbParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(ref parameter); + + UsageState oldParameterStatus = Parameter.Status; + + Parameter = reverbParameter; + + if (reverbParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.Status = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs new file mode 100644 index 000000000..da7172244 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// The usage state of an effect. + /// + public enum UsageState : byte + { + /// + /// The effect is in an invalid state. + /// + Invalid, + + /// + /// The effect is new. + /// + New, + + /// + /// The effect is enabled. + /// + Enabled, + + /// + /// The effect is disabled. + /// + Disabled, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs new file mode 100644 index 000000000..27b22363a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs @@ -0,0 +1,40 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Estimate the time that a should take. + /// + /// This is used for voice dropping. + public interface ICommandProcessingTimeEstimator + { + uint Estimate(AuxiliaryBufferCommand command); + uint Estimate(BiquadFilterCommand command); + uint Estimate(ClearMixBufferCommand command); + uint Estimate(DelayCommand command); + uint Estimate(Reverb3dCommand command); + uint Estimate(ReverbCommand command); + uint Estimate(DepopPrepareCommand command); + uint Estimate(DepopForMixBuffersCommand command); + uint Estimate(MixCommand command); + uint Estimate(MixRampCommand command); + uint Estimate(MixRampGroupedCommand command); + uint Estimate(CopyMixBufferCommand command); + uint Estimate(PerformanceCommand command); + uint Estimate(VolumeCommand command); + uint Estimate(VolumeRampCommand command); + uint Estimate(PcmInt16DataSourceCommandVersion1 command); + uint Estimate(PcmFloatDataSourceCommandVersion1 command); + uint Estimate(AdpcmDataSourceCommandVersion1 command); + uint Estimate(DataSourceVersion2Command command); + uint Estimate(CircularBufferSinkCommand command); + uint Estimate(DeviceSinkCommand command); + uint Estimate(DownMixSurroundToStereoCommand command); + uint Estimate(UpsampleCommand command); + uint Estimate(LimiterCommandVersion1 command); + uint Estimate(LimiterCommandVersion2 command); + uint Estimate(GroupedBiquadFilterCommand command); + uint Estimate(CaptureBufferCommand command); + uint Estimate(CompressorCommand command); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs new file mode 100644 index 000000000..a7ec4cf51 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs @@ -0,0 +1,133 @@ +using System; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Represents the information of a region shared between the CPU and DSP. + /// + public struct AddressInfo + { + /// + /// The target CPU address of the region. + /// + public CpuAddress CpuAddress; + + /// + /// The size of the region. + /// + public ulong Size; + + private unsafe MemoryPoolState* _memoryPools; + + /// + /// The forced DSP address of the region. + /// + public DspAddress ForceMappedDspAddress; + + private readonly unsafe ref MemoryPoolState MemoryPoolState => ref *_memoryPools; + + public readonly unsafe bool HasMemoryPoolState => (IntPtr)_memoryPools != IntPtr.Zero; + + /// + /// Create an new empty . + /// + /// A new empty . + public static AddressInfo Create() + { + return Create(0, 0); + } + + /// + /// Create a new . + /// + /// The target of the region. + /// The target size of the region. + /// A new . + public static AddressInfo Create(CpuAddress cpuAddress, ulong size) + { + unsafe + { + return new AddressInfo + { + CpuAddress = cpuAddress, + _memoryPools = MemoryPoolState.Null, + Size = size, + ForceMappedDspAddress = 0, + }; + } + } + + /// + /// Setup the CPU address and size of the . + /// + /// The target of the region. + /// The size of the region. + public void Setup(CpuAddress cpuAddress, ulong size) + { + CpuAddress = cpuAddress; + Size = size; + ForceMappedDspAddress = 0; + + unsafe + { + _memoryPools = MemoryPoolState.Null; + } + } + + /// + /// Set the associated. + /// + /// The associated. + public void SetupMemoryPool(Span memoryPoolState) + { + unsafe + { + fixed (MemoryPoolState* ptr = &MemoryMarshal.GetReference(memoryPoolState)) + { + SetupMemoryPool(ptr); + } + } + } + + /// + /// Set the associated. + /// + /// The associated. + public unsafe void SetupMemoryPool(MemoryPoolState* memoryPoolState) + { + _memoryPools = memoryPoolState; + } + + /// + /// Check if the is mapped. + /// + /// Returns true if the is mapped. + public readonly bool HasMappedMemoryPool() + { + return HasMemoryPoolState && MemoryPoolState.IsMapped(); + } + + /// + /// Get the DSP address associated to the . + /// + /// If true, mark the as used. + /// Returns the DSP address associated to the . + public readonly DspAddress GetReference(bool markUsed) + { + if (!HasMappedMemoryPool()) + { + return ForceMappedDspAddress; + } + + if (markUsed) + { + MemoryPoolState.IsUsed = true; + } + + return MemoryPoolState.Translate(CpuAddress, Size); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs new file mode 100644 index 000000000..91bd5dbf5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Server state for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)] + public struct MemoryPoolState + { + public const int Alignment = 0x10; + + /// + /// The location of the . + /// + public enum LocationType : uint + { + /// + /// located on the CPU side for user use. + /// + Cpu, + + /// + /// located on the DSP side for system use. + /// + Dsp, + } + + /// + /// The CPU address associated to the . + /// + public CpuAddress CpuAddress; + + /// + /// The DSP address associated to the . + /// + public DspAddress DspAddress; + + /// + /// The size associated to the . + /// + public ulong Size; + + /// + /// The associated to the . + /// + public LocationType Location; + + /// + /// Set to true if the is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + public static unsafe MemoryPoolState* Null => (MemoryPoolState*)IntPtr.Zero.ToPointer(); + + /// + /// Create a new with the given . + /// + /// The location type to use. + /// A new with the given . + public static MemoryPoolState Create(LocationType location) + { + return new MemoryPoolState + { + CpuAddress = 0, + DspAddress = 0, + Size = 0, + Location = location, + }; + } + + /// + /// Set the and size of the . + /// + /// The . + /// The size. + public void SetCpuAddress(CpuAddress cpuAddress, ulong size) + { + CpuAddress = cpuAddress; + Size = size; + } + + /// + /// Check if the given and size is contains in the . + /// + /// The . + /// The size. + /// True if the is contained inside the . + public readonly bool Contains(CpuAddress targetCpuAddress, ulong size) + { + if (CpuAddress <= targetCpuAddress && size + targetCpuAddress <= Size + CpuAddress) + { + return true; + } + + return false; + } + + /// + /// Translate the given CPU address to a DSP address. + /// + /// The . + /// The size. + /// the target DSP address. + public readonly DspAddress Translate(CpuAddress targetCpuAddress, ulong size) + { + if (Contains(targetCpuAddress, size) && IsMapped()) + { + ulong offset = targetCpuAddress - CpuAddress; + + return DspAddress + offset; + } + + return 0; + } + + /// + /// Is the mapped on the DSP? + /// + /// Returns true if the is mapped on the DSP. + public readonly bool IsMapped() + { + return DspAddress != 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs new file mode 100644 index 000000000..391b80f8d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs @@ -0,0 +1,365 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Logging; +using System; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Memory pool mapping helper. + /// + public class PoolMapper + { + const uint CurrentProcessPseudoHandle = 0xFFFF8001; + + /// + /// The result of . + /// + public enum UpdateResult : uint + { + /// + /// No error reported. + /// + Success = 0, + + /// + /// The user parameters were invalid. + /// + InvalidParameter = 1, + + /// + /// mapping failed. + /// + MapError = 2, + + /// + /// unmapping failed. + /// + UnmapError = 3, + } + + /// + /// The handle of the process owning the CPU memory manipulated. + /// + private readonly uint _processHandle; + + /// + /// The that will be manipulated. + /// + private readonly Memory _memoryPools; + + /// + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + /// + private readonly bool _isForceMapEnabled; + + /// + /// Create a new used for system mapping. + /// + /// The handle of the process owning the CPU memory manipulated. + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + public PoolMapper(uint processHandle, bool isForceMapEnabled) + { + _processHandle = processHandle; + _isForceMapEnabled = isForceMapEnabled; + _memoryPools = Memory.Empty; + } + + /// + /// Create a new used for user mapping. + /// + /// The handle of the process owning the CPU memory manipulated. + /// The user memory pools. + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + public PoolMapper(uint processHandle, Memory memoryPool, bool isForceMapEnabled) + { + _processHandle = processHandle; + _memoryPools = memoryPool; + _isForceMapEnabled = isForceMapEnabled; + } + + /// + /// Initialize the for system use. + /// + /// The for system use. + /// The to assign. + /// The size to assign. + /// Returns true if mapping on the succeeded. + public bool InitializeSystemPool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) + { + if (memoryPool.Location != MemoryPoolState.LocationType.Dsp) + { + return false; + } + + return InitializePool(ref memoryPool, cpuAddress, size); + } + + /// + /// Initialize the . + /// + /// The . + /// The to assign. + /// The size to assign. + /// Returns true if mapping on the succeeded. + public bool InitializePool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) + { + memoryPool.SetCpuAddress(cpuAddress, size); + + return Map(ref memoryPool) != 0; + } + + /// + /// Get the process handle associated to the . + /// + /// The . + /// Returns the process handle associated to the . + public uint GetProcessHandle(ref MemoryPoolState memoryPool) + { + if (memoryPool.Location == MemoryPoolState.LocationType.Cpu) + { + return CurrentProcessPseudoHandle; + } + + if (memoryPool.Location == MemoryPoolState.LocationType.Dsp) + { + return _processHandle; + } + + return 0; + } + + /// + /// Map the on the . + /// + /// The to map. + /// Returns the DSP address mapped. + public DspAddress Map(ref MemoryPoolState memoryPool) + { + DspAddress result = AudioProcessorMemoryManager.Map(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); + + if (result != 0) + { + memoryPool.DspAddress = result; + } + + return result; + } + + /// + /// Unmap the from the . + /// + /// The to unmap. + /// Returns true if unmapped. + public bool Unmap(ref MemoryPoolState memoryPool) + { + if (memoryPool.IsUsed) + { + return false; + } + + AudioProcessorMemoryManager.Unmap(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); + + memoryPool.SetCpuAddress(0, 0); + memoryPool.DspAddress = 0; + + return true; + } + + /// + /// Find a associated to the region given. + /// + /// The region . + /// The region size. + /// Returns the found or if not found. + private Span FindMemoryPool(CpuAddress cpuAddress, ulong size) + { + if (!_memoryPools.IsEmpty && _memoryPools.Length > 0) + { + for (int i = 0; i < _memoryPools.Length; i++) + { + if (_memoryPools.Span[i].Contains(cpuAddress, size)) + { + return _memoryPools.Span.Slice(i, 1); + } + } + } + + return Span.Empty; + } + + /// + /// Force unmap the given . + /// + /// The to force unmap + public void ForceUnmap(ref AddressInfo addressInfo) + { + if (_isForceMapEnabled) + { + Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); + + if (!memoryPool.IsEmpty) + { + AudioProcessorMemoryManager.Unmap(_processHandle, memoryPool[0].CpuAddress, memoryPool[0].Size); + + return; + } + + AudioProcessorMemoryManager.Unmap(_processHandle, addressInfo.CpuAddress, 0); + } + } + + /// + /// Try to attach the given region to the . + /// + /// The error information if an error was generated. + /// The to attach the region to. + /// The region . + /// The region size. + /// Returns true if mapping was performed. + public bool TryAttachBuffer(out ErrorInfo errorInfo, ref AddressInfo addressInfo, CpuAddress cpuAddress, ulong size) + { + errorInfo = new ErrorInfo(); + + addressInfo.Setup(cpuAddress, size); + + if (AssignDspAddress(ref addressInfo)) + { + errorInfo.ErrorCode = 0x0; + errorInfo.ExtraErrorInfo = 0x0; + + return true; + } + + errorInfo.ErrorCode = ResultCode.InvalidAddressInfo; + errorInfo.ExtraErrorInfo = addressInfo.CpuAddress; + + return _isForceMapEnabled; + } + + /// + /// Update a using user parameters. + /// + /// The to update. + /// Input user parameter. + /// Output user parameter. + /// Returns the of the operations performed. + public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) + { + MemoryPoolUserState inputState = inParameter.State; + + MemoryPoolUserState outputState; + + const uint PageSize = 0x1000; + + if (inputState != MemoryPoolUserState.RequestAttach && inputState != MemoryPoolUserState.RequestDetach) + { + return UpdateResult.Success; + } + + if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % PageSize) != 0) + { + return UpdateResult.InvalidParameter; + } + + if (inParameter.Size == 0 || (inParameter.Size % PageSize) != 0) + { + return UpdateResult.InvalidParameter; + } + + if (inputState == MemoryPoolUserState.RequestAttach) + { + bool initializeSuccess = InitializePool(ref memoryPool, inParameter.CpuAddress, inParameter.Size); + + if (!initializeSuccess) + { + memoryPool.SetCpuAddress(0, 0); + + Logger.Error?.Print(LogClass.AudioRenderer, $"Map of memory pool (address: 0x{inParameter.CpuAddress:x}, size 0x{inParameter.Size:x}) failed!"); + return UpdateResult.MapError; + } + + outputState = MemoryPoolUserState.Attached; + } + else + { + if (memoryPool.CpuAddress != inParameter.CpuAddress || memoryPool.Size != inParameter.Size) + { + return UpdateResult.InvalidParameter; + } + + if (!Unmap(ref memoryPool)) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Unmap of memory pool (address: 0x{memoryPool.CpuAddress:x}, size 0x{memoryPool.Size:x}) failed!"); + return UpdateResult.UnmapError; + } + + outputState = MemoryPoolUserState.Detached; + } + + outStatus.State = outputState; + + return UpdateResult.Success; + } + + /// + /// Map the to the . + /// + /// The to map. + /// Returns true if mapping was performed. + private bool AssignDspAddress(ref AddressInfo addressInfo) + { + if (addressInfo.CpuAddress == 0) + { + return false; + } + + if (_memoryPools.Length > 0) + { + Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); + + if (!memoryPool.IsEmpty) + { + addressInfo.SetupMemoryPool(memoryPool); + + return true; + } + } + + if (_isForceMapEnabled) + { + DspAddress dspAddress = AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); + + addressInfo.ForceMappedDspAddress = dspAddress; + + AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); + } + else + { + unsafe + { + addressInfo.SetupMemoryPool(MemoryPoolState.Null); + } + } + + return false; + } + + /// + /// Remove the usage flag from all the . + /// + /// The to reset. + public static void ClearUsageState(Memory memoryPool) + { + foreach (ref MemoryPoolState info in memoryPool.Span) + { + info.IsUsed = false; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs new file mode 100644 index 000000000..8991ceaf9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs @@ -0,0 +1,257 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Mix +{ + /// + /// Mix context. + /// + public class MixContext + { + /// + /// The total mix count. + /// + private uint _mixesCount; + + /// + /// Storage for . + /// + private Memory _mixes; + + /// + /// Storage of the sorted indices to . + /// + private Memory _sortedMixes; + + /// + /// Graph state. + /// + public NodeStates NodeStates { get; } + + /// + /// The instance of the adjacent matrix. + /// + public EdgeMatrix EdgeMatrix { get; } + + /// + /// Create a new instance of . + /// + public MixContext() + { + NodeStates = new NodeStates(); + EdgeMatrix = new EdgeMatrix(); + } + + /// + /// Initialize the . + /// + /// The storage for sorted indices. + /// The storage of . + /// The storage used for the . + /// The storage used for the . + public void Initialize(Memory sortedMixes, Memory mixes, Memory nodeStatesWorkBuffer, Memory edgeMatrixWorkBuffer) + { + _mixesCount = (uint)mixes.Length; + _mixes = mixes; + _sortedMixes = sortedMixes; + + if (!nodeStatesWorkBuffer.IsEmpty && !edgeMatrixWorkBuffer.IsEmpty) + { + NodeStates.Initialize(nodeStatesWorkBuffer, mixes.Length); + EdgeMatrix.Initialize(edgeMatrixWorkBuffer, mixes.Length); + } + + int sortedId = 0; + for (int i = 0; i < _mixes.Length; i++) + { + SetSortedState(sortedId++, i); + } + } + + /// + /// Associate the given to a given . + /// + /// The sorted id. + /// The index to associate. + private void SetSortedState(int id, int targetIndex) + { + _sortedMixes.Span[id] = targetIndex; + } + + /// + /// Get a reference to the final . + /// + /// A reference to the final . + public ref MixState GetFinalState() + { + return ref GetState(Constants.FinalMixId); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref MixState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_mixes, id, _mixesCount); + } + + /// + /// Get a reference to a at the given of the sorted mix info. + /// + /// The index to use. + /// A reference to a at the given . + public ref MixState GetSortedState(int id) + { + Debug.Assert(id >= 0 && id < _mixesCount); + + return ref GetState(_sortedMixes.Span[id]); + } + + /// + /// Get the total mix count. + /// + /// The total mix count. + public uint GetCount() + { + return _mixesCount; + } + + /// + /// Update the internal distance from the final mix value of every . + /// + private void UpdateDistancesFromFinalMix() + { + foreach (ref MixState mix in _mixes.Span) + { + mix.ClearDistanceFromFinalMix(); + } + + for (int i = 0; i < GetCount(); i++) + { + ref MixState mix = ref GetState(i); + + SetSortedState(i, i); + + if (mix.IsUsed) + { + uint distance; + + if (mix.MixId != Constants.FinalMixId) + { + int mixId = mix.MixId; + + for (distance = 0; distance < GetCount(); distance++) + { + if (mixId == Constants.UnusedMixId) + { + distance = MixState.InvalidDistanceFromFinalMix; + break; + } + + ref MixState distanceMix = ref GetState(mixId); + + if (distanceMix.DistanceFromFinalMix != MixState.InvalidDistanceFromFinalMix) + { + distance = distanceMix.DistanceFromFinalMix + 1; + break; + } + + mixId = distanceMix.DestinationMixId; + + if (mixId == Constants.FinalMixId) + { + break; + } + } + + if (distance > GetCount()) + { + distance = MixState.InvalidDistanceFromFinalMix; + } + } + else + { + distance = MixState.InvalidDistanceFromFinalMix; + } + + mix.DistanceFromFinalMix = distance; + } + } + } + + /// + /// Update the internal mix buffer offset of all . + /// + private void UpdateMixBufferOffset() + { + uint offset = 0; + + foreach (ref MixState mix in _mixes.Span) + { + mix.BufferOffset = offset; + + offset += mix.BufferCount; + } + } + + /// + /// Sort the mixes using distance from the final mix. + /// + public void Sort() + { + UpdateDistancesFromFinalMix(); + + int[] sortedMixesTemp = _sortedMixes[..(int)GetCount()].ToArray(); + + Array.Sort(sortedMixesTemp, (a, b) => + { + ref MixState stateA = ref GetState(a); + ref MixState stateB = ref GetState(b); + + return stateB.DistanceFromFinalMix.CompareTo(stateA.DistanceFromFinalMix); + }); + + sortedMixesTemp.AsSpan().CopyTo(_sortedMixes.Span); + + UpdateMixBufferOffset(); + } + + /// + /// Sort the mixes and splitters using an adjacency matrix. + /// + /// The used. + /// Return true, if no errors in the graph were detected. + public bool Sort(SplitterContext splitterContext) + { + if (splitterContext.UsingSplitter()) + { + bool isValid = NodeStates.Sort(EdgeMatrix); + + if (isValid) + { + ReadOnlySpan sortedMixesIndex = NodeStates.GetTsortResult(); + + int id = 0; + + for (int i = sortedMixesIndex.Length - 1; i >= 0; i--) + { + SetSortedState(id++, sortedMixesIndex[i]); + } + + UpdateMixBufferOffset(); + } + + return isValid; + } + + UpdateMixBufferOffset(); + + return true; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs new file mode 100644 index 000000000..88ae44831 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs @@ -0,0 +1,312 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Constants; + +namespace Ryujinx.Audio.Renderer.Server.Mix +{ + /// + /// Server state for a mix. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x940, Pack = Alignment)] + public struct MixState + { + public const uint InvalidDistanceFromFinalMix = 0x80000000; + + public const int Alignment = 0x10; + + /// + /// Base volume of the mix. + /// + public float Volume; + + /// + /// Target sample rate of the mix. + /// + public uint SampleRate; + + /// + /// Target buffer count. + /// + public uint BufferCount; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// The id of the mix. + /// + public int MixId; + + /// + /// The mix node id. + /// + public int NodeId; + + /// + /// the buffer offset to use for command generation. + /// + public uint BufferOffset; + + /// + /// The distance of the mix from the final mix. + /// + public uint DistanceFromFinalMix; + + /// + /// The effect processing order storage. + /// + private readonly IntPtr _effectProcessingOrderArrayPointer; + + /// + /// The max element count that can be found in the effect processing order storage. + /// + public uint EffectProcessingOrderArrayMaxCount; + + /// + /// The mix to output the result of this mix. + /// + public int DestinationMixId; + + /// + /// Mix buffer volumes storage. + /// + private MixVolumeArray _mixVolumeArray; + + /// + /// The splitter to output the result of this mix. + /// + public uint DestinationSplitterId; + + /// + /// If set to true, the long size pre-delay is supported on the reverb command. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsLongSizePreDelaySupported; + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + private struct MixVolumeArray + { + private const int Size = 4 * MixBufferCountMax * MixBufferCountMax; + } + + /// + /// Mix buffer volumes. + /// + /// Used when no splitter id is specified. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixVolumeArray); + + /// + /// Get the volume for a given connection destination. + /// + /// The source node index. + /// The destination node index + /// The volume for the given connection destination. + public float GetMixBufferVolume(int sourceIndex, int destinationIndex) + { + return MixBufferVolume[sourceIndex * MixBufferCountMax + destinationIndex]; + } + + /// + /// The array used to order effects associated to this mix. + /// + public readonly Span EffectProcessingOrderArray + { + get + { + if (_effectProcessingOrderArrayPointer == IntPtr.Zero) + { + return Span.Empty; + } + + unsafe + { + return new Span((void*)_effectProcessingOrderArrayPointer, (int)EffectProcessingOrderArrayMaxCount); + } + } + } + + /// + /// Create a new + /// + /// + /// + public MixState(Memory effectProcessingOrderArray, ref BehaviourContext behaviourContext) : this() + { + MixId = UnusedMixId; + + DistanceFromFinalMix = InvalidDistanceFromFinalMix; + + DestinationMixId = UnusedMixId; + + DestinationSplitterId = UnusedSplitterId; + + unsafe + { + // SAFETY: safe as effectProcessingOrderArray comes from the work buffer memory that is pinned. + _effectProcessingOrderArrayPointer = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(effectProcessingOrderArray.Span)); + } + + EffectProcessingOrderArrayMaxCount = (uint)effectProcessingOrderArray.Length; + + IsLongSizePreDelaySupported = behaviourContext.IsLongSizePreDelaySupported(); + + ClearEffectProcessingOrder(); + } + + /// + /// Clear the value to its default state. + /// + public void ClearDistanceFromFinalMix() + { + DistanceFromFinalMix = InvalidDistanceFromFinalMix; + } + + /// + /// Clear the to its default state. + /// + public readonly void ClearEffectProcessingOrder() + { + EffectProcessingOrderArray.Fill(-1); + } + + /// + /// Return true if the mix has any destinations. + /// + /// True if the mix has any destinations. + public readonly bool HasAnyDestination() + { + return DestinationMixId != UnusedMixId || DestinationSplitterId != UnusedSplitterId; + } + + /// + /// Update the mix connection on the adjacency matrix. + /// + /// The adjacency matrix. + /// The input parameter of the mix. + /// The splitter context. + /// Return true, new connections were done on the adjacency matrix. + private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext) + { + bool hasNewConnections; + + if (DestinationSplitterId == UnusedSplitterId) + { + hasNewConnections = false; + } + else + { + ref SplitterState splitter = ref splitterContext.GetState((int)DestinationSplitterId); + + hasNewConnections = splitter.HasNewConnection; + } + + if (DestinationMixId == parameter.DestinationMixId && DestinationSplitterId == parameter.DestinationSplitterId && !hasNewConnections) + { + return false; + } + + edgeMatrix.RemoveEdges(MixId); + + if (parameter.DestinationMixId == UnusedMixId) + { + if (parameter.DestinationSplitterId != UnusedSplitterId) + { + ref SplitterState splitter = ref splitterContext.GetState((int)parameter.DestinationSplitterId); + + for (int i = 0; i < splitter.DestinationCount; i++) + { + Span destination = splitter.GetData(i); + + if (!destination.IsEmpty) + { + int destinationMixId = destination[0].DestinationId; + + if (destinationMixId != UnusedMixId) + { + edgeMatrix.Connect(MixId, destinationMixId); + } + } + } + } + } + else + { + edgeMatrix.Connect(MixId, parameter.DestinationMixId); + } + + DestinationMixId = parameter.DestinationMixId; + DestinationSplitterId = parameter.DestinationSplitterId; + + return true; + } + + /// + /// Update the mix from user information. + /// + /// The adjacency matrix. + /// The input parameter of the mix. + /// The effect context. + /// The splitter context. + /// The behaviour context. + /// Return true if the mix was changed. + public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) + { + bool isDirty; + + Volume = parameter.Volume; + SampleRate = parameter.SampleRate; + BufferCount = parameter.BufferCount; + IsUsed = parameter.IsUsed; + MixId = parameter.MixId; + NodeId = parameter.NodeId; + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (behaviourContext.IsSplitterSupported()) + { + isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext); + } + else + { + isDirty = DestinationMixId != parameter.DestinationMixId; + + if (DestinationMixId != parameter.DestinationMixId) + { + DestinationMixId = parameter.DestinationMixId; + } + + DestinationSplitterId = UnusedSplitterId; + } + + ClearEffectProcessingOrder(); + + for (int i = 0; i < effectContext.GetCount(); i++) + { + ref BaseEffect effect = ref effectContext.GetEffect(i); + + if (effect.MixId == MixId) + { + Debug.Assert(effect.ProcessingOrder <= EffectProcessingOrderArrayMaxCount); + + if (effect.ProcessingOrder > EffectProcessingOrderArrayMaxCount) + { + return isDirty; + } + + EffectProcessingOrderArray[(int)effect.ProcessingOrder] = i; + } + } + + return isDirty; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs new file mode 100644 index 000000000..ffabf4676 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs @@ -0,0 +1,52 @@ +using Ryujinx.Audio.Renderer.Common; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Represents a detailed entry in a performance frame. + /// + public interface IPerformanceDetailEntry + { + /// + /// Get the start time of this entry event (in microseconds). + /// + /// The start time of this entry event (in microseconds). + int GetStartTime(); + + /// + /// Get the start time offset in this structure. + /// + /// The start time offset in this structure. + int GetStartTimeOffset(); + + /// + /// Get the processing time of this entry event (in microseconds). + /// + /// The processing time of this entry event (in microseconds). + int GetProcessingTime(); + + /// + /// Get the processing time offset in this structure. + /// + /// The processing time offset in this structure. + int GetProcessingTimeOffset(); + + /// + /// Set the of this entry. + /// + /// The node id of this entry. + void SetNodeId(int nodeId); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetEntryType(PerformanceEntryType type); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetDetailType(PerformanceDetailType detailType); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs new file mode 100644 index 000000000..a0178187b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs @@ -0,0 +1,46 @@ +using Ryujinx.Audio.Renderer.Common; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Represents an entry in a performance frame. + /// + public interface IPerformanceEntry + { + /// + /// Get the start time of this entry event (in microseconds). + /// + /// The start time of this entry event (in microseconds). + int GetStartTime(); + + /// + /// Get the start time offset in this structure. + /// + /// The start time offset in this structure. + int GetStartTimeOffset(); + + /// + /// Get the processing time of this entry event (in microseconds). + /// + /// The processing time of this entry event (in microseconds). + int GetProcessingTime(); + + /// + /// Get the processing time offset in this structure. + /// + /// The processing time offset in this structure. + int GetProcessingTimeOffset(); + + /// + /// Set the of this entry. + /// + /// The node id of this entry. + void SetNodeId(int nodeId); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetEntryType(PerformanceEntryType type); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs new file mode 100644 index 000000000..deacd8ccc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs @@ -0,0 +1,80 @@ +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// The header of a performance frame. + /// + public interface IPerformanceHeader + { + /// + /// Get the entry count offset in this structure. + /// + /// The entry count offset in this structure. + int GetEntryCountOffset(); + + /// + /// Set the DSP running behind flag. + /// + /// The flag. + void SetDspRunningBehind(bool isRunningBehind); + + /// + /// Set the count of voices that were dropped. + /// + /// The count of voices that were dropped. + void SetVoiceDropCount(uint voiceCount); + + /// + /// Set the start ticks of the . (before sending commands) + /// + /// The start ticks of the . (before sending commands) + void SetStartRenderingTicks(ulong startTicks); + + /// + /// Set the header magic. + /// + /// The header magic. + void SetMagic(uint magic); + + /// + /// Set the offset of the next performance header. + /// + /// The offset of the next performance header. + void SetNextOffset(int nextOffset); + + /// + /// Set the total time taken by all the commands profiled. + /// + /// The total time taken by all the commands profiled. + void SetTotalProcessingTime(int totalProcessingTime); + + /// + /// Set the index of this performance frame. + /// + /// The index of this performance frame. + void SetIndex(uint index); + + /// + /// Get the total count of entries in this frame. + /// + /// The total count of entries in this frame. + int GetEntryCount(); + + /// + /// Get the total count of detailed entries in this frame. + /// + /// The total count of detailed entries in this frame. + int GetEntryDetailCount(); + + /// + /// Set the total count of entries in this frame. + /// + /// The total count of entries in this frame. + void SetEntryCount(int entryCount); + + /// + /// Set the total count of detailed entries in this frame. + /// + /// The total count of detailed entries in this frame. + void SetEntryDetailCount(int entryDetailCount); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs new file mode 100644 index 000000000..a4024607c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs @@ -0,0 +1,72 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct PerformanceDetailVersion1 : IPerformanceDetailEntry + { + /// + /// The node id associated to this detailed entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this detailed entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this detailed entry. + /// + public int ProcessingTime; + + /// + /// The detailed entry type associated to this detailed entry. + /// + public PerformanceDetailType DetailType; + + /// + /// The entry type associated to this detailed entry. + /// + public PerformanceEntryType EntryType; + + public readonly int GetProcessingTime() + { + return ProcessingTime; + } + + public readonly int GetProcessingTimeOffset() + { + return 8; + } + + public readonly int GetStartTime() + { + return StartTime; + } + + public readonly int GetStartTimeOffset() + { + return 4; + } + + public void SetDetailType(PerformanceDetailType detailType) + { + DetailType = detailType; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs new file mode 100644 index 000000000..f10e2937e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs @@ -0,0 +1,72 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceDetailVersion2 : IPerformanceDetailEntry + { + /// + /// The node id associated to this detailed entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this detailed entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this detailed entry. + /// + public int ProcessingTime; + + /// + /// The detailed entry type associated to this detailed entry. + /// + public PerformanceDetailType DetailType; + + /// + /// The entry type associated to this detailed entry. + /// + public PerformanceEntryType EntryType; + + public readonly int GetProcessingTime() + { + return ProcessingTime; + } + + public readonly int GetProcessingTimeOffset() + { + return 8; + } + + public readonly int GetStartTime() + { + return StartTime; + } + + public readonly int GetStartTimeOffset() + { + return 4; + } + + public void SetDetailType(PerformanceDetailType detailType) + { + DetailType = detailType; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs new file mode 100644 index 000000000..d24b96a27 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Information used by the performance command to store informations in the performance entry. + /// + public class PerformanceEntryAddresses + { + /// + /// The memory storing the performance entry. + /// + public Memory BaseMemory; + + /// + /// The offset to the start time field. + /// + public uint StartTimeOffset; + + /// + /// The offset to the entry count field. + /// + public uint EntryCountOffset; + + /// + /// The offset to the processing time field. + /// + public uint ProcessingTimeOffset; + + /// + /// Increment the entry count. + /// + public void IncrementEntryCount() + { + BaseMemory.Span[(int)EntryCountOffset / 4]++; + } + + /// + /// Set the start time in the entry. + /// + /// The start time in nanoseconds. + public void SetStartTime(ulong startTimeNano) + { + BaseMemory.Span[(int)StartTimeOffset / 4] = (int)(startTimeNano / 1000); + } + + /// + /// Set the processing time in the entry. + /// + /// The end time in nanoseconds. + public void SetProcessingTime(ulong endTimeNano) + { + BaseMemory.Span[(int)ProcessingTimeOffset / 4] = (int)(endTimeNano / 1000) - BaseMemory.Span[(int)StartTimeOffset / 4]; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs new file mode 100644 index 000000000..2c407670f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct PerformanceEntryVersion1 : IPerformanceEntry + { + /// + /// The node id associated to this entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this entry. + /// + public int ProcessingTime; + + /// + /// The entry type associated to this entry. + /// + public PerformanceEntryType EntryType; + + public readonly int GetProcessingTime() + { + return ProcessingTime; + } + + public readonly int GetProcessingTimeOffset() + { + return 8; + } + + public readonly int GetStartTime() + { + return StartTime; + } + + public readonly int GetStartTimeOffset() + { + return 4; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs new file mode 100644 index 000000000..eb96a3141 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceEntryVersion2 : IPerformanceEntry + { + /// + /// The node id associated to this entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this entry. + /// + public int ProcessingTime; + + /// + /// The entry type associated to this entry. + /// + public PerformanceEntryType EntryType; + + public readonly int GetProcessingTime() + { + return ProcessingTime; + } + + public readonly int GetProcessingTimeOffset() + { + return 8; + } + + public readonly int GetStartTime() + { + return StartTime; + } + + public readonly int GetStartTimeOffset() + { + return 4; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs new file mode 100644 index 000000000..5aeb703c5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs @@ -0,0 +1,101 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceFrameHeaderVersion1 : IPerformanceHeader + { + /// + /// The magic of the performance header. + /// + public uint Magic; + + /// + /// The total count of entries in this frame. + /// + public int EntryCount; + + /// + /// The total count of detailed entries in this frame. + /// + public int EntryDetailCount; + + /// + /// The offset of the next performance header. + /// + public int NextOffset; + + /// + /// The total time taken by all the commands profiled. + /// + public int TotalProcessingTime; + + /// + /// The count of voices that were dropped. + /// + public uint VoiceDropCount; + + public readonly int GetEntryCount() + { + return EntryCount; + } + + public readonly int GetEntryCountOffset() + { + return 4; + } + + public readonly int GetEntryDetailCount() + { + return EntryDetailCount; + } + + public readonly void SetDspRunningBehind(bool isRunningBehind) + { + // NOTE: Not present in version 1 + } + + public void SetEntryCount(int entryCount) + { + EntryCount = entryCount; + } + + public void SetEntryDetailCount(int entryDetailCount) + { + EntryDetailCount = entryDetailCount; + } + + public readonly void SetIndex(uint index) + { + // NOTE: Not present in version 1 + } + + public void SetMagic(uint magic) + { + Magic = magic; + } + + public void SetNextOffset(int nextOffset) + { + NextOffset = nextOffset; + } + + public readonly void SetStartRenderingTicks(ulong startTicks) + { + // NOTE: not present in version 1 + } + + public void SetTotalProcessingTime(int totalProcessingTime) + { + TotalProcessingTime = totalProcessingTime; + } + + public void SetVoiceDropCount(uint voiceCount) + { + VoiceDropCount = voiceCount; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs new file mode 100644 index 000000000..d6e0ffc8b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs @@ -0,0 +1,117 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x30)] + public struct PerformanceFrameHeaderVersion2 : IPerformanceHeader + { + /// + /// The magic of the performance header. + /// + public uint Magic; + + /// + /// The total count of entries in this frame. + /// + public int EntryCount; + + /// + /// The total count of detailed entries in this frame. + /// + public int EntryDetailCount; + + /// + /// The offset of the next performance header. + /// + public int NextOffset; + + /// + /// The total time taken by all the commands profiled. + /// + public int TotalProcessingTime; + + /// + /// The count of voices that were dropped. + /// + public uint VoiceDropCount; + + /// + /// The start ticks of the . (before sending commands) + /// + public ulong StartRenderingTicks; + + /// + /// The index of this performance frame. + /// + public uint Index; + + /// + /// If set to true, the DSP is running behind. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsDspRunningBehind; + + public readonly int GetEntryCount() + { + return EntryCount; + } + + public readonly int GetEntryCountOffset() + { + return 4; + } + + public readonly int GetEntryDetailCount() + { + return EntryDetailCount; + } + + public void SetDspRunningBehind(bool isRunningBehind) + { + IsDspRunningBehind = isRunningBehind; + } + + public void SetEntryCount(int entryCount) + { + EntryCount = entryCount; + } + + public void SetEntryDetailCount(int entryDetailCount) + { + EntryDetailCount = entryDetailCount; + } + + public void SetIndex(uint index) + { + Index = index; + } + + public void SetMagic(uint magic) + { + Magic = magic; + } + + public void SetNextOffset(int nextOffset) + { + NextOffset = nextOffset; + } + + public void SetStartRenderingTicks(ulong startTicks) + { + StartRenderingTicks = startTicks; + } + + public void SetTotalProcessingTime(int totalProcessingTime) + { + TotalProcessingTime = totalProcessingTime; + } + + public void SetVoiceDropCount(uint voiceCount) + { + VoiceDropCount = voiceCount; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs new file mode 100644 index 000000000..0a035916c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs @@ -0,0 +1,102 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + public abstract class PerformanceManager + { + /// + /// Get the required size for a single performance frame. + /// + /// The audio renderer configuration. + /// The behaviour context. + /// The required size for a single performance frame. + public static ulong GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter, ref BehaviourContext behaviourContext) + { + uint version = behaviourContext.GetPerformanceMetricsDataFormat(); + + if (version == 2) + { + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + } + + if (version == 1) + { + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + } + + throw new NotImplementedException($"Unknown Performance metrics data format version {version}"); + } + + /// + /// Copy the performance frame history to the supplied user buffer and returns the size copied. + /// + /// The supplied user buffer to store the performance frame into. + /// The size copied to the supplied buffer. + public abstract uint CopyHistories(Span performanceOutput); + + /// + /// Set the target node id to profile. + /// + /// The target node id to profile. + public abstract void SetTargetNodeId(int target); + + /// + /// Check if the given target node id is profiled. + /// + /// The target node id to check. + /// Return true, if the given target node id is profiled. + public abstract bool IsTargetNodeId(int target); + + /// + /// Get the next buffer to store a performance entry. + /// + /// The output . + /// The info. + /// The node id of the entry. + /// Return true, if a valid was returned. + public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId); + + /// + /// Get the next buffer to store a performance detailed entry. + /// + /// The output . + /// The info. + /// The info. + /// The node id of the entry. + /// Return true, if a valid was returned. + public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId); + + /// + /// Finalize the current performance frame. + /// + /// Indicate if the DSP is running behind. + /// The count of voices that were dropped. + /// The start ticks of the audio rendering. + public abstract void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks); + + /// + /// Create a new . + /// + /// The backing memory available for use by the manager. + /// The audio renderer configuration. + /// The behaviour context; + /// A new . + public static PerformanceManager Create(Memory performanceBuffer, ref AudioRendererConfiguration parameter, BehaviourContext behaviourContext) + { + uint version = behaviourContext.GetPerformanceMetricsDataFormat(); + + return version switch + { + 1 => new PerformanceManagerGeneric(performanceBuffer, ref parameter), + 2 => new PerformanceManagerGeneric(performanceBuffer, ref parameter), + _ => throw new NotImplementedException($"Unknown Performance metrics data format version {version}"), + }; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs new file mode 100644 index 000000000..5a70a1bcf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs @@ -0,0 +1,308 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// A Generic implementation of . + /// + /// The header implementation of the performance frame. + /// The entry implementation of the performance frame. + /// A detailed implementation of the performance frame. + public class PerformanceManagerGeneric : PerformanceManager where THeader : unmanaged, IPerformanceHeader where TEntry : unmanaged, IPerformanceEntry where TEntryDetail : unmanaged, IPerformanceDetailEntry + { + /// + /// The magic used for the . + /// + private const uint MagicPerformanceBuffer = 0x46524550; + + /// + /// The fixed amount of that can be stored in a frame. + /// + private const int MaxFrameDetailCount = 100; + + private readonly Memory _buffer; + private readonly Memory _historyBuffer; + + private Memory CurrentBuffer => _buffer[.._frameSize]; + private Memory CurrentBufferData => CurrentBuffer[Unsafe.SizeOf()..]; + + private ref THeader CurrentHeader => ref MemoryMarshal.Cast(CurrentBuffer.Span)[0]; + + private Span Entries => MemoryMarshal.Cast(CurrentBufferData.Span[..GetEntriesSize()]); + private Span EntriesDetail => MemoryMarshal.Cast(CurrentBufferData.Span.Slice(GetEntriesSize(), GetEntriesDetailSize())); + + private readonly int _frameSize; + private readonly int _availableFrameCount; + private readonly int _entryCountPerFrame; + private int _detailTarget; + private int _entryIndex; + private int _entryDetailIndex; + private int _indexHistoryWrite; + private int _indexHistoryRead; + private uint _historyFrameIndex; + + public PerformanceManagerGeneric(Memory buffer, ref AudioRendererConfiguration parameter) + { + _buffer = buffer; + _frameSize = GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + + _entryCountPerFrame = (int)GetEntryCount(ref parameter); + _availableFrameCount = buffer.Length / _frameSize - 1; + + _historyFrameIndex = 0; + + _historyBuffer = _buffer[_frameSize..]; + + SetupNewHeader(); + } + + private Span GetBufferFromIndex(Span data, int index) + { + return data.Slice(index * _frameSize, _frameSize); + } + + private ref THeader GetHeaderFromBuffer(Span data, int index) + { + return ref MemoryMarshal.Cast(GetBufferFromIndex(data, index))[0]; + } + + private Span GetEntriesFromBuffer(Span data, int index) + { + return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf(), GetEntriesSize())); + } + + private Span GetEntriesDetailFromBuffer(Span data, int index) + { + return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf() + GetEntriesSize(), GetEntriesDetailSize())); + } + + private void SetupNewHeader() + { + _entryIndex = 0; + _entryDetailIndex = 0; + + CurrentHeader.SetEntryCount(0); + CurrentHeader.SetEntryDetailCount(0); + } + + public static uint GetEntryCount(ref AudioRendererConfiguration parameter) + { + return parameter.VoiceCount + parameter.EffectCount + parameter.SubMixBufferCount + parameter.SinkCount + 1; + } + + public int GetEntriesSize() + { + return Unsafe.SizeOf() * _entryCountPerFrame; + } + + public static int GetEntriesDetailSize() + { + return Unsafe.SizeOf() * MaxFrameDetailCount; + } + + public static int GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter) + { + return Unsafe.SizeOf() * (int)GetEntryCount(ref parameter) + GetEntriesDetailSize() + Unsafe.SizeOf(); + } + + public override uint CopyHistories(Span performanceOutput) + { + if (performanceOutput.IsEmpty) + { + return 0; + } + + int nextOffset = 0; + + while (_indexHistoryRead != _indexHistoryWrite) + { + if (nextOffset >= performanceOutput.Length) + { + break; + } + + ref THeader inputHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, _indexHistoryRead); + Span inputEntries = GetEntriesFromBuffer(_historyBuffer.Span, _indexHistoryRead); + Span inputEntriesDetail = GetEntriesDetailFromBuffer(_historyBuffer.Span, _indexHistoryRead); + + Span targetSpan = performanceOutput[nextOffset..]; + + // NOTE: We check for the space for two headers for the final blank header. + int requiredSpace = Unsafe.SizeOf() + Unsafe.SizeOf() * inputHeader.GetEntryCount() + + Unsafe.SizeOf() * inputHeader.GetEntryDetailCount() + + Unsafe.SizeOf(); + + if (targetSpan.Length < requiredSpace) + { + break; + } + + ref THeader outputHeader = ref MemoryMarshal.Cast(targetSpan)[0]; + + nextOffset += Unsafe.SizeOf(); + + Span outputEntries = MemoryMarshal.Cast(performanceOutput[nextOffset..]); + + int totalProcessingTime = 0; + + int effectiveEntryCount = 0; + + for (int entryIndex = 0; entryIndex < inputHeader.GetEntryCount(); entryIndex++) + { + ref TEntry input = ref inputEntries[entryIndex]; + + if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0) + { + ref TEntry output = ref outputEntries[effectiveEntryCount++]; + + output = input; + + nextOffset += Unsafe.SizeOf(); + + totalProcessingTime += input.GetProcessingTime(); + } + } + + Span outputEntriesDetail = MemoryMarshal.Cast(performanceOutput[nextOffset..]); + + int effectiveEntryDetailCount = 0; + + for (int entryDetailIndex = 0; entryDetailIndex < inputHeader.GetEntryDetailCount(); entryDetailIndex++) + { + ref TEntryDetail input = ref inputEntriesDetail[entryDetailIndex]; + + if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0) + { + ref TEntryDetail output = ref outputEntriesDetail[effectiveEntryDetailCount++]; + + output = input; + + nextOffset += Unsafe.SizeOf(); + } + } + + outputHeader = inputHeader; + outputHeader.SetMagic(MagicPerformanceBuffer); + outputHeader.SetTotalProcessingTime(totalProcessingTime); + outputHeader.SetNextOffset(nextOffset); + outputHeader.SetEntryCount(effectiveEntryCount); + outputHeader.SetEntryDetailCount(effectiveEntryDetailCount); + + _indexHistoryRead = (_indexHistoryRead + 1) % _availableFrameCount; + } + + if (nextOffset < performanceOutput.Length && (performanceOutput.Length - nextOffset) >= Unsafe.SizeOf()) + { + ref THeader outputHeader = ref MemoryMarshal.Cast(performanceOutput[nextOffset..])[0]; + + outputHeader = default; + } + + return (uint)nextOffset; + } + + public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId) + { + performanceEntry = new PerformanceEntryAddresses + { + BaseMemory = SpanMemoryManager.Cast(CurrentBuffer), + EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(), + }; + + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + Unsafe.SizeOf() * _entryIndex); + + ref TEntry entry = ref Entries[_entryIndex]; + + performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entry.GetStartTimeOffset(); + performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entry.GetProcessingTimeOffset(); + + entry = default; + entry.SetEntryType(entryType); + entry.SetNodeId(nodeId); + + _entryIndex++; + + return true; + } + + public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId) + { + performanceEntry = null; + + if (_entryDetailIndex > MaxFrameDetailCount) + { + return false; + } + + performanceEntry = new PerformanceEntryAddresses + { + BaseMemory = SpanMemoryManager.Cast(CurrentBuffer), + EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(), + }; + + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex); + + ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex]; + + performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entryDetail.GetStartTimeOffset(); + performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entryDetail.GetProcessingTimeOffset(); + + entryDetail = default; + entryDetail.SetDetailType(detailType); + entryDetail.SetEntryType(entryType); + entryDetail.SetNodeId(nodeId); + + _entryDetailIndex++; + + return true; + } + + public override bool IsTargetNodeId(int target) + { + return _detailTarget == target; + } + + public override void SetTargetNodeId(int target) + { + _detailTarget = target; + } + + public override void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks) + { + if (_availableFrameCount > 0) + { + int targetIndexForHistory = _indexHistoryWrite; + + _indexHistoryWrite = (_indexHistoryWrite + 1) % _availableFrameCount; + + ref THeader targetHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, targetIndexForHistory); + + CurrentBuffer.Span.CopyTo(GetBufferFromIndex(_historyBuffer.Span, targetIndexForHistory)); + + uint targetHistoryFrameIndex = _historyFrameIndex; + + if (_historyFrameIndex == uint.MaxValue) + { + _historyFrameIndex = 0; + } + else + { + _historyFrameIndex++; + } + + targetHeader.SetDspRunningBehind(dspRunningBehind); + targetHeader.SetVoiceDropCount(voiceDropCount); + targetHeader.SetStartRenderingTicks(startRenderingTicks); + targetHeader.SetIndex(targetHistoryFrameIndex); + + // Finally setup the new header + SetupNewHeader(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs b/src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs new file mode 100644 index 000000000..090850018 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs @@ -0,0 +1,48 @@ +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Represents a lite version of used by the + /// + /// + /// This also allows to reduce dependencies on the for unit testing. + /// + public sealed class RendererSystemContext + { + /// + /// The session id of the current renderer. + /// + public int SessionId; + + /// + /// The target channel count for sink. + /// + /// See for usage. + public uint ChannelCount; + + /// + /// The total count of mix buffer. + /// + public uint MixBufferCount; + + /// + /// Instance of the used to derive bug fixes and features of the current audio renderer revision. + /// + public BehaviourContext BehaviourContext; + + /// + /// Instance of the used for upsampling (see ) + /// + public UpsamplerManager UpsamplerManager; + + /// + /// The memory to use for depop processing. + /// + /// + /// See and + /// + public Memory DepopBuffer; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs new file mode 100644 index 000000000..d36c5e260 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs @@ -0,0 +1,102 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Base class used for server information of a sink. + /// + public class BaseSink + { + /// + /// The type of this . + /// + public SinkType Type; + + /// + /// Set to true if the sink is used. + /// + public bool IsUsed; + + /// + /// Set to true if the sink need to be skipped because of invalid state. + /// + public bool ShouldSkip; + + /// + /// The node id of the sink. + /// + public int NodeId; + + /// + /// Create a new . + /// + public BaseSink() + { + CleanUp(); + } + + /// + /// Clean up the internal state of the . + /// + public virtual void CleanUp() + { + Type = TargetSinkType; + IsUsed = false; + ShouldSkip = false; + } + + /// + /// The target handled by this . + /// + public virtual SinkType TargetSinkType => SinkType.Invalid; + + /// + /// Check if the sent by the user match the internal . + /// + /// The user parameter. + /// Return true, if the sent by the user match the internal . + public bool IsTypeValid(ref SinkInParameter parameter) + { + return parameter.Type == TargetSinkType; + } + + /// + /// Update the state during command generation. + /// + public virtual void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetSinkType); + } + + /// + /// Update the internal common parameters from user parameter. + /// + /// The user parameter. + protected void UpdateStandardParameter(ref SinkInParameter parameter) + { + if (IsUsed != parameter.IsUsed) + { + IsUsed = parameter.IsUsed; + NodeId = parameter.NodeId; + } + } + + /// + /// Update the internal state from user parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The user output status. + /// The mapper to use. + public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + errorInfo = new ErrorInfo(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs new file mode 100644 index 000000000..097757988 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs @@ -0,0 +1,109 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Server information for a circular buffer sink. + /// + public class CircularBufferSink : BaseSink + { + /// + /// The circular buffer parameter. + /// + public CircularBufferParameter Parameter; + + /// + /// The last written data offset on the circular buffer. + /// + private uint _lastWrittenOffset; + + /// + /// THe previous written offset of the circular buffer. + /// + private uint _oldWrittenOffset; + + /// + /// The current offset to write data on the circular buffer. + /// + public uint CurrentWriteOffset { get; private set; } + + /// + /// The of the circular buffer. + /// + public AddressInfo CircularBufferAddressInfo; + + public CircularBufferSink() + { + CircularBufferAddressInfo = AddressInfo.Create(); + } + + public override SinkType TargetSinkType => SinkType.CircularBuffer; + + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + errorInfo = new BehaviourParameter.ErrorInfo(); + outStatus = new SinkOutStatus(); + + Debug.Assert(IsTypeValid(ref parameter)); + + ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + if (parameter.IsUsed != IsUsed || ShouldSkip) + { + UpdateStandardParameter(ref parameter); + + if (parameter.IsUsed) + { + Debug.Assert(CircularBufferAddressInfo.CpuAddress == 0); + Debug.Assert(CircularBufferAddressInfo.GetReference(false) == 0); + + ShouldSkip = !mapper.TryAttachBuffer(out errorInfo, ref CircularBufferAddressInfo, inputDeviceParameter.BufferAddress, inputDeviceParameter.BufferSize); + } + else + { + Debug.Assert(CircularBufferAddressInfo.CpuAddress != 0); + Debug.Assert(CircularBufferAddressInfo.GetReference(false) != 0); + } + + Parameter = inputDeviceParameter; + } + + outStatus.LastWrittenOffset = _lastWrittenOffset; + } + + public override void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetSinkType); + + if (IsUsed) + { + uint frameSize = Constants.TargetSampleSize * Parameter.SampleCount * Parameter.InputCount; + + _lastWrittenOffset = _oldWrittenOffset; + + _oldWrittenOffset = CurrentWriteOffset; + + CurrentWriteOffset += frameSize; + + if (Parameter.BufferSize > 0) + { + CurrentWriteOffset %= Parameter.BufferSize; + } + } + } + + public override void CleanUp() + { + CircularBufferAddressInfo = AddressInfo.Create(); + _lastWrittenOffset = 0; + _oldWrittenOffset = 0; + CurrentWriteOffset = 0; + base.CleanUp(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs new file mode 100644 index 000000000..e03fe11d4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs @@ -0,0 +1,75 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Server information for a device sink. + /// + public class DeviceSink : BaseSink + { + /// + /// The downmix coefficients. + /// + public float[] DownMixCoefficients; + + /// + /// The device parameters. + /// + public DeviceParameter Parameter; + + /// + /// The upsampler instance used by this sink. + /// + /// Null if no upsampling is needed. + public UpsamplerState UpsamplerState; + + /// + /// Create a new . + /// + public DeviceSink() + { + DownMixCoefficients = new float[4]; + } + + public override void CleanUp() + { + UpsamplerState?.Release(); + + UpsamplerState = null; + + base.CleanUp(); + } + + public override SinkType TargetSinkType => SinkType.Device; + + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + if (parameter.IsUsed != IsUsed) + { + UpdateStandardParameter(ref parameter); + Parameter = inputDeviceParameter; + } + else + { + Parameter.DownMixParameterEnabled = inputDeviceParameter.DownMixParameterEnabled; + inputDeviceParameter.DownMixParameter.AsSpan().CopyTo(Parameter.DownMixParameter.AsSpan()); + } + + Parameter.DownMixParameter.AsSpan().CopyTo(DownMixCoefficients.AsSpan()); + + errorInfo = new BehaviourParameter.ErrorInfo(); + outStatus = new SinkOutStatus(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs new file mode 100644 index 000000000..951984d8c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs @@ -0,0 +1,56 @@ +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Sink context. + /// + public class SinkContext + { + /// + /// Storage for . + /// + private BaseSink[] _sinks; + + /// + /// The total sink count. + /// + private uint _sinkCount; + + /// + /// Initialize the . + /// + /// The total sink count. + public void Initialize(uint sinksCount) + { + _sinkCount = sinksCount; + _sinks = new BaseSink[_sinkCount]; + + for (int i = 0; i < _sinkCount; i++) + { + _sinks[i] = new BaseSink(); + } + } + + /// + /// Get the total sink count. + /// + /// The total sink count. + public uint GetCount() + { + return _sinkCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref BaseSink GetSink(int id) + { + Debug.Assert(id >= 0 && id < _sinkCount); + + return ref _sinks[id]; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs new file mode 100644 index 000000000..e408692ab --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs @@ -0,0 +1,299 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Splitter context. + /// + public class SplitterContext + { + /// + /// Storage for . + /// + private Memory _splitters; + + /// + /// Storage for . + /// + private Memory _splitterDestinations; + + /// + /// If set to true, trust the user destination count in . + /// + public bool IsBugFixed { get; private set; } + + /// + /// Initialize . + /// + /// The behaviour context. + /// The audio renderer configuration. + /// The . + /// Return true if the initialization was successful. + public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator) + { + if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0) + { + Setup(Memory.Empty, Memory.Empty, false); + + return true; + } + + Memory splitters = workBufferAllocator.Allocate(parameter.SplitterCount, SplitterState.Alignment); + + if (splitters.IsEmpty) + { + return false; + } + + int splitterId = 0; + + foreach (ref SplitterState splitter in splitters.Span) + { + splitter = new SplitterState(splitterId++); + } + + Memory splitterDestinations = workBufferAllocator.Allocate(parameter.SplitterDestinationCount, + SplitterDestination.Alignment); + + if (splitterDestinations.IsEmpty) + { + return false; + } + + int splitterDestinationId = 0; + foreach (ref SplitterDestination data in splitterDestinations.Span) + { + data = new SplitterDestination(splitterDestinationId++); + } + + SplitterState.InitializeSplitters(splitters.Span); + + Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed()); + + return true; + } + + /// + /// Get the work buffer size while adding the size needed for splitter to operate. + /// + /// The current size. + /// The behaviour context. + /// The renderer configuration. + /// Return the new size taking splitter into account. + public static ulong GetWorkBufferSize(ulong size, ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter) + { + if (behaviourContext.IsSplitterSupported()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterCount, SplitterState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment); + + if (behaviourContext.IsSplitterBugFixed()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, 0x10); + } + + return size; + } + + return size; + } + + /// + /// Setup the instance. + /// + /// The storage. + /// The storage. + /// If set to true, trust the user destination count in . + private void Setup(Memory splitters, Memory splitterDestinations, bool isBugFixed) + { + _splitters = splitters; + _splitterDestinations = splitterDestinations; + IsBugFixed = isBugFixed; + } + + /// + /// Clear the new connection flag. + /// + private void ClearAllNewConnectionFlag() + { + foreach (ref SplitterState splitter in _splitters.Span) + { + splitter.ClearNewConnectionFlag(); + } + } + + /// + /// Get the destination count using the count of splitter. + /// + /// The destination count using the count of splitter. + public int GetDestinationCountPerStateForCompatibility() + { + if (_splitters.IsEmpty) + { + return 0; + } + + return _splitterDestinations.Length / _splitters.Length; + } + + /// + /// Update one or multiple from user parameters. + /// + /// The splitter header. + /// The raw data after the splitter header. + private void UpdateState(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + { + for (int i = 0; i < inputHeader.SplitterCount; i++) + { + SplitterInParameter parameter = MemoryMarshal.Read(input); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + if (parameter.Id >= 0 && parameter.Id < _splitters.Length) + { + ref SplitterState splitter = ref GetState(parameter.Id); + + splitter.Update(this, ref parameter, input[Unsafe.SizeOf()..]); + } + + input = input[(0x1C + parameter.DestinationCount * 4)..]; + } + } + } + + /// + /// Update one or multiple from user parameters. + /// + /// The splitter header. + /// The raw data after the splitter header. + private void UpdateData(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + { + for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) + { + SplitterDestinationInParameter parameter = MemoryMarshal.Read(input); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length) + { + ref SplitterDestination destination = ref GetDestination(parameter.Id); + + destination.Update(parameter); + } + + input = input[Unsafe.SizeOf()..]; + } + } + } + + /// + /// Update splitter from user parameters. + /// + /// The input raw user data. + /// The total consumed size. + /// Return true if the update was successful. + public bool Update(ReadOnlySpan input, out int consumedSize) + { + if (_splitterDestinations.IsEmpty || _splitters.IsEmpty) + { + consumedSize = 0; + + return true; + } + + int originalSize = input.Length; + + SplitterInParameterHeader header = SpanIOHelper.Read(ref input); + + if (header.IsMagicValid()) + { + ClearAllNewConnectionFlag(); + + UpdateState(ref header, ref input); + UpdateData(ref header, ref input); + + consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10); + + return true; + } + + consumedSize = 0; + + return false; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref SplitterState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_splitters, id, (uint)_splitters.Length); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref SplitterDestination GetDestination(int id) + { + return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + public Memory GetDestinationMemory(int id) + { + return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); + } + + /// + /// Get a in the at and pass to . + /// + /// The index to use to get the . + /// The index of the . + /// A . + public Span GetDestination(int id, int destinationId) + { + ref SplitterState splitter = ref GetState(id); + + return splitter.GetData(destinationId); + } + + /// + /// Return true if the audio renderer has any splitters. + /// + /// True if the audio renderer has any splitters. + public bool UsingSplitter() + { + return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty; + } + + /// + /// Update the internal state of all splitters. + /// + public void UpdateInternalState() + { + foreach (ref SplitterState splitter in _splitters.Span) + { + splitter.UpdateInternalState(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs new file mode 100644 index 000000000..1faf7921f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs @@ -0,0 +1,193 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter destination. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)] + public struct SplitterDestination + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mix; + private MixArray _previousMix; + + /// + /// Pointer to the next linked element. + /// + private unsafe SplitterDestination* _next; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if the internal state need to be updated. + /// + [MarshalAs(UnmanagedType.I1)] + public bool NeedToUpdateInternalState; + + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix); + + /// + /// Previous mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix); + + /// + /// Get the of the next element or if not present. + /// + public readonly Span Next + { + get + { + unsafe + { + return _next != null ? new Span(_next, 1) : Span.Empty; + } + } + } + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterDestination(int id) : this() + { + Id = id; + DestinationId = Constants.UnusedMixId; + + ClearVolumes(); + } + + /// + /// Update the from user parameter. + /// + /// The user parameter. + public void Update(SplitterDestinationInParameter parameter) + { + Debug.Assert(Id == parameter.Id); + + if (parameter.IsMagicValid() && Id == parameter.Id) + { + DestinationId = parameter.DestinationId; + + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (!IsUsed && parameter.IsUsed) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + + NeedToUpdateInternalState = false; + } + + IsUsed = parameter.IsUsed; + } + } + + /// + /// Update the internal state of the instance. + /// + public void UpdateInternalState() + { + if (IsUsed && NeedToUpdateInternalState) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + } + + NeedToUpdateInternalState = false; + } + + /// + /// Set the update internal state marker. + /// + public void MarkAsNeedToUpdateInternalState() + { + NeedToUpdateInternalState = true; + } + + /// + /// Return true if the is used and has a destination. + /// + /// True if the is used and has a destination. + public readonly bool IsConfigured() + { + return IsUsed && DestinationId != Constants.UnusedMixId; + } + + /// + /// Get the volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolume(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return MixBufferVolume[destinationIndex]; + } + + /// + /// Clear the volumes. + /// + public void ClearVolumes() + { + MixBufferVolume.Clear(); + PreviousMixBufferVolume.Clear(); + } + + /// + /// Link the next element to the given . + /// + /// The given to link. + public void Link(ref SplitterDestination next) + { + unsafe + { + fixed (SplitterDestination* nextPtr = &next) + { + _next = nextPtr; + } + } + } + + /// + /// Remove the link to the next element. + /// + public void Unlink() + { + unsafe + { + _next = null; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs new file mode 100644 index 000000000..e08ee9ea7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs @@ -0,0 +1,221 @@ +using Ryujinx.Audio.Renderer.Parameter; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)] + public struct SplitterState + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// Target sample rate to use on the splitter. + /// + public uint SampleRate; + + /// + /// Count of splitter destinations (). + /// + public int DestinationCount; + + /// + /// Set to true if the splitter has a new connection. + /// + [MarshalAs(UnmanagedType.I1)] + public bool HasNewConnection; + + /// + /// Linked list of . + /// + private unsafe SplitterDestination* _destinationsData; + + /// + /// Span to the first element of the linked list of . + /// + public readonly Span Destinations + { + get + { + unsafe + { + return (IntPtr)_destinationsData != IntPtr.Zero ? new Span(_destinationsData, 1) : Span.Empty; + } + } + } + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterState(int id) : this() + { + Id = id; + } + + public readonly Span GetData(int index) + { + int i = 0; + + Span result = Destinations; + + while (i < index) + { + if (result.IsEmpty) + { + break; + } + + result = result[0].Next; + i++; + } + + return result; + } + + /// + /// Clear the new connection flag. + /// + public void ClearNewConnectionFlag() + { + HasNewConnection = false; + } + + /// + /// Utility function to apply a given to all . + /// + /// The action to execute on each elements. + private readonly void ForEachDestination(SpanAction action) + { + Span temp = Destinations; + + int i = 0; + + while (true) + { + if (temp.IsEmpty) + { + break; + } + + Span next = temp[0].Next; + + action.Invoke(temp, i++); + + temp = next; + } + } + + /// + /// Update the from user parameter. + /// + /// The splitter context. + /// The user parameter. + /// The raw input data after the . + public void Update(SplitterContext context, ref SplitterInParameter parameter, ReadOnlySpan input) + { + ClearLinks(); + + int destinationCount; + + if (context.IsBugFixed) + { + destinationCount = parameter.DestinationCount; + } + else + { + destinationCount = Math.Min(context.GetDestinationCountPerStateForCompatibility(), parameter.DestinationCount); + } + + if (destinationCount > 0) + { + ReadOnlySpan destinationIds = MemoryMarshal.Cast(input); + + Memory destination = context.GetDestinationMemory(destinationIds[0]); + + SetDestination(ref destination.Span[0]); + + DestinationCount = destinationCount; + + for (int i = 1; i < destinationCount; i++) + { + Memory nextDestination = context.GetDestinationMemory(destinationIds[i]); + + destination.Span[0].Link(ref nextDestination.Span[0]); + destination = nextDestination; + } + } + + Debug.Assert(parameter.Id == Id); + + if (parameter.Id == Id) + { + SampleRate = parameter.SampleRate; + HasNewConnection = true; + } + } + + /// + /// Set the head of the linked list of . + /// + /// A reference to a . + public void SetDestination(ref SplitterDestination newValue) + { + unsafe + { + fixed (SplitterDestination* newValuePtr = &newValue) + { + _destinationsData = newValuePtr; + } + } + } + + /// + /// Update the internal state of this instance. + /// + public readonly void UpdateInternalState() + { + ForEachDestination((destination, _) => destination[0].UpdateInternalState()); + } + + /// + /// Clear all links from the . + /// + public void ClearLinks() + { + ForEachDestination((destination, _) => destination[0].Unlink()); + + unsafe + { + _destinationsData = (SplitterDestination*)IntPtr.Zero; + } + } + + /// + /// Initialize a given . + /// + /// All the to initialize. + public static void InitializeSplitters(Span splitters) + { + foreach (ref SplitterState splitter in splitters) + { + unsafe + { + splitter._destinationsData = (SplitterDestination*)IntPtr.Zero; + } + + splitter.DestinationCount = 0; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs new file mode 100644 index 000000000..22eebc7cc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs @@ -0,0 +1,610 @@ +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.Buffers; +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 _inputOrigin; + private readonly ReadOnlyMemory _outputOrigin; + private ReadOnlyMemory _input; + + private Memory _output; + private readonly uint _processHandle; + private BehaviourContext _behaviourContext; + + private UpdateDataHeader _inputHeader; + private readonly Memory _outputHeader; + + private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; + + public StateUpdater(ReadOnlyMemory input, Memory output, uint processHandle, BehaviourContext behaviourContext) + { + _input = input; + _inputOrigin = _input; + _output = output; + _outputOrigin = _output; + _processHandle = processHandle; + _behaviourContext = behaviourContext; + + _inputHeader = SpanIOHelper.Read(ref _input); + + _outputHeader = SpanMemoryManager.Cast(_output[..Unsafe.SizeOf()]); + OutputHeader.Initialize(_behaviourContext.UserRevision); + _output = _output[Unsafe.SizeOf()..]; + } + + public ResultCode UpdateBehaviourContext() + { + BehaviourParameter parameter = SpanIOHelper.Read(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()) + { + return ResultCode.InvalidUpdateInfo; + } + + return ResultCode.Success; + } + + public ResultCode UpdateMemoryPools(Span memoryPools) + { + PoolMapper mapper = new(_processHandle, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + if (memoryPools.Length * Unsafe.SizeOf() != _inputHeader.MemoryPoolsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + foreach (ref MemoryPoolState memoryPool in memoryPools) + { + MemoryPoolInParameter parameter = SpanIOHelper.Read(ref _input); + + ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef(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() * memoryPools.Length); + OutputHeader.TotalSize += OutputHeader.MemoryPoolsSize; + + return ResultCode.Success; + } + + public ResultCode UpdateVoiceChannelResources(VoiceContext context) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoiceResourcesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + for (int i = 0; i < context.GetCount(); i++) + { + VoiceChannelResourceInParameter parameter = SpanIOHelper.Read(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 memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoicesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.VoicesSize].Span); + + _input = _input[(int)_inputHeader.VoicesSize..]; + + PoolMapper mapper = new(_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; + } + + Memory[] voiceUpdateStatesArray = ArrayPool>.Shared.Rent(Constants.VoiceChannelCountMax); + + Span> voiceUpdateStates = voiceUpdateStatesArray.AsSpan(0, Constants.VoiceChannelCountMax); + + // Start processing + for (int i = 0; i < context.GetCount(); i++) + { + VoiceInParameter parameter = parameters[i]; + + voiceUpdateStates.Fill(Memory.Empty); + + ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(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); + } + } + + ArrayPool>.Shared.Return(voiceUpdateStatesArray); + + int currentOutputSize = _output.Length; + + OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.VoicesSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize); + + return ResultCode.Success; + } + + private static void ResetEffect(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + effect.ForceUnmapBuffers(mapper); + + effect = parameter.Type switch + { + EffectType.Invalid => new BaseEffect(), + EffectType.BufferMix => new BufferMixEffect(), + EffectType.AuxiliaryBuffer => new AuxiliaryBufferEffect(), + EffectType.Delay => new DelayEffect(), + EffectType.Reverb => new ReverbEffect(), + EffectType.Reverb3d => new Reverb3dEffect(), + EffectType.BiquadFilter => new BiquadFilterEffect(), + EffectType.Limiter => new LimiterEffect(), + EffectType.CaptureBuffer => new CaptureBufferEffect(), + EffectType.Compressor => new CompressorEffect(), + _ => throw new NotImplementedException($"EffectType {parameter.Type} not implemented!"), + }; + } + + public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + { + if (_behaviourContext.IsEffectInfoVersion2Supported()) + { + return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools); + } + + return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools); + } + + public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.EffectsSize].Span); + + _input = _input[(int)_inputHeader.EffectsSize..]; + + PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + for (int i = 0; i < context.GetCount(); i++) + { + EffectInParameterVersion2 parameter = parameters[i]; + + ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef(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() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.EffectsSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + + return ResultCode.Success; + } + + public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.EffectsSize].Span); + + _input = _input[(int)_inputHeader.EffectsSize..]; + + PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + for (int i = 0; i < context.GetCount(); i++) + { + EffectInParameterVersion1 parameter = parameters[i]; + + ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef(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() * 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[consumedSize..]; + + return ResultCode.Success; + } + + return ResultCode.InvalidUpdateInfo; + } + + private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan 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(_input.Span)[0]; + + mixCount = parameter.MixCount; + + inputSize += (uint)Unsafe.SizeOf(); + } + else + { + mixCount = mixContext.GetCount(); + } + + inputMixSize = mixCount * (uint)Unsafe.SizeOf(); + + inputSize += inputMixSize; + + if (inputSize != _inputHeader.MixesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) + { + _input = _input[Unsafe.SizeOf()..]; + } + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Span[..(int)inputMixSize]); + + _input = _input[(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(); + + sink = parameter.Type switch + { + SinkType.Invalid => new BaseSink(), + SinkType.CircularBuffer => new CircularBufferSink(), + SinkType.Device => new DeviceSink(), + _ => throw new NotImplementedException($"SinkType {parameter.Type} not implemented!"), + }; + } + + public ResultCode UpdateSinks(SinkContext context, Memory memoryPools) + { + PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.SinksSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input[..(int)_inputHeader.SinksSize].Span); + + _input = _input[(int)_inputHeader.SinksSize..]; + + for (int i = 0; i < context.GetCount(); i++) + { + SinkInParameter parameter = parameters[i]; + ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef(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() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.SinksSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize); + + return ResultCode.Success; + } + + public ResultCode UpdatePerformanceBuffer(PerformanceManager manager, Span performanceOutput) + { + if (Unsafe.SizeOf() != _inputHeader.PerformanceBufferSize) + { + return ResultCode.InvalidUpdateInfo; + } + + PerformanceInParameter parameter = SpanIOHelper.Read(ref _input); + + ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + if (manager != null) + { + outStatus.HistorySize = manager.CopyHistories(performanceOutput); + + manager.SetTargetNodeId(parameter.TargetNodeId); + } + else + { + outStatus.HistorySize = 0; + } + + OutputHeader.PerformanceBufferSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.PerformanceBufferSize; + + return ResultCode.Success; + } + + public ResultCode UpdateErrorInfo() + { + ref BehaviourErrorInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + _behaviourContext.CopyErrorInfo(outStatus.ErrorInfos.AsSpan(), out outStatus.ErrorInfosCount); + + OutputHeader.BehaviourSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.BehaviourSize; + + return ResultCode.Success; + } + + public ResultCode UpdateRendererInfo(ulong elapsedFrameCount) + { + ref RendererInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + outStatus.ElapsedFrameCount = elapsedFrameCount; + + OutputHeader.RenderInfoSize = (uint)Unsafe.SizeOf(); + 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; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs new file mode 100644 index 000000000..0db61c5e6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The execution mode of an . + /// + public enum AudioRendererExecutionMode : byte + { + /// + /// Automatically send commands to the DSP at a fixed rate (see + /// + Auto, + + /// + /// Audio renderer operation needs to be done manually via ExecuteAudioRenderer. + /// + /// This is not supported on the DSP and is as such stubbed. + Manual, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs new file mode 100644 index 000000000..fd9e231cf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The rendering device of an . + /// + public enum AudioRendererRenderingDevice : byte + { + /// + /// Rendering is performed on the DSP. + /// + /// + /// Only supports . + /// + Dsp, + + /// + /// Rendering is performed on the CPU. + /// + /// + /// Only supports . + /// + Cpu, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs b/src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs new file mode 100644 index 000000000..46aae05ab --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs @@ -0,0 +1,39 @@ +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The internal play state of a + /// + public enum PlayState + { + /// + /// The voice has been started and is playing. + /// + Started, + + /// + /// The voice has been stopped. + /// + /// + /// This cannot be directly set by user. + /// See for correct usage. + /// + Stopped, + + /// + /// The user asked the voice to be stopped. + /// + /// + /// This is changed to the state after command generation. + /// + /// + Stopping, + + /// + /// The voice has been paused by user request. + /// + /// + /// The user can resume to the state. + /// + Paused, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs new file mode 100644 index 000000000..a3c442a45 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs @@ -0,0 +1,14 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + public struct UpsamplerBufferState + { + public const int HistoryLength = 20; + + public float Scale; + public Array20 History; + public bool Initialized; + public int Phase; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs new file mode 100644 index 000000000..dbc2c9b3f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + /// + /// Upsampler manager. + /// + public class UpsamplerManager + { + /// + /// Work buffer for upsampler. + /// + private readonly Memory _upSamplerWorkBuffer; + + /// + /// Global lock of the object. + /// + private readonly object _lock = new(); + + /// + /// The upsamplers instances. + /// + private readonly UpsamplerState[] _upsamplers; + + /// + /// The count of upsamplers. + /// + private readonly uint _count; + + /// + /// Create a new . + /// + /// Work buffer for upsampler. + /// The count of upsamplers. + public UpsamplerManager(Memory upSamplerWorkBuffer, uint count) + { + _upSamplerWorkBuffer = upSamplerWorkBuffer; + _count = count; + + _upsamplers = new UpsamplerState[_count]; + } + + /// + /// Allocate a new . + /// + /// A new or null if out of memory. + public UpsamplerState Allocate() + { + int workBufferOffset = 0; + + lock (_lock) + { + for (int i = 0; i < _count; i++) + { + if (_upsamplers[i] == null) + { + _upsamplers[i] = new UpsamplerState(this, i, _upSamplerWorkBuffer.Slice(workBufferOffset, Constants.UpSampleEntrySize), Constants.TargetSampleCount); + + return _upsamplers[i]; + } + + workBufferOffset += Constants.UpSampleEntrySize; + } + } + + return null; + } + + /// + /// Free a at the given index. + /// + /// The index of the to free. + public void Free(int index) + { + lock (_lock) + { + Debug.Assert(_upsamplers[index] != null); + + _upsamplers[index] = null; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs new file mode 100644 index 000000000..39a58c91a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs @@ -0,0 +1,68 @@ +using System; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + /// + /// Server state for a upsampling. + /// + public class UpsamplerState + { + /// + /// The output buffer containing the target samples. + /// + public Memory OutputBuffer { get; } + + /// + /// The target sample count. + /// + public uint SampleCount { get; } + + /// + /// The index of the . (used to free it) + /// + private readonly int _index; + + /// + /// The . + /// + private readonly UpsamplerManager _manager; + + /// + /// The source sample count. + /// + public uint SourceSampleCount; + + /// + /// The input buffer indices of the buffers holding the samples that need upsampling. + /// + public ushort[] InputBufferIndices; + + /// + /// State of each input buffer index kept across invocations of the upsampler. + /// + public UpsamplerBufferState[] BufferStates; + + /// + /// Create a new . + /// + /// The upsampler manager. + /// The index of the . (used to free it) + /// The output buffer used to contain the target samples. + /// The target sample count. + public UpsamplerState(UpsamplerManager manager, int index, Memory outputBuffer, uint sampleCount) + { + _manager = manager; + _index = index; + OutputBuffer = outputBuffer; + SampleCount = sampleCount; + } + + /// + /// Release the . + /// + public void Release() + { + _manager.Free(_index); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs new file mode 100644 index 000000000..e3d5f797d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs @@ -0,0 +1,40 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// Server state for a voice channel resource. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xD0, Pack = Alignment)] + public struct VoiceChannelResource + { + public const int Alignment = 0x10; + + /// + /// Mix volumes for the resource. + /// + public Array24 Mix; + + /// + /// Previous mix volumes for resource. + /// + public Array24 PreviousMix; + + /// + /// The id of the resource. + /// + public uint Id; + + /// + /// Indicate if the resource is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + public void UpdateState() + { + Mix.AsSpan().CopyTo(PreviousMix.AsSpan()); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs new file mode 100644 index 000000000..7ce7143ed --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs @@ -0,0 +1,149 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// Voice context. + /// + public class VoiceContext + { + /// + /// Storage of the sorted indices to . + /// + private Memory _sortedVoices; + + /// + /// Storage for . + /// + private Memory _voices; + + /// + /// Storage for . + /// + private Memory _voiceChannelResources; + + /// + /// Storage for that are used during audio renderer server updates. + /// + private Memory _voiceUpdateStatesCpu; + + /// + /// Storage for for the . + /// + private Memory _voiceUpdateStatesDsp; + + /// + /// The total voice count. + /// + private uint _voiceCount; + + public void Initialize(Memory sortedVoices, Memory voices, Memory voiceChannelResources, Memory voiceUpdateStatesCpu, Memory voiceUpdateStatesDsp, uint voiceCount) + { + _sortedVoices = sortedVoices; + _voices = voices; + _voiceChannelResources = voiceChannelResources; + _voiceUpdateStatesCpu = voiceUpdateStatesCpu; + _voiceUpdateStatesDsp = voiceUpdateStatesDsp; + _voiceCount = voiceCount; + } + + /// + /// Get the total voice count. + /// + /// The total voice count. + public uint GetCount() + { + return _voiceCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref VoiceChannelResource GetChannelResource(int id) + { + return ref SpanIOHelper.GetFromMemory(_voiceChannelResources, id, _voiceCount); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + /// The returned should only be used when updating the server state. + public Memory GetUpdateStateForCpu(int id) + { + return SpanIOHelper.GetMemory(_voiceUpdateStatesCpu, id, _voiceCount); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + /// The returned should only be used in the context of processing on the . + public Memory GetUpdateStateForDsp(int id) + { + return SpanIOHelper.GetMemory(_voiceUpdateStatesDsp, id, _voiceCount); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref VoiceState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_voices, id, _voiceCount); + } + + public ref VoiceState GetSortedState(int id) + { + Debug.Assert(id >= 0 && id < _voiceCount); + + return ref GetState(_sortedVoices.Span[id]); + } + + /// + /// Update internal state during command generation. + /// + public void UpdateForCommandGeneration() + { + _voiceUpdateStatesDsp.CopyTo(_voiceUpdateStatesCpu); + } + + /// + /// Sort the internal voices by priority and sorting order (if the priorities match). + /// + public void Sort() + { + for (int i = 0; i < _voiceCount; i++) + { + _sortedVoices.Span[i] = i; + } + + int[] sortedVoicesTemp = _sortedVoices[..(int)GetCount()].ToArray(); + + Array.Sort(sortedVoicesTemp, (a, b) => + { + ref VoiceState aState = ref GetState(a); + ref VoiceState bState = ref GetState(b); + + int result = aState.Priority.CompareTo(bState.Priority); + + if (result == 0) + { + return aState.SortingOrder.CompareTo(bState.SortingOrder); + } + + return result; + }); + + sortedVoicesTemp.AsSpan().CopyTo(_sortedVoices.Span); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs new file mode 100644 index 000000000..225f7d31b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs @@ -0,0 +1,701 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +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; +using PlayState = Ryujinx.Audio.Renderer.Server.Types.PlayState; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + [StructLayout(LayoutKind.Sequential, Pack = Alignment)] + public struct VoiceState + { + public const int Alignment = 0x10; + + /// + /// Set to true if the voice is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool InUse; + + /// + /// Set to true if the voice is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + [MarshalAs(UnmanagedType.I1)] + public bool WasPlaying; + + /// + /// The of the voice. + /// + public SampleFormat SampleFormat; + + /// + /// The sample rate of the voice. + /// + public uint SampleRate; + + /// + /// The total channel count used. + /// + public uint ChannelsCount; + + /// + /// Id of the voice. + /// + public int Id; + + /// + /// Node id of the voice. + /// + public int NodeId; + + /// + /// The target mix id of the voice. + /// + public int MixId; + + /// + /// The current voice . + /// + public PlayState PlayState; + + /// + /// The previous voice . + /// + public PlayState PreviousPlayState; + + /// + /// The priority of the voice. + /// + public uint Priority; + + /// + /// Target sorting position of the voice. (used to sort voice with the same ) + /// + public uint SortingOrder; + + /// + /// The pitch used on the voice. + /// + public float Pitch; + + /// + /// The output volume of the voice. + /// + public float Volume; + + /// + /// The previous output volume of the voice. + /// + public float PreviousVolume; + + /// + /// Biquad filters to apply to the output of the voice. + /// + public Array2 BiquadFilters; + + /// + /// Total count of of the voice. + /// + public uint WaveBuffersCount; + + /// + /// Current playing of the voice. + /// + public uint WaveBuffersIndex; + + /// + /// Change the behaviour of the voice. + /// + /// This was added on REV5. + public DecodingBehaviour DecodingBehaviour; + + /// + /// User state required by the data source. + /// + /// Only used for as the GC-ADPCM coefficients. + public AddressInfo DataSourceStateAddressInfo; + + /// + /// The wavebuffers of this voice. + /// + public Array4 WaveBuffers; + + /// + /// The channel resource ids associated to the voice. + /// + public Array6 ChannelResourceIds; + + /// + /// The target splitter id of the voice. + /// + public uint SplitterId; + + /// + /// Change the Sample Rate Conversion (SRC) quality of the voice. + /// + /// This was added on REV8. + public SampleRateConversionQuality SrcQuality; + + /// + /// If set to true, the voice was dropped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropFlag; + + /// + /// Set to true if the data source state work buffer wasn't mapped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool DataSourceStateUnmapped; + + /// + /// Set to true if any of the work buffer wasn't mapped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool BufferInfoUnmapped; + + /// + /// The biquad filter initialization state storage. + /// + private BiquadFilterNeedInitializationArrayStruct _biquadFilterNeedInitialization; + + /// + /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. + /// + /// This was added on REV5. + public byte FlushWaveBufferCount; + + [StructLayout(LayoutKind.Sequential, Size = Constants.VoiceBiquadFilterCount)] + private struct BiquadFilterNeedInitializationArrayStruct { } + + /// + /// The biquad filter initialization state array. + /// + public Span BiquadFilterNeedInitialization => SpanHelpers.AsSpan(ref _biquadFilterNeedInitialization); + + /// + /// Initialize the . + /// + public void Initialize() + { + IsNew = false; + VoiceDropFlag = false; + DataSourceStateUnmapped = false; + BufferInfoUnmapped = false; + FlushWaveBufferCount = 0; + PlayState = 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().Clear(); + WaveBuffersCount = 0; + WaveBuffersIndex = 0; + MixId = Constants.UnusedMixId; + SplitterId = Constants.UnusedSplitterId; + DataSourceStateAddressInfo.Setup(0, 0); + + InitializeWaveBuffers(); + } + + /// + /// Initialize the in this . + /// + 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; + } + } + + /// + /// Check if the voice needs to be skipped. + /// + /// Returns true if the voice needs to be skipped. + public readonly bool ShouldSkip() + { + return !InUse || WaveBuffersCount == 0 || DataSourceStateUnmapped || BufferInfoUnmapped || VoiceDropFlag; + } + + /// + /// Return true if the mix has any destinations. + /// + /// True if the mix has any destinations. + public readonly bool HasAnyDestination() + { + return MixId != Constants.UnusedMixId || SplitterId != Constants.UnusedSplitterId; + } + + /// + /// Indicate if the server voice information needs to be updated. + /// + /// The user parameter. + /// Return true, if the server voice information needs to be updated. + private readonly 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; + } + + /// + /// Update the internal state from a user parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + /// The behaviour context. + 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(); + } + } + + /// + /// Update the internal play state from user play state. + /// + /// The target user play state. + public void UpdatePlayState(Common.PlayState userPlayState) + { + PlayState oldServerPlayState = PlayState; + + PreviousPlayState = oldServerPlayState; + + PlayState newServerPlayState; + + switch (userPlayState) + { + case Common.PlayState.Start: + newServerPlayState = PlayState.Started; + break; + + case Common.PlayState.Stop: + if (oldServerPlayState == PlayState.Stopped) + { + return; + } + + newServerPlayState = PlayState.Stopping; + break; + + case Common.PlayState.Pause: + newServerPlayState = PlayState.Paused; + break; + + default: + throw new NotImplementedException($"Unhandled PlayState.{userPlayState}"); + } + + PlayState = newServerPlayState; + } + + /// + /// Write the status of the voice to the given user output. + /// + /// The given user output. + /// The user parameter. + /// The voice states associated to the . + public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan> 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; + } + } + + /// + /// Update the internal state of all the of the . + /// + /// An array of used to report errors when mapping any of the . + /// The user parameter. + /// The voice states associated to the . + /// The mapper to use. + /// The behaviour context. + public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan> 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.Clear(); + } + } + + 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); + } + } + + /// + /// Update the internal state of one of the of the . + /// + /// A used to report errors when mapping the . + /// The to update. + /// The from the user input. + /// The from the user input. + /// If set to true, the server side wavebuffer is considered valid. + /// The mapper to use. + /// The behaviour context. + private void UpdateWaveBuffer(Span 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; + } + } + } + + /// + /// Reset the resources associated to this . + /// + /// The voice context. + 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 dspSharedState = context.GetUpdateStateForDsp(channelResourceId); + + MemoryMarshal.Cast(dspSharedState.Span).Clear(); + + voiceChannelResource.UpdateState(); + } + } + + /// + /// Flush a certain amount of . + /// + /// The amount of wavebuffer to flush. + /// The voice states associated to the . + /// The channel count from user input. + private void FlushWaveBuffers(uint waveBufferCount, Memory[] 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; + } + } + + /// + /// Update the internal parameters for command generation. + /// + /// The voice states associated to the . + /// Return true if this voice should be played. + public bool UpdateParametersForCommandGeneration(Memory[] voiceUpdateStates) + { + if (FlushWaveBufferCount != 0) + { + FlushWaveBuffers(FlushWaveBufferCount, voiceUpdateStates, ChannelsCount); + + FlushWaveBufferCount = 0; + } + + switch (PlayState) + { + case 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 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().Clear(); + voiceUpdateState.Fraction = 0; + voiceUpdateState.LoopContext = new AdpcmLoopContext(); + } + + PlayState = PlayState.Stopped; + WasPlaying = PreviousPlayState == PlayState.Started; + + return WasPlaying; + + case PlayState.Stopped: + case 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 == PlayState.Started; + + return WasPlaying; + default: + throw new NotImplementedException($"{PlayState}"); + } + } + + /// + /// Update the internal state for command generation. + /// + /// The voice context. + /// Return true if this voice should be played. + public bool UpdateForCommandGeneration(VoiceContext context) + { + if (IsNew) + { + ResetResources(context); + PreviousVolume = Volume; + IsNew = false; + } + + Memory[] voiceUpdateStates = new Memory[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < ChannelsCount; i++) + { + voiceUpdateStates[i] = context.GetUpdateStateForDsp(ChannelResourceIds[i]); + } + + return UpdateParametersForCommandGeneration(voiceUpdateStates); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs new file mode 100644 index 000000000..a9946ba44 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs @@ -0,0 +1,105 @@ +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// A wavebuffer used for server update. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 1)] + public struct WaveBuffer + { + /// + /// The of the sample data of the wavebuffer. + /// + public AddressInfo BufferAddressInfo; + + /// + /// The of the context of the wavebuffer. + /// + /// Only used by . + public AddressInfo ContextAddressInfo; + + + /// + /// First sample to play of the wavebuffer. + /// + public uint StartSampleOffset; + + /// + /// Last sample to play of the wavebuffer. + /// + public uint EndSampleOffset; + + /// + /// Set to true if the wavebuffer is looping. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ShouldLoop; + + /// + /// Set to true if the wavebuffer is the end of stream. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Set to true if the wavebuffer wasn't sent to the . + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsSendToAudioProcessor; + + /// + /// First sample to play when looping the wavebuffer. + /// + public uint LoopStartSampleOffset; + + /// + /// Last sample to play when looping the wavebuffer. + /// + public uint LoopEndSampleOffset; + + /// + /// The max loop count. + /// + public int LoopCount; + + /// + /// Create a new for use by the . + /// + /// The target version of the wavebuffer. + /// A new for use by the . + public Common.WaveBuffer ToCommon(int version) + { + Common.WaveBuffer waveBuffer = new() + { + Buffer = BufferAddressInfo.GetReference(true), + BufferSize = (uint)BufferAddressInfo.Size, + }; + + if (ContextAddressInfo.CpuAddress != 0) + { + waveBuffer.Context = ContextAddressInfo.GetReference(true); + waveBuffer.ContextSize = (uint)ContextAddressInfo.Size; + } + + waveBuffer.StartSampleOffset = StartSampleOffset; + waveBuffer.EndSampleOffset = EndSampleOffset; + waveBuffer.Looping = ShouldLoop; + waveBuffer.IsEndOfStream = IsEndOfStream; + + if (version == 2) + { + waveBuffer.LoopCount = LoopCount; + waveBuffer.LoopStartSampleOffset = LoopStartSampleOffset; + waveBuffer.LoopEndSampleOffset = LoopEndSampleOffset; + } + else + { + waveBuffer.LoopCount = -1; + } + + return waveBuffer; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs b/src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs new file mode 100644 index 000000000..8ebf20340 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs @@ -0,0 +1,57 @@ +using System.Runtime.CompilerServices; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// The memory management + /// + /// This is stub for the most part but is kept to permit LLE if wanted. + static class AudioProcessorMemoryManager + { + /// + /// Map the given to the address space. + /// + /// The process owning the CPU memory. + /// The to map. + /// The size of the CPU memory region to map. + /// The address on the address space. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DspAddress Map(uint processHandle, CpuAddress cpuAddress, ulong size) + { + return cpuAddress; + } + + /// + /// Unmap the given from the address space. + /// + /// The process owning the CPU memory. + /// The to unmap. + /// The size of the CPU memory region to unmap. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Unmap(uint processHandle, CpuAddress cpuAddress, ulong size) + { + } + + /// + /// Invalidate the data cache at the given address. + /// + /// The base DSP address to invalidate + /// The size of the DSP memory region to invalidate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidateDspCache(DspAddress address, ulong size) + { + } + + /// + /// Invalidate the CPU data cache at the given address. + /// + /// The base to invalidate + /// The size of the CPU memory region to invalidate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidateDataCache(CpuAddress address, ulong size) + { + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/BitArray.cs b/src/Ryujinx.Audio/Renderer/Utils/BitArray.cs new file mode 100644 index 000000000..8fd0baeae --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/BitArray.cs @@ -0,0 +1,103 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A simple bit array implementation backed by a . + /// + public class BitArray + { + /// + /// The backing storage of the . + /// + private readonly Memory _storage; + + /// + /// Create a new from . + /// + /// The backing storage of the . + public BitArray(Memory storage) + { + _storage = storage; + } + + /// + /// Get the byte position of a given bit index. + /// + /// A bit index. + /// The byte position of a given bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ToPosition(int index) => index / 8; + + /// + /// Get the bit position of a given bit index inside a byte. + /// + /// A bit index. + /// The bit position of a given bit index inside a byte. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ToBitPosition(int index) => index % 8; + + /// + /// Test if the bit at the given index is set. + /// + /// A bit index. + /// Return true if the bit at the given index is set + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Test(int index) + { + ulong mask = 1ul << ToBitPosition(index); + + return (_storage.Span[ToPosition(index)] & mask) == mask; + } + + /// + /// Set the bit at the given index. + /// + /// A bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(int index) + { + Set(index, true); + } + + /// + /// Reset the bit at the given index. + /// + /// A bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset(int index) + { + Set(index, false); + } + + /// + /// Set a bit value at the given index. + /// + /// A bit index. + /// The new bit value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Set(int index, bool value) + { + byte mask = (byte)(1 << ToBitPosition(index)); + + if (value) + { + _storage.Span[ToPosition(index)] |= mask; + } + else + { + _storage.Span[ToPosition(index)] &= (byte)~mask; + } + } + + /// + /// Reset all bits in the storage. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + _storage.Span.Clear(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs b/src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs new file mode 100644 index 000000000..bc2313ccf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs @@ -0,0 +1,99 @@ +using Ryujinx.Audio.Integration; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A that outputs to a wav file. + /// + public class FileHardwareDevice : IHardwareDevice + { + private FileStream _stream; + private readonly uint _channelCount; + private readonly uint _sampleRate; + + private const int HeaderSize = 44; + + public FileHardwareDevice(string path, uint channelCount, uint sampleRate) + { + _stream = File.OpenWrite(path); + _channelCount = channelCount; + _sampleRate = sampleRate; + + _stream.Seek(HeaderSize, SeekOrigin.Begin); + } + + private void UpdateHeader() + { + var writer = new BinaryWriter(_stream); + + long currentPos = writer.Seek(0, SeekOrigin.Current); + + writer.Seek(0, SeekOrigin.Begin); + + writer.Write("RIFF"u8); + writer.Write((int)(writer.BaseStream.Length - 8)); + writer.Write("WAVE"u8); + writer.Write("fmt "u8); + writer.Write(16); + writer.Write((short)1); + writer.Write((short)GetChannelCount()); + writer.Write(GetSampleRate()); + writer.Write(GetSampleRate() * GetChannelCount() * sizeof(short)); + writer.Write((short)(GetChannelCount() * sizeof(short))); + writer.Write((short)(sizeof(short) * 8)); + writer.Write("data"u8); + writer.Write((int)(writer.BaseStream.Length - HeaderSize)); + + writer.Seek((int)currentPos, SeekOrigin.Begin); + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + _stream.Write(MemoryMarshal.Cast(data)); + + UpdateHeader(); + _stream.Flush(); + } + + public void SetVolume(float volume) + { + // Do nothing, volume is not used for FileHardwareDevice at the moment. + } + + public float GetVolume() + { + // FileHardwareDevice does not incorporate volume. + return 0; + } + + public uint GetChannelCount() + { + return _channelCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _stream?.Flush(); + _stream?.Dispose(); + + _stream = null; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs b/src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs new file mode 100644 index 000000000..26907af91 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A simple generic message queue for unmanaged types. + /// + /// The target unmanaged type used + public class Mailbox : IDisposable where T : unmanaged + { + private readonly BlockingCollection _messageQueue; + private readonly BlockingCollection _responseQueue; + + public Mailbox() + { + _messageQueue = new BlockingCollection(1); + _responseQueue = new BlockingCollection(1); + } + + public void SendMessage(T data) + { + _messageQueue.Add(data); + } + + public void SendResponse(T data) + { + _responseQueue.Add(data); + } + + public T ReceiveMessage() + { + return _messageQueue.Take(); + } + + public T ReceiveResponse() + { + return _responseQueue.Take(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _messageQueue.Dispose(); + _responseQueue.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs new file mode 100644 index 000000000..7a6edab19 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs @@ -0,0 +1,71 @@ +namespace Ryujinx.Audio.Renderer.Utils.Math +{ + record struct Matrix2x2 + { + public float M11; + public float M12; + public float M21; + public float M22; + + public Matrix2x2(float m11, float m12, + float m21, float m22) + { + M11 = m11; + M12 = m12; + + M21 = m21; + M22 = m22; + } + + public static Matrix2x2 operator +(Matrix2x2 value1, Matrix2x2 value2) + { + Matrix2x2 m; + + m.M11 = value1.M11 + value2.M11; + m.M12 = value1.M12 + value2.M12; + m.M21 = value1.M21 + value2.M21; + m.M22 = value1.M22 + value2.M22; + + return m; + } + + public static Matrix2x2 operator -(Matrix2x2 value1, float value2) + { + Matrix2x2 m; + + m.M11 = value1.M11 - value2; + m.M12 = value1.M12 - value2; + m.M21 = value1.M21 - value2; + m.M22 = value1.M22 - value2; + + return m; + } + + public static Matrix2x2 operator *(Matrix2x2 value1, float value2) + { + Matrix2x2 m; + + m.M11 = value1.M11 * value2; + m.M12 = value1.M12 * value2; + m.M21 = value1.M21 * value2; + m.M22 = value1.M22 * value2; + + return m; + } + + public static Matrix2x2 operator *(Matrix2x2 value1, Matrix2x2 value2) + { + Matrix2x2 m; + + // First row + m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21; + m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22; + + // Second row + m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21; + m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22; + + return m; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs new file mode 100644 index 000000000..ff0123026 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs @@ -0,0 +1,97 @@ +namespace Ryujinx.Audio.Renderer.Utils.Math +{ + record struct Matrix6x6 + { + public float M11; + public float M12; + public float M13; + public float M14; + public float M15; + public float M16; + + public float M21; + public float M22; + public float M23; + public float M24; + public float M25; + public float M26; + + public float M31; + public float M32; + public float M33; + public float M34; + public float M35; + public float M36; + + public float M41; + public float M42; + public float M43; + public float M44; + public float M45; + public float M46; + + public float M51; + public float M52; + public float M53; + public float M54; + public float M55; + public float M56; + + public float M61; + public float M62; + public float M63; + public float M64; + public float M65; + public float M66; + + public Matrix6x6(float m11, float m12, float m13, float m14, float m15, float m16, + float m21, float m22, float m23, float m24, float m25, float m26, + float m31, float m32, float m33, float m34, float m35, float m36, + float m41, float m42, float m43, float m44, float m45, float m46, + float m51, float m52, float m53, float m54, float m55, float m56, + float m61, float m62, float m63, float m64, float m65, float m66) + { + M11 = m11; + M12 = m12; + M13 = m13; + M14 = m14; + M15 = m15; + M16 = m16; + + M21 = m21; + M22 = m22; + M23 = m23; + M24 = m24; + M25 = m25; + M26 = m26; + + M31 = m31; + M32 = m32; + M33 = m33; + M34 = m34; + M35 = m35; + M36 = m36; + + M41 = m41; + M42 = m42; + M43 = m43; + M44 = m44; + M45 = m45; + M46 = m46; + + M51 = m51; + M52 = m52; + M53 = m53; + M54 = m54; + M55 = m55; + M56 = m56; + + M61 = m61; + M62 = m62; + M63 = m63; + M64 = m64; + M65 = m65; + M66 = m66; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs new file mode 100644 index 000000000..7b4b7ad1d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs @@ -0,0 +1,45 @@ +using Ryujinx.Audio.Renderer.Utils.Math; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + static class MatrixHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 Transform(ref Vector6 value1, ref Matrix6x6 value2) + { + return new Vector6 + { + X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W + value2.M15 * value1.V + value2.M16 * value1.U, + Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W + value2.M25 * value1.V + value2.M26 * value1.U, + Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W + value2.M35 * value1.V + value2.M36 * value1.U, + W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W + value2.M45 * value1.V + value2.M46 * value1.U, + V = value2.M51 * value1.X + value2.M52 * value1.Y + value2.M53 * value1.Z + value2.M54 * value1.W + value2.M55 * value1.V + value2.M56 * value1.U, + U = value2.M61 * value1.X + value2.M62 * value1.Y + value2.M63 * value1.Z + value2.M64 * value1.W + value2.M65 * value1.V + value2.M66 * value1.U, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Transform(ref Vector4 value1, ref Matrix4x4 value2) + { + return new Vector4 + { + X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W, + Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W, + Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W, + W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Transform(ref Vector2 value1, ref Matrix2x2 value2) + { + return new Vector2 + { + X = value2.M11 * value1.X + value2.M12 * value1.Y, + Y = value2.M21 * value1.X + value2.M22 * value1.Y, + }; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs new file mode 100644 index 000000000..303c5e9d0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs @@ -0,0 +1,56 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Utils.Math +{ + record struct Vector6 + { + public float X; + public float Y; + public float Z; + public float W; + public float V; + public float U; + + public Vector6(float value) : this(value, value, value, value, value, value) + { + } + + public Vector6(float x, float y, float z, float w, float v, float u) + { + X = x; + Y = y; + Z = z; + W = w; + V = v; + U = u; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 operator +(Vector6 left, Vector6 right) + { + return new Vector6(left.X + right.X, + left.Y + right.Y, + left.Z + right.Z, + left.W + right.W, + left.V + right.V, + left.U + right.U); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 operator *(Vector6 left, Vector6 right) + { + return new Vector6(left.X * right.X, + left.Y * right.Y, + left.Z * right.Z, + left.W * right.W, + left.V * right.V, + left.U * right.U); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 operator *(Vector6 left, float right) + { + return left * new Vector6(right); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs b/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs new file mode 100644 index 000000000..abbb6ea6c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs @@ -0,0 +1,171 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// Helper for IO operations on and . + /// + public static class SpanIOHelper + { + /// + /// Write the given data to the given backing and move cursor after the written data. + /// + /// The data type. + /// The backing to store the data. + /// The data to write to the backing . + public static void Write(ref Memory backingMemory, ref T data) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + MemoryMarshal.Write(backingMemory.Span[..size], in data); + + backingMemory = backingMemory[size..]; + } + + /// + /// Write the given data to the given backing and move cursor after the written data. + /// + /// The data type. + /// The backing to store the data. + /// The data to write to the backing . + public static void Write(ref Span backingMemory, ref T data) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + MemoryMarshal.Write(backingMemory[..size], in data); + + backingMemory = backingMemory[size..]; + } + + /// + /// Get a out of a and move cursor after T size. + /// + /// The data type. + /// The backing to get a from. + /// A from backing . + public static Span GetWriteRef(ref Memory backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + Span result = MemoryMarshal.Cast(backingMemory.Span[..size]); + + backingMemory = backingMemory[size..]; + + return result; + } + + /// + /// Get a out of a backingMemory and move cursor after T size. + /// + /// The data type. + /// The backing to get a from. + /// A from backing . + public static Span GetWriteRef(ref Span backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + Span result = MemoryMarshal.Cast(backingMemory[..size]); + + backingMemory = backingMemory[size..]; + + return result; + } + + /// + /// Read data from the given backing and move cursor after the read data. + /// + /// The data type. + /// The backing to read data from. + /// Return the read data. + public static T Read(ref ReadOnlyMemory backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + T result = MemoryMarshal.Read(backingMemory.Span[..size]); + + backingMemory = backingMemory[size..]; + + return result; + } + + /// + /// Read data from the given backing and move cursor after the read data. + /// + /// The data type. + /// The backing to read data from. + /// Return the read data. + public static T Read(ref ReadOnlySpan backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + T result = MemoryMarshal.Read(backingMemory[..size]); + + backingMemory = backingMemory[size..]; + + return result; + } + + /// + /// Extract a at the given index. + /// + /// The data type. + /// The to extract the data from. + /// The id in the provided memory. + /// The max allowed count. (for bound checking of the id in debug mode) + /// a at the given id. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory GetMemory(Memory memory, int id, uint count) where T : unmanaged + { + Debug.Assert(id >= 0 && id < count); + + return memory.Slice(id, 1); + } + + /// + /// Extract a ref T at the given index. + /// + /// The data type. + /// The to extract the data from. + /// The id in the provided memory. + /// The max allowed count. (for bound checking of the id in debug mode) + /// a ref T at the given id. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetFromMemory(Memory memory, int id, uint count) where T : unmanaged + { + return ref GetMemory(memory, id, count).Span[0]; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs b/src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs new file mode 100644 index 000000000..b6bafbe0f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs @@ -0,0 +1,43 @@ +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + public sealed unsafe class SpanMemoryManager : MemoryManager + where T : unmanaged + { + private readonly T* _pointer; + private readonly int _length; + + public SpanMemoryManager(Span span) + { + fixed (T* ptr = &MemoryMarshal.GetReference(span)) + { + _pointer = ptr; + _length = span.Length; + } + } + + public override Span GetSpan() => new(_pointer, _length); + + public override MemoryHandle Pin(int elementIndex = 0) + { + if (elementIndex < 0 || elementIndex >= _length) + { + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + + return new MemoryHandle(_pointer + elementIndex); + } + + public override void Unpin() { } + + protected override void Dispose(bool disposing) { } + + public static Memory Cast(Memory memory) where TFrom : unmanaged + { + return new SpanMemoryManager(MemoryMarshal.Cast(memory.Span)).Memory; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs b/src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs new file mode 100644 index 000000000..32d50e12f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs @@ -0,0 +1,59 @@ +using Ryujinx.Audio.Integration; +using System; + +namespace Ryujinx.Audio.Renderer.Utils +{ + public class SplitterHardwareDevice : IHardwareDevice + { + private readonly IHardwareDevice _baseDevice; + private readonly IHardwareDevice _secondaryDevice; + + public SplitterHardwareDevice(IHardwareDevice baseDevice, IHardwareDevice secondaryDevice) + { + _baseDevice = baseDevice; + _secondaryDevice = secondaryDevice; + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + _baseDevice.AppendBuffer(data, channelCount); + _secondaryDevice?.AppendBuffer(data, channelCount); + } + + public void SetVolume(float volume) + { + _baseDevice.SetVolume(volume); + _secondaryDevice.SetVolume(volume); + } + + public float GetVolume() + { + return _baseDevice.GetVolume(); + } + + public uint GetChannelCount() + { + return _baseDevice.GetChannelCount(); + } + + public uint GetSampleRate() + { + return _baseDevice.GetSampleRate(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _baseDevice.Dispose(); + _secondaryDevice?.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/ResultCode.cs b/src/Ryujinx.Audio/ResultCode.cs new file mode 100644 index 000000000..eab27c16d --- /dev/null +++ b/src/Ryujinx.Audio/ResultCode.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Audio +{ + public enum ResultCode + { + ModuleId = 153, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (1 << ErrorCodeShift) | ModuleId, + OperationFailed = (2 << ErrorCodeShift) | ModuleId, + UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId, + WorkBufferTooSmall = (4 << ErrorCodeShift) | ModuleId, + BufferRingFull = (8 << ErrorCodeShift) | ModuleId, + UnsupportedChannelConfiguration = (10 << ErrorCodeShift) | ModuleId, + InvalidUpdateInfo = (41 << ErrorCodeShift) | ModuleId, + InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId, + InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId, + UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId, + InvalidExecutionContextOperation = (514 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.Audio/Ryujinx.Audio.csproj b/src/Ryujinx.Audio/Ryujinx.Audio.csproj new file mode 100644 index 000000000..fc20f4ec4 --- /dev/null +++ b/src/Ryujinx.Audio/Ryujinx.Audio.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + true + + + + + + + + + diff --git a/src/Ryujinx.Ava/App.axaml b/src/Ryujinx.Ava/App.axaml new file mode 100644 index 000000000..eab318b7b --- /dev/null +++ b/src/Ryujinx.Ava/App.axaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx.Ava/App.axaml.cs b/src/Ryujinx.Ava/App.axaml.cs new file mode 100644 index 000000000..c1a3ab3e2 --- /dev/null +++ b/src/Ryujinx.Ava/App.axaml.cs @@ -0,0 +1,136 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Ui.Common.Configuration; +using Ryujinx.Ui.Common.Helper; +using System; +using System.Diagnostics; +using System.IO; + +namespace Ryujinx.Ava +{ + public class App : Application + { + public override void Initialize() + { + Name = $"Ryujinx {Program.Version}"; + + AvaloniaXamlLoader.Load(this); + + if (OperatingSystem.IsMacOS()) + { + Process.Start("/usr/bin/defaults", "write org.ryujinx.Ryujinx ApplePressAndHoldEnabled -bool false"); + } + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + + if (Program.PreviewerDetached) + { + ApplyConfiguredTheme(); + + ConfigurationState.Instance.Ui.BaseStyle.Event += ThemeChanged_Event; + ConfigurationState.Instance.Ui.CustomThemePath.Event += ThemeChanged_Event; + ConfigurationState.Instance.Ui.EnableCustomTheme.Event += CustomThemeChanged_Event; + } + } + + private void CustomThemeChanged_Event(object sender, ReactiveEventArgs e) + { + ApplyConfiguredTheme(); + } + + private void ShowRestartDialog() + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Dispatcher.UIThread.InvokeAsync(async () => + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogThemeRestartMessage], + LocaleManager.Instance[LocaleKeys.DialogThemeRestartSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.DialogRestartRequiredMessage]); + + if (result == UserResult.Yes) + { + var path = Environment.ProcessPath; + var proc = Process.Start(path, CommandLineState.Arguments); + desktop.Shutdown(); + Environment.Exit(0); + } + } + }); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + + private void ThemeChanged_Event(object sender, ReactiveEventArgs e) + { + ApplyConfiguredTheme(); + } + + private void ApplyConfiguredTheme() + { + try + { + string baseStyle = ConfigurationState.Instance.Ui.BaseStyle; + string themePath = ConfigurationState.Instance.Ui.CustomThemePath; + bool enableCustomTheme = ConfigurationState.Instance.Ui.EnableCustomTheme; + + if (string.IsNullOrWhiteSpace(baseStyle)) + { + ConfigurationState.Instance.Ui.BaseStyle.Value = "Dark"; + + baseStyle = ConfigurationState.Instance.Ui.BaseStyle; + } + + RequestedThemeVariant = baseStyle switch + { + "Light" => ThemeVariant.Light, + "Dark" => ThemeVariant.Dark, + _ => ThemeVariant.Default, + }; + + if (enableCustomTheme) + { + if (!string.IsNullOrWhiteSpace(themePath)) + { + try + { + var themeContent = File.ReadAllText(themePath); + var customStyle = AvaloniaRuntimeXamlLoader.Parse(themeContent); + + Styles.Add(customStyle); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to Apply Custom Theme. Error: {ex.Message}"); + } + } + } + } + catch (Exception) + { + Logger.Warning?.Print(LogClass.Application, "Failed to Apply Theme. A restart is needed to apply the selected theme"); + + ShowRestartDialog(); + } + } + } +} diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx.Ava/AppHost.cs new file mode 100644 index 000000000..dac4a3c57 --- /dev/null +++ b/src/Ryujinx.Ava/AppHost.cs @@ -0,0 +1,1196 @@ +using ARMeilleure.Translation; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input; +using Avalonia.Threading; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Audio.Integration; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Renderer; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.Logging; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.Graphics.Vulkan; +using Ryujinx.HLE; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Input; +using Ryujinx.Input.HLE; +using Ryujinx.Ui.App.Common; +using Ryujinx.Ui.Common; +using Ryujinx.Ui.Common.Configuration; +using Ryujinx.Ui.Common.Helper; +using Silk.NET.Vulkan; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SPB.Graphics.Vulkan; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; +using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; +using Image = SixLabors.ImageSharp.Image; +using InputManager = Ryujinx.Input.HLE.InputManager; +using IRenderer = Ryujinx.Graphics.GAL.IRenderer; +using Key = Ryujinx.Input.Key; +using MouseButton = Ryujinx.Input.MouseButton; +using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; +using Size = Avalonia.Size; +using Switch = Ryujinx.HLE.Switch; + +namespace Ryujinx.Ava +{ + internal class AppHost + { + private const int CursorHideIdleTime = 5; // Hide Cursor seconds. + private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. + private const int TargetFps = 60; + private const float VolumeDelta = 0.05f; + + private static readonly Cursor _invisibleCursor = new(StandardCursorType.None); + private readonly IntPtr _invisibleCursorWin; + private readonly IntPtr _defaultCursorWin; + + private readonly long _ticksPerFrame; + private readonly Stopwatch _chrono; + private long _ticks; + + private readonly AccountManager _accountManager; + private readonly UserChannelPersistence _userChannelPersistence; + private readonly InputManager _inputManager; + + private readonly MainWindowViewModel _viewModel; + private readonly IKeyboard _keyboardInterface; + private readonly TopLevel _topLevel; + public RendererHost RendererHost; + + private readonly GraphicsDebugLevel _glLogLevel; + private float _newVolume; + private KeyboardHotkeyState _prevHotkeyState; + + private long _lastCursorMoveTime; + private bool _isCursorInRenderer = true; + + private bool _isStopped; + private bool _isActive; + private bool _renderingStarted; + + private readonly ManualResetEvent _gpuDoneEvent; + + private IRenderer _renderer; + private readonly Thread _renderingThread; + private readonly CancellationTokenSource _gpuCancellationTokenSource; + private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; + + private bool _dialogShown; + private readonly bool _isFirmwareTitle; + + private readonly object _lockObject = new(); + + public event EventHandler AppExit; + public event EventHandler StatusUpdatedEvent; + + public VirtualFileSystem VirtualFileSystem { get; } + public ContentManager ContentManager { get; } + public NpadManager NpadManager { get; } + public TouchScreenManager TouchScreenManager { get; } + public Switch Device { get; set; } + + public int Width { get; private set; } + public int Height { get; private set; } + public string ApplicationPath { get; private set; } + public bool ScreenshotRequested { get; set; } + + public AppHost( + RendererHost renderer, + InputManager inputManager, + string applicationPath, + VirtualFileSystem virtualFileSystem, + ContentManager contentManager, + AccountManager accountManager, + UserChannelPersistence userChannelPersistence, + MainWindowViewModel viewmodel, + TopLevel topLevel) + { + _viewModel = viewmodel; + _inputManager = inputManager; + _accountManager = accountManager; + _userChannelPersistence = userChannelPersistence; + _renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" }; + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; + _topLevel = topLevel; + + _inputManager.SetMouseDriver(new AvaloniaMouseDriver(_topLevel, renderer)); + + _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); + + NpadManager = _inputManager.CreateNpadManager(); + TouchScreenManager = _inputManager.CreateTouchScreenManager(); + ApplicationPath = applicationPath; + VirtualFileSystem = virtualFileSystem; + ContentManager = contentManager; + + RendererHost = renderer; + + _chrono = new Stopwatch(); + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + + if (ApplicationPath.StartsWith("@SystemContent")) + { + ApplicationPath = VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath); + + _isFirmwareTitle = true; + } + + ConfigurationState.Instance.HideCursor.Event += HideCursorState_Changed; + + _topLevel.PointerMoved += TopLevel_PointerEnteredOrMoved; + _topLevel.PointerEntered += TopLevel_PointerEnteredOrMoved; + _topLevel.PointerExited += TopLevel_PointerExited; + + if (OperatingSystem.IsWindows()) + { + _invisibleCursorWin = CreateEmptyCursor(); + _defaultCursorWin = CreateArrowCursor(); + } + + ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAntiAliasing; + ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; + ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough; + + ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState; + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; + ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState; + + _gpuCancellationTokenSource = new CancellationTokenSource(); + _gpuDoneEvent = new ManualResetEvent(false); + } + + private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e) + { + if (sender is MainWindow window) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + + var point = e.GetCurrentPoint(window).Position; + var bounds = RendererHost.EmbeddedWindow.Bounds; + + _isCursorInRenderer = point.X >= bounds.X && + point.X <= bounds.Width + bounds.X && + point.Y >= bounds.Y && + point.Y <= bounds.Height + bounds.Y; + } + } + + private void TopLevel_PointerExited(object sender, PointerEventArgs e) + { + _isCursorInRenderer = false; + } + + private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs e) + { + _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void UpdateScalingFilter(object sender, ReactiveEventArgs e) + { + _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void UpdateColorSpacePassthrough(object sender, ReactiveEventArgs e) + { + _renderer.Window?.SetColorSpacePassthrough((bool)ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value); + } + + private void ShowCursor() + { + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = Cursor.Default; + + if (OperatingSystem.IsWindows()) + { + SetCursor(_defaultCursorWin); + } + }); + } + + private void HideCursor() + { + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = _invisibleCursor; + + if (OperatingSystem.IsWindows()) + { + SetCursor(_invisibleCursorWin); + } + }); + } + + private void SetRendererWindowSize(Size size) + { + if (_renderer != null) + { + double scale = _topLevel.RenderScaling; + + _renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); + } + } + + private void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) + { + if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0) + { + Task.Run(() => + { + lock (_lockObject) + { + DateTime currentTime = DateTime.Now; + string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; + + string directory = AppDataManager.Mode switch + { + AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"), + _ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx"), + }; + + string path = Path.Combine(directory, filename); + + try + { + Directory.CreateDirectory(directory); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {directory}. Error : {ex.GetType().Name}", "Screenshot"); + + return; + } + + Image image = e.IsBgra ? Image.LoadPixelData(e.Data, e.Width, e.Height) + : Image.LoadPixelData(e.Data, e.Width, e.Height); + + if (e.FlipX) + { + image.Mutate(x => x.Flip(FlipMode.Horizontal)); + } + + if (e.FlipY) + { + image.Mutate(x => x.Flip(FlipMode.Vertical)); + } + + image.SaveAsPng(path, new PngEncoder + { + ColorType = PngColorType.Rgb, + }); + + image.Dispose(); + + Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); + } + }); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Screenshot is empty. Size : {e.Data.Length} bytes. Resolution : {e.Width}x{e.Height}", "Screenshot"); + } + } + + public void Start() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); + } + + DisplaySleep.Prevent(); + + NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + TouchScreenManager.Initialize(Device); + + _viewModel.IsGameRunning = true; + + Dispatcher.UIThread.InvokeAsync(() => + { + _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device.Processes.ActiveApplication, Program.Version); + }); + + _viewModel.SetUiProgressHandlers(Device); + + RendererHost.BoundsChanged += Window_BoundsChanged; + + _isActive = true; + + _renderingThread.Start(); + + _viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; + + MainLoop(); + + Exit(); + } + + private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs args) + { + if (Device != null) + { + Device.Configuration.IgnoreMissingServices = args.NewValue; + } + } + + private void UpdateAspectRatioState(object sender, ReactiveEventArgs args) + { + if (Device != null) + { + Device.Configuration.AspectRatio = args.NewValue; + } + } + + private void UpdateAntiAliasing(object sender, ReactiveEventArgs e) + { + _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue); + } + + private void UpdateDockedModeState(object sender, ReactiveEventArgs e) + { + Device?.System.ChangeDockedModeState(e.NewValue); + } + + private void UpdateAudioVolumeState(object sender, ReactiveEventArgs e) + { + Device?.SetVolume(e.NewValue); + + Dispatcher.UIThread.Post(() => + { + _viewModel.Volume = e.NewValue; + }); + } + + private void UpdateEnableInternetAccessState(object sender, ReactiveEventArgs e) + { + Device.Configuration.EnableInternetAccess = e.NewValue; + } + + private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs e) + { + Device.Configuration.MultiplayerLanInterfaceId = e.NewValue; + } + + private void UpdateMultiplayerModeState(object sender, ReactiveEventArgs e) + { + Device.Configuration.MultiplayerMode = e.NewValue; + } + + public void Stop() + { + _isActive = false; + } + + private void Exit() + { + (_keyboardInterface as AvaloniaKeyboard)?.Clear(); + + if (_isStopped) + { + return; + } + + _isStopped = true; + _isActive = false; + } + + public void DisposeContext() + { + Dispose(); + + _isActive = false; + + // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. + // We only need to wait for all commands submitted during the main gpu loop to be processed. + _gpuDoneEvent.WaitOne(); + _gpuDoneEvent.Dispose(); + + DisplaySleep.Restore(); + + NpadManager.Dispose(); + TouchScreenManager.Dispose(); + Device.Dispose(); + + DisposeGpu(); + + AppExit?.Invoke(this, EventArgs.Empty); + } + + private void Dispose() + { + if (Device.Processes != null) + { + MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText); + } + + ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState; + ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel; + ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing; + ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event -= UpdateColorSpacePassthrough; + + _topLevel.PointerMoved -= TopLevel_PointerEnteredOrMoved; + _topLevel.PointerEntered -= TopLevel_PointerEnteredOrMoved; + _topLevel.PointerExited -= TopLevel_PointerExited; + + _gpuCancellationTokenSource.Cancel(); + _gpuCancellationTokenSource.Dispose(); + + _chrono.Stop(); + } + + public void DisposeGpu() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution?.Dispose(); + _windowsMultimediaTimerResolution = null; + } + + if (RendererHost.EmbeddedWindow is EmbeddedWindowOpenGL openGlWindow) + { + // Try to bind the OpenGL context before calling the shutdown event. + openGlWindow.MakeCurrent(false, false); + + Device.DisposeGpu(); + + // Unbind context and destroy everything. + openGlWindow.MakeCurrent(true, false); + } + else + { + Device.DisposeGpu(); + } + } + + private void HideCursorState_Changed(object sender, ReactiveEventArgs state) + { + if (state.NewValue == HideCursorMode.OnIdle) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + } + + public async Task LoadGuestApplication() + { + InitializeSwitchInstance(); + MainWindow.UpdateGraphicsConfig(); + + SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError)) + { + { + if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion)) + { + if (userError == UserError.NoFirmware) + { + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + ""); + + if (result != UserResult.Yes) + { + await UserErrorDialog.ShowUserErrorDialog(userError); + Device.Dispose(); + + return false; + } + } + + if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _)) + { + await UserErrorDialog.ShowUserErrorDialog(userError); + Device.Dispose(); + + return false; + } + + // Tell the user that we installed a firmware for them. + if (userError == UserError.NoFirmware) + { + firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + _viewModel.RefreshFirmwareStatus(); + + await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString), + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString), + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + } + } + else + { + await UserErrorDialog.ShowUserErrorDialog(userError); + Device.Dispose(); + + return false; + } + } + } + } + + Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); + + if (_isFirmwareTitle) + { + Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); + + if (!Device.LoadNca(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + else if (Directory.Exists(ApplicationPath)) + { + string[] romFsFiles = Directory.GetFiles(ApplicationPath, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(ApplicationPath, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + if (!Device.LoadCart(ApplicationPath, romFsFiles[0])) + { + Device.Dispose(); + + return false; + } + } + else + { + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + if (!Device.LoadCart(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + } + else if (File.Exists(ApplicationPath)) + { + switch (Path.GetExtension(ApplicationPath).ToLowerInvariant()) + { + case ".xci": + { + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + if (!Device.LoadXci(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + case ".nca": + { + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + if (!Device.LoadNca(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + case ".nsp": + case ".pfs0": + { + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + if (!Device.LoadNsp(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + default: + { + Logger.Info?.Print(LogClass.Application, "Loading as homebrew."); + + try + { + if (!Device.LoadProgram(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + Device.Dispose(); + + return false; + } + + break; + } + } + } + else + { + Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + Device.Dispose(); + + return false; + } + + DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name); + + ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata => + { + appMetadata.UpdatePreGame(); + }); + + return true; + } + + internal void Resume() + { + Device?.System.TogglePauseEmulation(false); + + _viewModel.IsPaused = false; + _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version); + Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); + } + + internal void Pause() + { + Device?.System.TogglePauseEmulation(true); + + _viewModel.IsPaused = true; + _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, LocaleManager.Instance[LocaleKeys.Paused]); + Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); + } + + private void InitializeSwitchInstance() + { + // Initialize KeySet. + VirtualFileSystem.ReloadKeySet(); + + // Initialize Renderer. + IRenderer renderer; + + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) + { + renderer = new VulkanRenderer( + Vk.GetApi(), + (RendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface, + VulkanHelper.GetRequiredInstanceExtensions, + ConfigurationState.Instance.Graphics.PreferredGpu.Value); + } + else + { + renderer = new OpenGLRenderer(); + } + + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + + var isGALThreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + if (isGALThreaded) + { + renderer = new ThreadedRenderer(renderer); + } + + Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALThreaded}"); + + // Initialize Configuration. + var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB; + + HLEConfiguration configuration = new(VirtualFileSystem, + _viewModel.LibHacHorizonManager, + ContentManager, + _accountManager, + _userChannelPersistence, + renderer, + InitializeAudio(), + memoryConfiguration, + _viewModel.UiHandler, + (SystemLanguage)ConfigurationState.Instance.System.Language.Value, + (RegionCode)ConfigurationState.Instance.System.Region.Value, + ConfigurationState.Instance.Graphics.EnableVsync, + ConfigurationState.Instance.System.EnableDockedMode, + ConfigurationState.Instance.System.EnablePtc, + ConfigurationState.Instance.System.EnableInternetAccess, + ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, + ConfigurationState.Instance.System.FsGlobalAccessLogMode, + ConfigurationState.Instance.System.SystemTimeOffset, + ConfigurationState.Instance.System.TimeZone, + ConfigurationState.Instance.System.MemoryManagerMode, + ConfigurationState.Instance.System.IgnoreMissingServices, + ConfigurationState.Instance.Graphics.AspectRatio, + ConfigurationState.Instance.System.AudioVolume, + ConfigurationState.Instance.System.UseHypervisor, + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, + ConfigurationState.Instance.Multiplayer.Mode); + + Device = new Switch(configuration); + } + + private static IHardwareDeviceDriver InitializeAudio() + { + var availableBackends = new List + { + AudioBackend.SDL2, + AudioBackend.SoundIo, + AudioBackend.OpenAl, + AudioBackend.Dummy, + }; + + AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value; + + for (int i = 0; i < availableBackends.Count; i++) + { + if (availableBackends[i] == preferredBackend) + { + availableBackends.RemoveAt(i); + availableBackends.Insert(0, preferredBackend); + break; + } + } + + static IHardwareDeviceDriver InitializeAudioBackend(AudioBackend backend, AudioBackend nextBackend) where T : IHardwareDeviceDriver, new() + { + if (T.IsSupported) + { + return new T(); + } + + Logger.Warning?.Print(LogClass.Audio, $"{backend} is not supported, falling back to {nextBackend}."); + + return null; + } + + IHardwareDeviceDriver deviceDriver = null; + + for (int i = 0; i < availableBackends.Count; i++) + { + AudioBackend currentBackend = availableBackends[i]; + AudioBackend nextBackend = i + 1 < availableBackends.Count ? availableBackends[i + 1] : AudioBackend.Dummy; + + deviceDriver = currentBackend switch + { + AudioBackend.SDL2 => InitializeAudioBackend(AudioBackend.SDL2, nextBackend), + AudioBackend.SoundIo => InitializeAudioBackend(AudioBackend.SoundIo, nextBackend), + AudioBackend.OpenAl => InitializeAudioBackend(AudioBackend.OpenAl, nextBackend), + _ => new DummyHardwareDeviceDriver(), + }; + + if (deviceDriver != null) + { + ConfigurationState.Instance.System.AudioBackend.Value = currentBackend; + break; + } + } + + MainWindowViewModel.SaveConfig(); + + return deviceDriver; + } + + private void Window_BoundsChanged(object sender, Size e) + { + Width = (int)e.Width; + Height = (int)e.Height; + + SetRendererWindowSize(e); + } + + private void MainLoop() + { + while (_isActive) + { + UpdateFrame(); + + // Polling becomes expensive if it's not slept. + Thread.Sleep(1); + } + } + + private void RenderLoop() + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_viewModel.StartGamesInFullscreen) + { + _viewModel.WindowState = WindowState.FullScreen; + } + + if (_viewModel.WindowState == WindowState.FullScreen) + { + _viewModel.ShowMenuAndStatusBar = false; + } + }); + + _renderer = Device.Gpu.Renderer is ThreadedRenderer tr ? tr.BaseRenderer : Device.Gpu.Renderer; + + _renderer.ScreenCaptured += Renderer_ScreenCaptured; + + (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer); + + Device.Gpu.Renderer.Initialize(_glLogLevel); + + _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value); + _renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + _renderer?.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value); + + Width = (int)RendererHost.Bounds.Width; + Height = (int)RendererHost.Bounds.Height; + + _renderer.Window.SetSize((int)(Width * _topLevel.RenderScaling), (int)(Height * _topLevel.RenderScaling)); + + _chrono.Start(); + + Device.Gpu.Renderer.RunLoop(() => + { + Device.Gpu.SetGpuThread(); + Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + Translator.IsReadyForTranslation.Set(); + + _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); + + while (_isActive) + { + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + if (!_renderingStarted) + { + _renderingStarted = true; + _viewModel.SwitchToRenderer(false); + } + + Device.PresentFrame(() => (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers()); + } + + if (_ticks >= _ticksPerFrame) + { + UpdateStatus(); + } + } + + // Make sure all commands in the run loop are fully executed before leaving the loop. + if (Device.Gpu.Renderer is ThreadedRenderer threaded) + { + threaded.FlushThreadedCommands(); + } + + _gpuDoneEvent.Set(); + }); + + (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true); + } + + public void UpdateStatus() + { + // Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued. + string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld]; + + if (GraphicsConfig.ResScale != 1) + { + dockedMode += $" ({GraphicsConfig.ResScale}x)"; + } + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", + ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL", + dockedMode, + ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), + LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", + $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", + $"GPU: {_renderer.GetHardwareInfo().GpuVendor}")); + } + + public async Task ShowExitPrompt() + { + bool shouldExit = !ConfigurationState.Instance.ShowConfirmExit; + if (!shouldExit) + { + if (_dialogShown) + { + return; + } + + _dialogShown = true; + + shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(); + + _dialogShown = false; + } + + if (shouldExit) + { + Stop(); + } + } + + private bool UpdateFrame() + { + if (!_isActive) + { + return false; + } + + NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + + if (_viewModel.IsActive) + { + if (_isCursorInRenderer) + { + if (ConfigurationState.Instance.Hid.EnableMouse) + { + HideCursor(); + } + else + { + switch (ConfigurationState.Instance.HideCursor.Value) + { + case HideCursorMode.Never: + ShowCursor(); + break; + case HideCursorMode.OnIdle: + if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency) + { + HideCursor(); + } + else + { + ShowCursor(); + } + break; + case HideCursorMode.Always: + HideCursor(); + break; + } + } + } + else + { + ShowCursor(); + } + + Dispatcher.UIThread.Post(() => + { + if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) + { + Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); + } + }); + + KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); + + if (currentHotkeyState != _prevHotkeyState) + { + switch (currentHotkeyState) + { + case KeyboardHotkeyState.ToggleVSync: + Device.EnableDeviceVsync = !Device.EnableDeviceVsync; + + break; + case KeyboardHotkeyState.Screenshot: + ScreenshotRequested = true; + break; + case KeyboardHotkeyState.ShowUi: + _viewModel.ShowMenuAndStatusBar = !_viewModel.ShowMenuAndStatusBar; + break; + case KeyboardHotkeyState.Pause: + if (_viewModel.IsPaused) + { + Resume(); + } + else + { + Pause(); + } + break; + case KeyboardHotkeyState.ToggleMute: + if (Device.IsAudioMuted()) + { + Device.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + Device.SetVolume(0); + } + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.ResScaleUp: + GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1; + break; + case KeyboardHotkeyState.ResScaleDown: + GraphicsConfig.ResScale = + (MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1; + break; + case KeyboardHotkeyState.VolumeUp: + _newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2); + Device.SetVolume(_newVolume); + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.VolumeDown: + _newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2); + Device.SetVolume(_newVolume); + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.None: + (_keyboardInterface as AvaloniaKeyboard).Clear(); + break; + } + } + + _prevHotkeyState = currentHotkeyState; + + if (ScreenshotRequested) + { + ScreenshotRequested = false; + _renderer.Screenshot(); + } + } + + // Touchscreen. + bool hasTouch = false; + + if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse) + { + hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + } + + if (!hasTouch) + { + Device.Hid.Touchscreen.Update(); + } + + Device.Hid.DebugPad.Update(); + + return true; + } + + private KeyboardHotkeyState GetHotkeyState() + { + KeyboardHotkeyState state = KeyboardHotkeyState.None; + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) + { + state = KeyboardHotkeyState.ToggleVSync; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) + { + state = KeyboardHotkeyState.Screenshot; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi)) + { + state = KeyboardHotkeyState.ShowUi; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) + { + state = KeyboardHotkeyState.Pause; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) + { + state = KeyboardHotkeyState.ToggleMute; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp)) + { + state = KeyboardHotkeyState.ResScaleUp; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown)) + { + state = KeyboardHotkeyState.ResScaleDown; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeUp)) + { + state = KeyboardHotkeyState.VolumeUp; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeDown)) + { + state = KeyboardHotkeyState.VolumeDown; + } + + return state; + } + } +} diff --git a/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf b/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf new file mode 100644 index 000000000..8f05a4bbc Binary files /dev/null and b/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf differ diff --git a/src/Ryujinx.Ava/Assets/Locales/de_DE.json b/src/Ryujinx.Ava/Assets/Locales/de_DE.json new file mode 100644 index 000000000..7cdcdf5a2 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/de_DE.json @@ -0,0 +1,656 @@ +{ + "Language": "Deutsch", + "MenuBarFileOpenApplet": "Öffne Anwendung", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Öffnet das Mii-Editor-Applet im Standalone-Modus", + "SettingsTabInputDirectMouseAccess": "Direkter Mauszugriff", + "SettingsTabSystemMemoryManagerMode": "Speichermanagermodus:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (schnell)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host ungeprüft (am schnellsten, unsicher)", + "SettingsTabSystemUseHypervisor": "Hypervisor verwenden", + "MenuBarFile": "_Datei", + "MenuBarFileOpenFromFile": "_Datei öffnen", + "MenuBarFileOpenUnpacked": "_Entpacktes Spiel öffnen", + "MenuBarFileOpenEmuFolder": "Ryujinx-Ordner öffnen", + "MenuBarFileOpenLogsFolder": "Logs-Ordner öffnen", + "MenuBarFileExit": "_Beenden", + "MenuBarOptions": "Optionen", + "MenuBarOptionsToggleFullscreen": "Vollbild", + "MenuBarOptionsStartGamesInFullscreen": "Spiele im Vollbildmodus starten", + "MenuBarOptionsStopEmulation": "Emulation beenden", + "MenuBarOptionsSettings": "_Einstellungen", + "MenuBarOptionsManageUserProfiles": "_Benutzerprofile verwalten", + "MenuBarActions": "_Aktionen", + "MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht simulieren", + "MenuBarActionsScanAmiibo": "Amiibo scannen", + "MenuBarTools": "_Werkzeuge", + "MenuBarToolsInstallFirmware": "Firmware installieren", + "MenuBarFileToolsInstallFirmwareFromFile": "Firmware von einer XCI- oder einer ZIP-Datei installieren", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Firmware aus einem Verzeichnis installieren", + "MenuBarToolsManageFileTypes": "Dateitypen verwalten", + "MenuBarToolsInstallFileTypes": "Dateitypen installieren", + "MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren", + "MenuBarHelp": "Hilfe", + "MenuBarHelpCheckForUpdates": "Nach Updates suchen", + "MenuBarHelpAbout": "Über Ryujinx", + "MenuSearch": "Suchen...", + "GameListHeaderFavorite": "Favorit", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Entwickler", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Spielzeit", + "GameListHeaderLastPlayed": "Zuletzt gespielt", + "GameListHeaderFileExtension": "Dateiformat", + "GameListHeaderFileSize": "Dateigröße", + "GameListHeaderPath": "Pfad", + "GameListContextMenuOpenUserSaveDirectory": "Spielstand-Verzeichnis öffnen", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Benutzer-Spielstand beinhaltet", + "GameListContextMenuOpenDeviceSaveDirectory": "Benutzer-Geräte-Verzeichnis öffnen", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Geräte-Spielstände beinhaltet", + "GameListContextMenuOpenBcatSaveDirectory": "Benutzer-BCAT-Vezeichnis öffnen", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den BCAT-Cache des Spiels beinhaltet", + "GameListContextMenuManageTitleUpdates": "Verwalte Spiel-Updates", + "GameListContextMenuManageTitleUpdatesToolTip": "Öffnet den Spiel-Update-Manager", + "GameListContextMenuManageDlc": "Verwalten von DLC", + "GameListContextMenuManageDlcToolTip": "Öffnet den DLC-Manager", + "GameListContextMenuOpenModsDirectory": "Mod-Verzeichnis öffnen", + "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", + "GameListContextMenuCacheManagement": "Cache Verwaltung", + "GameListContextMenuCacheManagementPurgePptc": "PPTC als ungültig markieren", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Markiert den PPTC als ungültig, sodass dieser beim nächsten Spielstart neu erstellt wird", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Cache löschen", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Löscht den Shader-Cache der Anwendung", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC-Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Öffnet das Verzeichnis, das den PPTC-Cache der Anwendung beinhaltet", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader-Cache-Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Öffnet das Verzeichnis, das den Shader Cache der Anwendung beinhaltet", + "GameListContextMenuExtractData": "Daten extrahieren", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrahiert das ExeFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrahiert das RomFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrahiert das Logo aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", + "StatusBarSystemVersion": "Systemversion: {0}", + "LinuxVmMaxMapCountDialogTitle": "Niedriges Limit für Speicherzuordnungen erkannt", + "LinuxVmMaxMapCountDialogTextPrimary": "Möchtest Du den Wert von vm.max_map_count auf {0} erhöhen", + "LinuxVmMaxMapCountDialogTextSecondary": "Einige Spiele könnten versuchen, mehr Speicherzuordnungen zu erstellen, als derzeit erlaubt. Ryujinx wird abstürzen, sobald dieses Limit überschritten wird.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Ja, bis zum nächsten Neustart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Ja, permanent", + "LinuxVmMaxMapCountWarningTextPrimary": "Maximale Anzahl an Speicherzuordnungen ist niedriger als empfohlen.", + "LinuxVmMaxMapCountWarningTextSecondary": "Der aktuelle Wert von vm.max_map_count ({0}) ist kleiner als {1}. Einige Spiele könnten versuchen, mehr Speicherzuordnungen zu erstellen, als derzeit erlaubt. Ryujinx wird abstürzen, sobald dieses Limit überschritten wird.\n\nDu kannst das Limit entweder manuell erhöhen oder pkexec installieren, damit Ryujinx Dir dabei hilft.", + "Settings": "Einstellungen", + "SettingsTabGeneral": "Oberfläche", + "SettingsTabGeneralGeneral": "Allgemein", + "SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere die Statusanzeige für Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen", + "SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\"-Dialog", + "SettingsTabGeneralHideCursor": "Mauszeiger ausblenden", + "SettingsTabGeneralHideCursorNever": "Niemals", + "SettingsTabGeneralHideCursorOnIdle": "Mauszeiger bei Inaktivität ausblenden", + "SettingsTabGeneralHideCursorAlways": "Immer", + "SettingsTabGeneralGameDirectories": "Spielverzeichnisse", + "SettingsTabGeneralAdd": "Hinzufügen", + "SettingsTabGeneralRemove": "Entfernen", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Kern", + "SettingsTabSystemSystemRegion": "Systemregion:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australien", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Systemsprache:", + "SettingsTabSystemSystemLanguageJapanese": "Japanisch", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikanisches Englisch", + "SettingsTabSystemSystemLanguageFrench": "Französisch", + "SettingsTabSystemSystemLanguageGerman": "Deutsch", + "SettingsTabSystemSystemLanguageItalian": "Italienisch", + "SettingsTabSystemSystemLanguageSpanish": "Spanisch", + "SettingsTabSystemSystemLanguageChinese": "Chinesisch", + "SettingsTabSystemSystemLanguageKorean": "Koreanisch", + "SettingsTabSystemSystemLanguageDutch": "Niederländisch", + "SettingsTabSystemSystemLanguagePortuguese": "Portugiesisch", + "SettingsTabSystemSystemLanguageRussian": "Russisch", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanesisch", + "SettingsTabSystemSystemLanguageBritishEnglish": "Britisches Englisch", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadisches Französisch", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Lateinamerikanisches Spanisch", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Vereinfachtes Chinesisch", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditionelles Chinesisch", + "SettingsTabSystemSystemTimeZone": "System-Zeitzone:", + "SettingsTabSystemSystemTime": "Systemzeit:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integritätsprüfung", + "SettingsTabSystemAudioBackend": "Audio-Backend:", + "SettingsTabSystemAudioBackendDummy": "Ohne Funktion", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Kann Fehler verursachen)", + "SettingsTabSystemExpandDramSize": "Erweitere DRAM Größe auf 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignoriere fehlende Dienste", + "SettingsTabGraphics": "Grafik", + "SettingsTabGraphicsAPI": "Grafik-API", + "SettingsTabGraphicsEnableShaderCache": "Shader-Cache aktivieren", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotrope Filterung:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Auflösungsskalierung:", + "SettingsTabGraphicsResolutionScaleCustom": "Benutzerdefiniert (nicht empfohlen)", + "SettingsTabGraphicsResolutionScaleNative": "Nativ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Bildseitenverhältnis:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "An Fenster anpassen", + "SettingsTabGraphicsDeveloperOptions": "Optionen für Entwickler", + "SettingsTabGraphicsShaderDumpPath": "Grafik-Shader-Dump-Pfad:", + "SettingsTabLogging": "Logs", + "SettingsTabLoggingLogging": "Logs", + "SettingsTabLoggingEnableLoggingToFile": "Protokollierung in Datei aktivieren", + "SettingsTabLoggingEnableStubLogs": "Aktiviere Stub-Logs", + "SettingsTabLoggingEnableInfoLogs": "Aktiviere Info-Logs", + "SettingsTabLoggingEnableWarningLogs": "Aktiviere Warn-Logs", + "SettingsTabLoggingEnableErrorLogs": "Aktiviere Fehler-Logs", + "SettingsTabLoggingEnableTraceLogs": "Aktiviere Trace-Logs", + "SettingsTabLoggingEnableGuestLogs": "Aktiviere Gast-Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Aktiviere Fs Zugriff-Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Globaler Zugriff-Log-Modus:", + "SettingsTabLoggingDeveloperOptions": "Entwickleroptionen", + "SettingsTabLoggingDeveloperOptionsNote": "ACHTUNG: Wird die Leistung reduzieren", + "SettingsTabLoggingGraphicsBackendLogLevel": "Protokollstufe des Grafik-Backends:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Keine", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Fehler", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Verlangsamungen", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Alle", + "SettingsTabLoggingEnableDebugLogs": "Aktiviere Debug-Log", + "SettingsTabInput": "Eingabe", + "SettingsTabInputEnableDockedMode": "Angedockter Modus", + "SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff", + "SettingsButtonSave": "Speichern", + "SettingsButtonClose": "Schließen", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Abbrechen", + "SettingsButtonApply": "Übernehmen", + "ControllerSettingsPlayer": "Spieler", + "ControllerSettingsPlayer1": "Spieler 1", + "ControllerSettingsPlayer2": "Spieler 2", + "ControllerSettingsPlayer3": "Spieler 3", + "ControllerSettingsPlayer4": "Spieler 4", + "ControllerSettingsPlayer5": "Spieler 5", + "ControllerSettingsPlayer6": "Spieler 6", + "ControllerSettingsPlayer7": "Spieler 7", + "ControllerSettingsPlayer8": "Spieler 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Eingabegerät", + "ControllerSettingsRefresh": "Aktualisieren", + "ControllerSettingsDeviceDisabled": "Deaktiviert", + "ControllerSettingsControllerType": "Controller-Typ", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Joy-Con-Paar", + "ControllerSettingsControllerTypeJoyConLeft": "Linker Joy-Con", + "ControllerSettingsControllerTypeJoyConRight": "Rechter Joy-Con", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Standard", + "ControllerSettingsLoad": "Laden", + "ControllerSettingsAdd": "Hinzufügen", + "ControllerSettingsRemove": "Entfernen", + "ControllerSettingsButtons": "Aktionstasten", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Steuerkreuz", + "ControllerSettingsDPadUp": "Hoch", + "ControllerSettingsDPadDown": "Runter", + "ControllerSettingsDPadLeft": "Links", + "ControllerSettingsDPadRight": "Rechts", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Hoch", + "ControllerSettingsStickDown": "Runter", + "ControllerSettingsStickLeft": "Links", + "ControllerSettingsStickRight": "Rechts", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "X-Achse invertieren", + "ControllerSettingsStickInvertYAxis": "Y-Achse invertieren", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Linker Analogstick", + "ControllerSettingsRStick": "Rechter Analogstick", + "ControllerSettingsTriggersLeft": "Linker Trigger", + "ControllerSettingsTriggersRight": "Rechter Trigger", + "ControllerSettingsTriggersButtonsLeft": "Linke Schultertaste", + "ControllerSettingsTriggersButtonsRight": "Rechte Schultertaste", + "ControllerSettingsTriggers": "Trigger", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Linke Aktionstasten", + "ControllerSettingsExtraButtonsRight": "Rechte Aktionstasten", + "ControllerSettingsMisc": "Verschiedenes", + "ControllerSettingsTriggerThreshold": "Empfindlichkeit:", + "ControllerSettingsMotion": "Bewegung", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook kompatible Bewegungssteuerung", + "ControllerSettingsMotionControllerSlot": "Controller-Slot:", + "ControllerSettingsMotionMirrorInput": "Eingabe spiegeln", + "ControllerSettingsMotionRightJoyConSlot": "Rechter Joy-Con-Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro-Empfindlichkeit:", + "ControllerSettingsMotionGyroDeadzone": "Gyro-Deadzone:", + "ControllerSettingsSave": "Speichern", + "ControllerSettingsClose": "Schließen", + "UserProfilesSelectedUserProfile": "Ausgewähltes Profil:", + "UserProfilesSaveProfileName": "Profilname speichern", + "UserProfilesChangeProfileImage": "Profilbild ändern", + "UserProfilesAvailableUserProfiles": "Verfügbare Profile:", + "UserProfilesAddNewProfile": "Neues Profil", + "UserProfilesDelete": "Löschen", + "UserProfilesClose": "Schließen", + "ProfileNameSelectionWatermark": "Wähle einen Spitznamen", + "ProfileImageSelectionTitle": "Auswahl des Profilbildes", + "ProfileImageSelectionHeader": "Wähle ein Profilbild aus", + "ProfileImageSelectionNote": "Es kann ein eigenes Profilbild importiert werden oder ein Avatar aus der System-Firmware", + "ProfileImageSelectionImportImage": "Bilddatei importieren", + "ProfileImageSelectionSelectAvatar": "Firmware-Avatar auswählen", + "InputDialogTitle": "Eingabe-Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Abbrechen", + "InputDialogAddNewProfileTitle": "Wähle den Profilnamen", + "InputDialogAddNewProfileHeader": "Bitte gebe einen Profilnamen ein", + "InputDialogAddNewProfileSubtext": "(Maximale Länge: {0})", + "AvatarChoose": "Bestätigen", + "AvatarSetBackgroundColor": "Hintergrundfarbe auswählen", + "AvatarClose": "Schließen", + "ControllerSettingsLoadProfileToolTip": "Lädt ein Profil", + "ControllerSettingsAddProfileToolTip": "Fügt ein Profil hinzu", + "ControllerSettingsRemoveProfileToolTip": "Entfernt ein Profil", + "ControllerSettingsSaveProfileToolTip": "Speichert ein Profil", + "MenuBarFileToolsTakeScreenshot": "Screenshot aufnehmen", + "MenuBarFileToolsHideUi": "Oberfläche ausblenden", + "GameListContextMenuRunApplication": "Anwendung ausführen", + "GameListContextMenuToggleFavorite": "Als Favoriten hinzufügen/entfernen", + "GameListContextMenuToggleFavoriteToolTip": "Aktiviert den Favoriten-Status des Spiels", + "SettingsTabGeneralTheme": "Design", + "SettingsTabGeneralThemeCustomTheme": "Pfad für das benutzerdefinierte Design", + "SettingsTabGeneralThemeBaseStyle": "Farbschema", + "SettingsTabGeneralThemeBaseStyleDark": "Dunkel", + "SettingsTabGeneralThemeBaseStyleLight": "Hell", + "SettingsTabGeneralThemeEnableCustomTheme": "Design für die Emulator-Benutzeroberfläche", + "ButtonBrowse": "Durchsuchen", + "ControllerSettingsConfigureGeneral": "Konfigurieren", + "ControllerSettingsRumble": "Vibration", + "ControllerSettingsRumbleStrongMultiplier": "Starker Vibrations-Multiplikator", + "ControllerSettingsRumbleWeakMultiplier": "Schwacher Vibrations-Multiplikator", + "DialogMessageSaveNotAvailableMessage": "Es existieren keine Speicherdaten für {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Sollen Speicherdaten für dieses Spiel erstellt werden?", + "DialogConfirmationTitle": "Ryujinx - Bestätigung", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Fehler", + "DialogWarningTitle": "Ryujinx - Warnung", + "DialogExitTitle": "Ryujinx - Beenden", + "DialogErrorMessage": "Ein Fehler ist aufgetreten", + "DialogExitMessage": "Ryujinx wirklich schließen?", + "DialogExitSubMessage": "Alle nicht gespeicherten Daten gehen verloren!", + "DialogMessageCreateSaveErrorMessage": "Es ist ein Fehler bei der Erstellung der angegebenen Speicherdaten aufgetreten: {0}", + "DialogMessageFindSaveErrorMessage": "Es ist ein Fehler beim Suchen der angegebenen Speicherdaten aufgetreten: {0}", + "FolderDialogExtractTitle": "Wähle den Ordner, in welchen die Dateien entpackt werden sollen", + "DialogNcaExtractionMessage": "Extrahiert {0} abschnitt von {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA-Abschnitt-Extraktor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraktion fehlgeschlagen. Der Hauptheader der NCA war in der ausgewählten Datei nicht vorhanden.", + "DialogNcaExtractionCheckLogErrorMessage": "Extraktion fehlgeschlagen. Überprüfe die Logs für weitere Informationen.", + "DialogNcaExtractionSuccessMessage": "Extraktion erfolgreich abgeschlossen.", + "DialogUpdaterConvertFailedMessage": "Die Konvertierung der aktuellen Ryujinx-Version ist fehlgeschlagen.", + "DialogUpdaterCancelUpdateMessage": "Download wird abgebrochen!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Es wird bereits die aktuellste Version von Ryujinx benutzt", + "DialogUpdaterFailedToGetVersionMessage": "Beim Versuch, Veröffentlichungs-Info von GitHub Release zu erhalten, ist ein Fehler aufgetreten. Dies kann aufgrund einer neuen Veröffentlichung, die gerade von GitHub Actions kompiliert wird, verursacht werden.", + "DialogUpdaterConvertFailedGithubMessage": "Fehler beim Konvertieren der erhaltenen Ryujinx-Version von GitHub Release.", + "DialogUpdaterDownloadingMessage": "Update wird heruntergeladen...", + "DialogUpdaterExtractionMessage": "Update wird entpackt...", + "DialogUpdaterRenamingMessage": "Update wird umbenannt...", + "DialogUpdaterAddingFilesMessage": "Update wird hinzugefügt...", + "DialogUpdaterCompleteMessage": "Update abgeschlossen!", + "DialogUpdaterRestartMessage": "Ryujinx jetzt neu starten?", + "DialogUpdaterArchNotSupportedMessage": "Eine nicht unterstützte Systemarchitektur wird benutzt!", + "DialogUpdaterArchNotSupportedSubMessage": "Nur 64-Bit-Systeme werden unterstützt!", + "DialogUpdaterNoInternetMessage": "Es besteht keine Verbindung mit dem Internet!", + "DialogUpdaterNoInternetSubMessage": "Bitte vergewissern, dass eine funktionierende Internetverbindung existiert!", + "DialogUpdaterDirtyBuildMessage": "Inoffizielle Versionen von Ryujinx können nicht aktualisiert werden", + "DialogUpdaterDirtyBuildSubMessage": "Lade Ryujinx bitte von hier herunter, um eine unterstützte Version zu erhalten: https://ryujinx.org/", + "DialogRestartRequiredMessage": "Neustart erforderlich", + "DialogThemeRestartMessage": "Das Design wurde gespeichert. Ein Neustart ist erforderlich, um das Design anzuwenden.", + "DialogThemeRestartSubMessage": "Jetzt neu starten?", + "DialogFirmwareInstallEmbeddedMessage": "Die in diesem Spiel enthaltene Firmware installieren? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Es wurde keine installierte Firmware gefunden, aber Ryujinx konnte die Firmware {0} aus dem bereitgestellten Spiel installieren.\nRyujinx wird nun gestartet.", + "DialogFirmwareNoFirmwareInstalledMessage": "Keine Firmware installiert", + "DialogFirmwareInstalledMessage": "Firmware {0} wurde installiert", + "DialogInstallFileTypesSuccessMessage": "Dateitypen erfolgreich installiert!", + "DialogInstallFileTypesErrorMessage": "Dateitypen konnten nicht installiert werden.", + "DialogUninstallFileTypesSuccessMessage": "Dateitypen erfolgreich deinstalliert!", + "DialogUninstallFileTypesErrorMessage": "Deinstallation der Dateitypen fehlgeschlagen.", + "DialogOpenSettingsWindowLabel": "Fenster-Einstellungen öffnen", + "DialogControllerAppletTitle": "Controller-Applet", + "DialogMessageDialogErrorExceptionMessage": "Fehler bei der Anzeige des Meldungs-Dialogs: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Fehler bei der Anzeige der Software-Tastatur: {0}", + "DialogErrorAppletErrorExceptionMessage": "Fehler beim Anzeigen des ErrorApplet-Dialogs: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nWeitere Informationen zur Behebung dieses Fehlers können in unserem Setup-Guide gefunden werden.", + "DialogUserErrorDialogTitle": "Ryujinx Fehler ({0})", + "DialogAmiiboApiTitle": "Amiibo-API", + "DialogAmiiboApiFailFetchMessage": "Beim Abrufen von Informationen aus der API ist ein Fehler aufgetreten.", + "DialogAmiiboApiConnectErrorMessage": "Verbindung zum Amiibo API Server kann nicht hergestellt werden. Der Dienst ist möglicherweise nicht verfügbar oder es existiert keine Internetverbindung.", + "DialogProfileInvalidProfileErrorMessage": "Das Profil {0} ist mit dem aktuellen Eingabekonfigurationssystem nicht kompatibel.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Das Standardprofil kann nicht überschrieben werden", + "DialogProfileDeleteProfileTitle": "Profil löschen", + "DialogProfileDeleteProfileMessage": "Diese Aktion kann nicht rückgängig gemacht werden. Wirklich fortfahren?", + "DialogWarning": "Warnung", + "DialogPPTCDeletionMessage": "Du bist dabei den PPTC für das folgende Spiel als ungültig zu markieren:\n\n{0}\n\nWirklich fortfahren?", + "DialogPPTCDeletionErrorMessage": "Fehler bei der Löschung des PPTC Caches bei {0}: {1}", + "DialogShaderDeletionMessage": "Du bist dabei, den Shader Cache zu löschen für :\n\n{0}\n\nWirklich fortfahren?", + "DialogShaderDeletionErrorMessage": "Es ist ein Fehler bei der Löschung des Shader Caches bei {0}: {1} aufgetreten", + "DialogRyujinxErrorMessage": "Ein Fehler ist aufgetreten", + "DialogInvalidTitleIdErrorMessage": "UI Fehler: Das ausgewählte Spiel hat keine gültige Titel-ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Es wurde keine gültige System-Firmware gefunden in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installiere Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Systemversion {0} wird jetzt installiert.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nDies wird die aktuelle Systemversion {0} ersetzen.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nMöchtest du fortfahren?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware wird installiert...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Systemversion {0} wurde erfolgreich installiert.", + "DialogUserProfileDeletionWarningMessage": "Es können keine anderen Profile geöffnet werden, wenn das ausgewählte Profil gelöscht wird.", + "DialogUserProfileDeletionConfirmMessage": "Möchtest du das ausgewählte Profil löschen?", + "DialogUserProfileUnsavedChangesTitle": "Warnung - Nicht gespeicherte Änderungen", + "DialogUserProfileUnsavedChangesMessage": "Sie haben Änderungen an diesem Nutzerprofil vorgenommen, die nicht gespeichert wurden.", + "DialogUserProfileUnsavedChangesSubMessage": "Möchten Sie Ihre Änderungen wirklich verwerfen?", + "DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?", + "DialogLoadNcaErrorMessage": "{0}. Fehlerhafte Datei: {1}", + "DialogDlcNoDlcErrorMessage": "Die angegebene Datei enthält keinen DLC für den ausgewählten Titel!", + "DialogPerformanceCheckLoggingEnabledMessage": "Es wurde die Debug Protokollierung aktiviert", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Um eine optimale Leistung zu erzielen, wird empfohlen, die Debug Protokollierung zu deaktivieren. Debug Protokollierung jetzt deaktivieren?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Es wurde das Shader Dumping aktiviert, das nur von Entwicklern verwendet werden soll.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Für eine optimale Leistung wird empfohlen, das Shader Dumping zu deaktivieren. Shader Dumping jetzt deaktivieren?", + "DialogLoadAppGameAlreadyLoadedMessage": "Es wurde bereits ein Spiel gestartet", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Bitte beende die Emulation oder schließe den Emulator, vor dem Starten eines neuen Spiels", + "DialogUpdateAddUpdateErrorMessage": "Die angegebene Datei enthält keine Updates für den ausgewählten Titel!", + "DialogSettingsBackendThreadingWarningTitle": "Warnung - Render Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx muss muss neu gestartet werden, damit die Änderungen wirksam werden. Abhängig von dem Betriebssystem muss möglicherweise das Multithreading des Treibers manuell deaktiviert werden, wenn Ryujinx verwendet wird.", + "SettingsTabGraphicsFeaturesOptions": "Erweiterungen", + "SettingsTabGraphicsBackendMultithreading": "Grafik-Backend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Aus", + "CommonOn": "An", + "InputDialogYes": "Ja", + "InputDialogNo": "Nein", + "DialogProfileInvalidProfileNameErrorMessage": "Der Dateiname enthält ungültige Zeichen. Bitte erneut versuchen.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Fortsetzen", + "AboutUrlTooltipMessage": "Klicke hier, um die Ryujinx Website im Standardbrowser zu öffnen.", + "AboutDisclaimerMessage": "Ryujinx ist in keinster Weise weder mit Nintendo™, \nnoch mit deren Partnern verbunden.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) wird in unserer Amiibo \nEmulation benutzt.", + "AboutPatreonUrlTooltipMessage": "Klicke hier, um die Ryujinx Patreon Seite im Standardbrowser zu öffnen.", + "AboutGithubUrlTooltipMessage": "Klicke hier, um die Ryujinx GitHub Seite im Standardbrowser zu öffnen.", + "AboutDiscordUrlTooltipMessage": "Klicke hier, um eine Einladung zum Ryujinx Discord Server im Standardbrowser zu öffnen.", + "AboutTwitterUrlTooltipMessage": "Klicke hier, um die Ryujinx Twitter Seite im Standardbrowser zu öffnen.", + "AboutRyujinxAboutTitle": "Über:", + "AboutRyujinxAboutContent": "Ryujinx ist ein Nintendo Switch™ Emulator.\nBitte unterstütze uns auf Patreon.\nAuf Twitter oder Discord erfährst du alle Neuigkeiten.\nEntwickler, die an einer Mitarbeit interessiert sind, können auf GitHub oder Discord mehr erfahren.", + "AboutRyujinxMaintainersTitle": "Entwickelt von:", + "AboutRyujinxMaintainersContentTooltipMessage": "Klicke hier, um die Liste der Mitwirkenden im Standardbrowser zu öffnen.", + "AboutRyujinxSupprtersTitle": "Unterstützt auf Patreon von:", + "AmiiboSeriesLabel": "Amiibo Serie", + "AmiiboCharacterLabel": "Charakter", + "AmiiboScanButtonLabel": "Einscannen", + "AmiiboOptionsShowAllLabel": "Zeige alle Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Benutze zufällige Tag-UUID", + "DlcManagerTableHeadingEnabledLabel": "Aktiviert", + "DlcManagerTableHeadingTitleIdLabel": "Title-ID", + "DlcManagerTableHeadingContainerPathLabel": "Container-Pfad", + "DlcManagerTableHeadingFullPathLabel": "Vollständiger-Pfad", + "DlcManagerRemoveAllButton": "Entferne alle", + "DlcManagerEnableAllButton": "Alle aktivieren", + "DlcManagerDisableAllButton": "Alle deaktivieren", + "MenuBarOptionsChangeLanguage": "Sprache ändern", + "MenuBarShowFileTypes": "Dateitypen anzeigen", + "CommonSort": "Sortieren", + "CommonShowNames": "Spiel-Namen anzeigen", + "CommonFavorite": "Favoriten", + "OrderAscending": "Aufsteigend", + "OrderDescending": "Absteigend", + "SettingsTabGraphicsFeatures": "Erweiterungen", + "ErrorWindowTitle": "Fehler-Fenster", + "ToggleDiscordTooltip": "Zeige momentanes Spiel auf Discord", + "AddGameDirBoxTooltip": "Gibt das Spielverzeichnis an, das der Liste hinzuzufügt wird", + "AddGameDirTooltip": "Fügt ein neues Spielverzeichnis hinzu", + "RemoveGameDirTooltip": "Entfernt das ausgewähltes Spielverzeichnis", + "CustomThemeCheckTooltip": "Verwende ein eigenes Design für die Emulator-Benutzeroberfläche", + "CustomThemePathTooltip": "Gibt den Pfad zum Design für die Emulator-Benutzeroberfläche an", + "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach einem benutzerdefinierten Design für die Emulator-Benutzeroberfläche", + "DockModeToggleTooltip": "Im gedockten Modus verhält sich das emulierte System wie eine Nintendo Switch im TV Modus. Dies verbessert die grafische Qualität der meisten Spiele. Umgekehrt führt die Deaktivierung dazu, dass sich das emulierte System wie eine Nintendo Switch im Handheld Modus verhält, was die Grafikqualität beeinträchtigt.\n\nKonfiguriere das Eingabegerät für Spieler 1, um im Docked Modus zu spielen; konfiguriere das Controllerprofil via der Handheld Option, wenn geplant wird den Handheld Modus zu nutzen.\n\nIm Zweifelsfall AN lassen.", + "DirectKeyboardTooltip": "Aktiviert/Deaktiviert den \"Direkter Tastaturzugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Tastaur als Eingabegerät in Spielen)", + "DirectMouseTooltip": "Aktiviert/Deaktiviert den \"Direkten Mauszugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Maus als Eingabegerät in Spielen)", + "RegionTooltip": "Ändert die Systemregion", + "LanguageTooltip": "Ändert die Systemsprache", + "TimezoneTooltip": "Ändert die Systemzeitzone", + "TimeTooltip": "Ändert die Systemzeit", + "VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen, Ladebildschirme länger benötigen oder hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden.\n\nIm Zweifelsfall AN lassen.", + "PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.", + "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", + "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", + "MemoryManagerTooltip": "Ändert wie der Gastspeicher abgebildet wird und wie auf ihn zugegriffen wird. Beinflusst die Leistung der emulierten CPU erheblich.\n\nIm Zweifelsfall Host ungeprüft auswählen.", + "MemoryManagerSoftwareTooltip": "Verwendung einer Software-Seitentabelle für die Adressumsetzung. Höchste Genauigkeit, aber langsamste Leistung.", + "MemoryManagerHostTooltip": "Direkte Zuordnung von Speicher im Host-Adressraum. Viel schnellere JIT-Kompilierung und Ausführung.", + "MemoryManagerUnsafeTooltip": "Direkte Zuordnung des Speichers, aber keine Maskierung der Adresse innerhalb des Gastadressraums vor dem Zugriff. Schneller, aber auf Kosten der Sicherheit. Die Gastanwendung kann von überall in Ryujinx auf den Speicher zugreifen, daher sollte in diesem Modus nur Programme ausgeführt werden denen vertraut wird.", + "UseHypervisorTooltip": "Verwende Hypervisor anstelle von JIT. Verbessert die Leistung stark, falls vorhanden, kann jedoch in seinem aktuellen Zustand instabil sein.", + "DRamTooltip": "Erhöht den Arbeitsspeicher des emulierten Systems von 4 GiB auf 6 GiB.\n\nDies ist nur für Texturenpakete mit höherer Auflösung oder Mods mit 4K-Auflösung nützlich. Diese Option verbessert NICHT die Leistung.\n\nIm Zweifelsfall AUS lassen.", + "IgnoreMissingServicesTooltip": "Durch diese Option werden nicht implementierte Dienste der Switch-Firmware ignoriert. Dies kann dabei helfen, Abstürze beim Starten bestimmter Spiele zu umgehen.\n\nIm Zweifelsfall AUS lassen.", + "GraphicsBackendThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf AUTO stellen.", + "GalThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies Beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf auf AUTO stellen.", + "ShaderCacheToggleTooltip": "Speichert einen persistenten Shader Cache, der das Stottern bei nachfolgenden Durchläufen reduziert.\n\nIm Zweifelsfall AN lassen.", + "ResolutionScaleTooltip": "Wendet die Auflösungsskalierung auf anwendbare Render Ziele", + "ResolutionScaleEntryTooltip": "Fließkomma Auflösungsskalierung, wie 1,5.\n Bei nicht ganzzahligen Werten ist die Wahrscheinlichkeit größer, dass Probleme entstehen, die auch zum Absturz führen können.", + "AnisotropyTooltip": "Stufe der Anisotropen Filterung (Auf Auto setzen, um den vom Spiel geforderten Wert zu verwenden)", + "AspectRatioTooltip": "Auf das Renderer-Fenster angewandtes Seitenverhältnis.", + "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", + "FileLogTooltip": "Speichert die Konsolenausgabe in einer Log-Datei auf der Festplatte. Hat keinen Einfluss auf die Leistung.", + "StubLogTooltip": "Ausgabe von Stub-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "InfoLogTooltip": "Ausgabe von Info-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "WarnLogTooltip": "Ausgabe von Warn-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "ErrorLogTooltip": "Ausgabe von Fehler-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "TraceLogTooltip": "Ausgabe von Trace-Log in der Konsole. Hat keinen Einfluss auf die Leistung.", + "GuestLogTooltip": "Ausgabe von Gast-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "FileAccessLogTooltip": "Ausgabe von FS-Zugriff-Logs in der Konsole.", + "FSAccessLogModeTooltip": "Aktiviert die Ausgabe des FS-Zugriff-Logs in der Konsole. Mögliche Modi sind 0-3", + "DeveloperOptionTooltip": "Mit Vorsicht verwenden", + "OpenGlLogLevel": "Erfordert die Aktivierung der entsprechenden Log-Level", + "DebugLogTooltip": "Ausgabe von Debug-Logs in der Konsole.\n\nVerwende diese Option nur auf ausdrückliche Anweisung von Ryujinx Entwicklern, da sie das Lesen der Protokolle erschwert und die Leistung des Emulators verschlechtert.", + "LoadApplicationFileTooltip": "Öffnet die Dateiauswahl um Datei zu laden, welche mit der Switch kompatibel ist", + "LoadApplicationFolderTooltip": "Öffnet die Dateiauswahl um ein Spiel zu laden, welches mit der Switch kompatibel ist", + "OpenRyujinxFolderTooltip": "Öffnet den Ordner, der das Ryujinx Dateisystem enthält", + "OpenRyujinxLogsTooltip": "Öffnet den Ordner, in welchem die Logs gespeichert werden", + "ExitTooltip": "Beendet Ryujinx", + "OpenSettingsTooltip": "Öffnet das Einstellungsfenster", + "OpenProfileManagerTooltip": "Öffnet das Profilverwaltungsfenster", + "StopEmulationTooltip": "Beendet die Emulation des derzeitigen Spiels und kehrt zu der Spielauswahl zurück", + "CheckUpdatesTooltip": "Sucht nach Updates für Ryujinx", + "OpenAboutTooltip": "Öffnet das 'Über Ryujinx'-Fenster", + "GridSize": "Rastergröße", + "GridSizeTooltip": "Ändert die Größe der Rasterelemente", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brasilianisches Portugiesisch", + "AboutRyujinxContributorsButtonHeader": "Alle Mitwirkenden anzeigen", + "SettingsTabSystemAudioVolume": "Lautstärke: ", + "AudioVolumeTooltip": "Ändert die Lautstärke", + "SettingsTabSystemEnableInternetAccess": "Gast-Internet-Zugang/LAN Modus", + "EnableInternetAccessTooltip": "Erlaubt es der emulierten Anwendung sich mit dem Internet zu verbinden.\n\nSpiele die den LAN-Modus unterstützen, ermöglichen es Ryujinx sich sowohl mit anderen Ryujinx-Systemen, als auch mit offiziellen Nintendo Switch Konsolen zu verbinden. Allerdings nur, wenn diese Option aktiviert ist und die Systeme mit demselben lokalen Netzwerk verbunden sind.\n\nDies erlaubt KEINE Verbindung zu Nintendo-Servern. Kann bei bestimmten Spielen die versuchen sich mit dem Internet zu verbinden zum Absturz führen.\n\nIm Zweifelsfall AUS lassen", + "GameListContextMenuManageCheatToolTip": "Öffnet den Cheat-Manager", + "GameListContextMenuManageCheat": "Cheats verwalten", + "ControllerSettingsStickRange": "Bereich:", + "DialogStopEmulationTitle": "Ryujinx - Beende Emulation", + "DialogStopEmulationMessage": "Emulation wirklich beenden?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Netzwerk", + "SettingsTabNetworkConnection": "Netwerkverbindung", + "SettingsTabCpuCache": "CPU-Cache", + "SettingsTabCpuMemory": "CPU-Speicher", + "DialogUpdaterFlatpakNotSupportedMessage": "Bitte aktualisiere Ryujinx mit FlatHub", + "UpdaterDisabledWarningTitle": "Updater deaktiviert!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für einen gemoddete Switch erstellt worden sind.", + "ControllerSettingsRotate90": "Um 90° rotieren", + "IconSize": "Cover Größe", + "IconSizeTooltip": "Ändert die Größe der Spiel-Cover", + "MenuBarOptionsShowConsole": "Zeige Konsole", + "ShaderCachePurgeError": "Es ist ein Fehler beim löschen des Shader Caches aufgetreten bei {0}: {1}", + "UserErrorNoKeys": "Keys nicht gefunden", + "UserErrorNoFirmware": "Firmware nicht gefunden", + "UserErrorFirmwareParsingFailed": "Firmware-Analysierung-Fehler", + "UserErrorApplicationNotFound": "Anwendung nicht gefunden", + "UserErrorUnknown": "Unbekannter Fehler", + "UserErrorUndefined": "Undefinierter Fehler", + "UserErrorNoKeysDescription": "Ryujinx konnte deine 'prod.keys' Datei nicht finden", + "UserErrorNoFirmwareDescription": "Ryujinx konnte keine installierte Firmware finden!", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx konnte die zu verfügung gestellte Firmware nicht analysieren. Ein möglicher Grund dafür sind veraltete keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx konnte keine valide Anwendung an dem gegeben Pfad finden.", + "UserErrorUnknownDescription": "Ein unbekannter Fehler ist aufgetreten!", + "UserErrorUndefinedDescription": "Ein undefinierter Fehler ist aufgetreten! Dies sollte nicht passieren. Bitte kontaktiere einen Entwickler!", + "OpenSetupGuideMessage": "Öffne den 'Setup Guide'", + "NoUpdate": "Kein Update", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Bestätigung", + "FileDialogAllTypes": "Alle Typen", + "Never": "Niemals", + "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein", + "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein", + "SoftwareKeyboard": "Software-Tastatur", + "SoftwareKeyboardModeNumbersOnly": "Nur Zahlen", + "SoftwareKeyboardModeAlphabet": "Keine CJK-Zeichen", + "SoftwareKeyboardModeASCII": "Nur ASCII-Text", + "DialogControllerAppletMessagePlayerRange": "Die Anwendung benötigt {0} Spieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", + "DialogControllerAppletMessage": "Die Anwendung benötigt genau {0} Speieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", + "DialogControllerAppletDockModeSet": "Der 'Docked Modus' ist ausgewählt. Handheld ist ebenfalls ungültig.\n\n", + "UpdaterRenaming": "Alte Dateien umbenennen...", + "UpdaterRenameFailed": "Der Updater konnte die folgende Datei nicht umbenennen: {0}", + "UpdaterAddingFiles": "Neue Dateien hinzufügen...", + "UpdaterExtracting": "Update extrahieren...", + "UpdaterDownloading": "Update herunterladen...", + "Game": "Spiel", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Verbindungsfehler.", + "AboutPageDeveloperListMore": "{0} und mehr...", + "ApiError": "API Fehler.", + "LoadingHeading": "{0} wird gestartet", + "CompilingPPTC": "PTC wird kompiliert", + "CompilingShaders": "Shader werden kompiliert", + "AllKeyboards": "Alle Tastaturen", + "OpenFileDialogTitle": "Wähle eine unterstützte Datei", + "OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel", + "AllSupportedFormats": "Alle unterstützten Formate", + "RyujinxUpdater": "Ryujinx - Updater", + "SettingsTabHotkeys": "Tastatur Hotkeys", + "SettingsTabHotkeysHotkeys": "Tastatur Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Zeige UI:", + "SettingsTabHotkeysPauseHotkey": "Pausieren:", + "SettingsTabHotkeysToggleMuteHotkey": "Stummschalten:", + "ControllerMotionTitle": "Bewegungssteuerung - Einstellungen", + "ControllerRumbleTitle": "Vibration - Einstellungen", + "SettingsSelectThemeFileDialogTitle": "Wähle ein Design für die Emulator-Benutzeroberfläche", + "SettingsXamlThemeFile": "Xaml Design-Datei", + "AvatarWindowTitle": "Profile verwalten - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unbekannt", + "Usage": "Nutzung", + "Writable": "Beschreibbar", + "SelectDlcDialogTitle": "DLC-Dateien auswählen", + "SelectUpdateDialogTitle": "Update-Datei auswählen", + "UserProfileWindowTitle": "Benutzerprofile verwalten", + "CheatWindowTitle": "Spiel-Cheats verwalten", + "DlcWindowTitle": "Spiel-DLC verwalten", + "UpdateWindowTitle": "Spiel-Updates verwalten", + "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "DLC verfügbar für {0} [{1}]", + "UserProfilesEditProfile": "Profil bearbeiten", + "Cancel": "Abbrechen", + "Save": "Speichern", + "Discard": "Verwerfen", + "UserProfilesSetProfileImage": "Profilbild einrichten", + "UserProfileEmptyNameError": "Name ist erforderlich", + "UserProfileNoImageError": "Bitte ein Profilbild auswählen", + "GameUpdateWindowHeading": "Update verfügbar für {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:", + "SettingsTabHotkeysResScaleDownHotkey": "Auflösung verringern:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "Benutzer Id:", + "SettingsTabGraphicsBackend": "Grafik-Backend:", + "SettingsTabGraphicsBackendTooltip": "Verwendendetes Grafik-Backend", + "SettingsEnableTextureRecompression": "Textur-Rekompression", + "SettingsEnableTextureRecompressionTooltip": "Komprimiert bestimmte Texturen, um den VRAM-Verbrauch zu reduzieren.\n\nEmpfohlen für die Verwendung von GPUs, die weniger als 4 GiB VRAM haben.\n\nIm Zweifelsfall AUS lassen", + "SettingsTabGraphicsPreferredGpu": "Bevorzugte GPU:", + "SettingsTabGraphicsPreferredGpuTooltip": "Wähle die Grafikkarte aus, die mit dem Vulkan Grafik-Backend verwendet werden soll.\n\nDies hat keinen Einfluss auf die GPU die OpenGL verwendet.\n\nIm Zweifelsfall die als \"dGPU\" gekennzeichnete GPU auswählen. Diese Einstellung unberührt lassen, wenn keine zur Auswahl steht.", + "SettingsAppRequiredRestartMessage": "Ein Neustart von Ryujinx ist erforderlich", + "SettingsGpuBackendRestartMessage": "Das Grafik-Backend oder die Grafikkarteneinstellungen wurden geändert. Ein Neustart ist erforderlich um diese Einstellungen anzuwenden.", + "SettingsGpuBackendRestartSubMessage": "Ryujinx jetzt neu starten?", + "RyujinxUpdaterMessage": "Möchtest du Ryujinx auf die neueste Version aktualisieren?", + "SettingsTabHotkeysVolumeUpHotkey": "Lautstärke erhöhen:", + "SettingsTabHotkeysVolumeDownHotkey": "Lautstärke verringern:", + "SettingsEnableMacroHLE": "HLE Makros aktivieren", + "SettingsEnableMacroHLETooltip": "High-Level-Emulation von GPU-Makrocode.\n\nVerbessert die Leistung, kann aber in einigen Spielen zu Grafikfehlern führen.\n\nBei Unsicherheit AKTIVIEREN.", + "SettingsEnableColorSpacePassthrough": "Farbraum Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Weist das Vulkan-Backend an, Farbinformationen ohne Angabe eines Farbraums weiterzuleiten. Für Benutzer mit Wide-Gamut-Displays kann dies zu lebendigeren Farben führen, allerdings auf Kosten der Farbkorrektheit.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Speicherstände verwalten", + "DeleteUserSave": "Möchtest du den Spielerstand für dieses Spiel löschen?", + "IrreversibleActionNote": "Diese Option kann nicht rückgängig gemacht werden.", + "SaveManagerHeading": "Spielstände für {0} verwalten", + "SaveManagerTitle": "Speicherdaten Manager", + "Name": "Name", + "Size": "Größe", + "Search": "Suche", + "UserProfilesRecoverLostAccounts": "Konto wiederherstellen", + "Recover": "Wiederherstellen", + "UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden", + "UserProfilesRecoverEmptyList": "Keine Profile zum Wiederherstellen", + "GraphicsAATooltip": "Wendet Anti-Aliasing auf das Spiel-Rendering an", + "GraphicsAALabel": "Antialiasing:", + "GraphicsScalingFilterLabel": "Skalierungsfilter:", + "GraphicsScalingFilterTooltip": "Ermöglicht Framebuffer-Skalierung", + "GraphicsScalingFilterLevelLabel": "Stufe", + "GraphicsScalingFilterLevelTooltip": "Skalierungsfilter-Stufe festlegen", + "SmaaLow": "SMAA Niedrig", + "SmaaMedium": "SMAA Mittel", + "SmaaHigh": "SMAA Hoch", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Nutzer bearbeiten", + "UserEditorTitleCreate": "Nutzer erstellen", + "SettingsTabNetworkInterface": "Netzwerkschnittstelle:", + "NetworkInterfaceTooltip": "Die Netzwerkschnittstelle, die für LAN-Funktionen verwendet wird", + "NetworkInterfaceDefault": "Standard", + "PackagingShaders": "Verpackt Shader", + "AboutChangelogButton": "Changelog in GitHub öffnen", + "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/el_GR.json b/src/Ryujinx.Ava/Assets/Locales/el_GR.json new file mode 100644 index 000000000..59f50ad92 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/el_GR.json @@ -0,0 +1,656 @@ +{ + "Language": "Ελληνικά", + "MenuBarFileOpenApplet": "Άνοιγμα Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Άνοιγμα του Mii Editor Applet σε Αυτόνομη λειτουργία", + "SettingsTabInputDirectMouseAccess": "Άμεση Πρόσβαση Ποντικιού", + "SettingsTabSystemMemoryManagerMode": "Λειτουργία Διαχείρισης Μνήμης:", + "SettingsTabSystemMemoryManagerModeSoftware": "Λογισμικό", + "SettingsTabSystemMemoryManagerModeHost": "Υπολογιστής (γρήγορο)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)", + "SettingsTabSystemUseHypervisor": "Χρήση Hypervisor", + "MenuBarFile": "_Αρχείο", + "MenuBarFileOpenFromFile": "_Φόρτωση Αρχείου Εφαρμογής", + "MenuBarFileOpenUnpacked": "Φόρτωση Απακετάριστου _Παιχνιδιού", + "MenuBarFileOpenEmuFolder": "Άνοιγμα Φακέλου Ryujinx", + "MenuBarFileOpenLogsFolder": "Άνοιγμα Φακέλου Καταγραφής", + "MenuBarFileExit": "_Έξοδος", + "MenuBarOptions": "Επιλογές", + "MenuBarOptionsToggleFullscreen": "Λειτουργία Πλήρους Οθόνης", + "MenuBarOptionsStartGamesInFullscreen": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη", + "MenuBarOptionsStopEmulation": "Διακοπή Εξομοίωσης", + "MenuBarOptionsSettings": "_Ρυθμίσεις", + "MenuBarOptionsManageUserProfiles": "Διαχείριση Προφίλ _Χρηστών", + "MenuBarActions": "_Δράσεις", + "MenuBarOptionsSimulateWakeUpMessage": "Προσομοίωση Μηνύματος Αφύπνισης", + "MenuBarActionsScanAmiibo": "Σάρωση Amiibo", + "MenuBarTools": "_Εργαλεία", + "MenuBarToolsInstallFirmware": "Εγκατάσταση Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Εγκατάσταση Firmware από XCI ή ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Εγκατάσταση Firmware από τοποθεσία", + "MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων", + "MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.", + "MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων", + "MenuBarHelp": "Βοήθεια", + "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", + "MenuBarHelpAbout": "Σχετικά με", + "MenuSearch": "Αναζήτηση...", + "GameListHeaderFavorite": "Αγαπημένο", + "GameListHeaderIcon": "Εικονίδιο", + "GameListHeaderApplication": "Όνομα", + "GameListHeaderDeveloper": "Προγραμματιστής", + "GameListHeaderVersion": "Έκδοση", + "GameListHeaderTimePlayed": "Χρόνος", + "GameListHeaderLastPlayed": "Παίχτηκε", + "GameListHeaderFileExtension": "Κατάληξη", + "GameListHeaderFileSize": "Μέγεθος Αρχείου", + "GameListHeaderPath": "Τοποθεσία", + "GameListContextMenuOpenUserSaveDirectory": "Άνοιγμα Τοποθεσίας Αποθήκευσης Χρήστη", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Χρήστη της εφαρμογής", + "GameListContextMenuOpenDeviceSaveDirectory": "Άνοιγμα Τοποθεσίας Συσκευής Χρήστη", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Συσκευής της εφαρμογής", + "GameListContextMenuOpenBcatSaveDirectory": "Άνοιγμα Τοποθεσίας BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση BCAT της εφαρμογής", + "GameListContextMenuManageTitleUpdates": "Διαχείριση Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageTitleUpdatesToolTip": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageDlc": "Διαχείριση DLC", + "GameListContextMenuManageDlcToolTip": "Ανοίγει το παράθυρο διαχείρισης DLC", + "GameListContextMenuOpenModsDirectory": "Άνοιγμα Τοποθεσίας Τροποποιήσεων", + "GameListContextMenuOpenModsDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τις Τροποποιήσεις της εφαρμογής", + "GameListContextMenuCacheManagement": "Διαχείριση Προσωρινής Μνήμης", + "GameListContextMenuCacheManagementPurgePptc": "Εκκαθάριση Προσωρινής Μνήμης PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Διαγράφει την προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementPurgeShaderCache": "Εκκαθάριση Προσωρινής Μνήμης Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Διαγράφει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Άνοιγμα Τοποθεσίας PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τη προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Άνοιγμα τοποθεσίας προσωρινής μνήμης Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuExtractData": "Εξαγωγή Δεδομένων", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Εξαγωγή της ενότητας ExeFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Εξαγωγή της ενότητας RomFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataLogo": "Λογότυπο", + "GameListContextMenuExtractDataLogoToolTip": "Εξαγωγή της ενότητας Logo από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "StatusBarGamesLoaded": "{0}/{1} Φορτωμένα Παιχνίδια", + "StatusBarSystemVersion": "Έκδοση Συστήματος: {0}", + "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", + "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", + "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", + "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "Settings": "Ρυθμίσεις", + "SettingsTabGeneral": "Εμφάνιση", + "SettingsTabGeneralGeneral": "Γενικά", + "SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", + "SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".", + "SettingsTabGeneralHideCursor": "Απόκρυψη Κέρσορα:", + "SettingsTabGeneralHideCursorNever": "Ποτέ", + "SettingsTabGeneralHideCursorOnIdle": "Απόκρυψη Δρομέα στην Αδράνεια", + "SettingsTabGeneralHideCursorAlways": "Πάντα", + "SettingsTabGeneralGameDirectories": "Τοποθεσίες παιχνιδιών", + "SettingsTabGeneralAdd": "Προσθήκη", + "SettingsTabGeneralRemove": "Αφαίρεση", + "SettingsTabSystem": "Σύστημα", + "SettingsTabSystemCore": "Πυρήνας", + "SettingsTabSystemSystemRegion": "Περιοχή Συστήματος:", + "SettingsTabSystemSystemRegionJapan": "Ιαπωνία", + "SettingsTabSystemSystemRegionUSA": "ΗΠΑ", + "SettingsTabSystemSystemRegionEurope": "Ευρώπη", + "SettingsTabSystemSystemRegionAustralia": "Αυστραλία", + "SettingsTabSystemSystemRegionChina": "Κίνα", + "SettingsTabSystemSystemRegionKorea": "Κορέα", + "SettingsTabSystemSystemRegionTaiwan": "Ταϊβάν", + "SettingsTabSystemSystemLanguage": "Γλώσσα Συστήματος:", + "SettingsTabSystemSystemLanguageJapanese": "Ιαπωνικά", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Αμερικάνικα Αγγλικά", + "SettingsTabSystemSystemLanguageFrench": "Γαλλικά", + "SettingsTabSystemSystemLanguageGerman": "Γερμανικά", + "SettingsTabSystemSystemLanguageItalian": "Ιταλικά", + "SettingsTabSystemSystemLanguageSpanish": "Ισπανικά", + "SettingsTabSystemSystemLanguageChinese": "Κινέζικα", + "SettingsTabSystemSystemLanguageKorean": "Κορεάτικα", + "SettingsTabSystemSystemLanguageDutch": "Ολλανδικά", + "SettingsTabSystemSystemLanguagePortuguese": "Πορτογαλικά", + "SettingsTabSystemSystemLanguageRussian": "Ρώσικα", + "SettingsTabSystemSystemLanguageTaiwanese": "Ταϊβανέζικα", + "SettingsTabSystemSystemLanguageBritishEnglish": "Βρετανικά Αγγλικά", + "SettingsTabSystemSystemLanguageCanadianFrench": "Καναδικά Γαλλικά", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Λατινοαμερικάνικα Ισπανικά", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Απλοποιημένα Κινέζικα", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Παραδοσιακά Κινεζικά", + "SettingsTabSystemSystemTimeZone": "Ζώνη Ώρας Συστήματος:", + "SettingsTabSystemSystemTime": "Ώρα Συστήματος:", + "SettingsTabSystemEnableVsync": "Ενεργοποίηση Κατακόρυφου Συγχρονισμού", + "SettingsTabSystemEnablePptc": "Ενεργοποίηση PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Ενεργοποίηση Ελέγχων Ακεραιότητας FS", + "SettingsTabSystemAudioBackend": "Backend Ήχου:", + "SettingsTabSystemAudioBackendDummy": "Απενεργοποιημένο", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Μικροδιορθώσεις", + "SettingsTabSystemHacksNote": " (Μπορεί να προκαλέσουν αστάθεια)", + "SettingsTabSystemExpandDramSize": "Επέκταση μεγέθους DRAM στα 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Αγνόηση υπηρεσιών που λείπουν", + "SettingsTabGraphics": "Γραφικά", + "SettingsTabGraphicsAPI": "API Γραφικά", + "SettingsTabGraphicsEnableShaderCache": "Ενεργοποίηση Προσωρινής Μνήμης Shader", + "SettingsTabGraphicsAnisotropicFiltering": "Ανισότροπο Φιλτράρισμα:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Αυτόματο", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Κλίμακα Ανάλυσης:", + "SettingsTabGraphicsResolutionScaleCustom": "Προσαρμοσμένο (Δεν συνιστάται)", + "SettingsTabGraphicsResolutionScaleNative": "Εγγενής (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Αναλογία Απεικόνισης:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Έκταση σε όλο το παράθυρο", + "SettingsTabGraphicsDeveloperOptions": "Επιλογές Προγραμματιστή", + "SettingsTabGraphicsShaderDumpPath": "Τοποθεσία Shaders Γραφικών:", + "SettingsTabLogging": "Καταγραφή", + "SettingsTabLoggingLogging": "Καταγραφή", + "SettingsTabLoggingEnableLoggingToFile": "Ενεργοποίηση Καταγραφής Αρχείου", + "SettingsTabLoggingEnableStubLogs": "Ενεργοποίηση Καταγραφής Stub", + "SettingsTabLoggingEnableInfoLogs": "Ενεργοποίηση Καταγραφής Πληροφοριών", + "SettingsTabLoggingEnableWarningLogs": "Ενεργοποίηση Καταγραφής Προειδοποίησης", + "SettingsTabLoggingEnableErrorLogs": "Ενεργοποίηση Καταγραφής Σφαλμάτων", + "SettingsTabLoggingEnableTraceLogs": "Ενεργοποίηση Καταγραφής Ιχνών", + "SettingsTabLoggingEnableGuestLogs": "Ενεργοποίηση Καταγραφής Επισκεπτών", + "SettingsTabLoggingEnableFsAccessLogs": "Ενεργοποίηση Καταγραφής Πρόσβασης FS", + "SettingsTabLoggingFsGlobalAccessLogMode": "Λειτουργία Καταγραφής Καθολικής Πρόσβασης FS:", + "SettingsTabLoggingDeveloperOptions": "Επιλογές Προγραμματιστή (ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η απόδοση Θα μειωθεί)", + "SettingsTabLoggingDeveloperOptionsNote": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Θα μειώσει την απόδοση", + "SettingsTabLoggingGraphicsBackendLogLevel": "Επίπεδο Καταγραφής Διεπαφής Γραφικών:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Κανένα", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Σφάλμα", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Επιβραδύνσεις", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Όλα", + "SettingsTabLoggingEnableDebugLogs": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων", + "SettingsTabInput": "Χειρισμός", + "SettingsTabInputEnableDockedMode": "Ενεργοποίηση Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο", + "SettingsButtonSave": "Αποθήκευση", + "SettingsButtonClose": "Κλείσιμο", + "SettingsButtonOk": "ΟΚ", + "SettingsButtonCancel": "Ακύρωση", + "SettingsButtonApply": "Εφαρμογή", + "ControllerSettingsPlayer": "Παίχτης", + "ControllerSettingsPlayer1": "Παίχτης 1", + "ControllerSettingsPlayer2": "Παίχτης 2", + "ControllerSettingsPlayer3": "Παίχτης 3", + "ControllerSettingsPlayer4": "Παίχτης 4", + "ControllerSettingsPlayer5": "Παίχτης 5", + "ControllerSettingsPlayer6": "Παίχτης 6", + "ControllerSettingsPlayer7": "Παίχτης 7", + "ControllerSettingsPlayer8": "Παίχτης 8", + "ControllerSettingsHandheld": "Χειροκίνητο", + "ControllerSettingsInputDevice": "Συσκευή Χειρισμού", + "ControllerSettingsRefresh": "Ανανέωση", + "ControllerSettingsDeviceDisabled": "Απενεργοποιημένο", + "ControllerSettingsControllerType": "Τύπος Χειριστηρίου", + "ControllerSettingsControllerTypeHandheld": "Φορητό", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Ζεύγος JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "Αριστερό JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Δεξί JoyCon", + "ControllerSettingsProfile": "Προφίλ", + "ControllerSettingsProfileDefault": "Προκαθορισμένο", + "ControllerSettingsLoad": "Φόρτωση", + "ControllerSettingsAdd": "Προσθήκη", + "ControllerSettingsRemove": "Αφαίρεση", + "ControllerSettingsButtons": "Κουμπιά", + "ControllerSettingsButtonA": "Α", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Κατευθυντικό Pad", + "ControllerSettingsDPadUp": "Πάνω", + "ControllerSettingsDPadDown": "Κάτω", + "ControllerSettingsDPadLeft": "Αριστερά", + "ControllerSettingsDPadRight": "Δεξιά", + "ControllerSettingsStickButton": "Κουμπί", + "ControllerSettingsStickUp": "Πάνω", + "ControllerSettingsStickDown": "Κάτω", + "ControllerSettingsStickLeft": "Αριστερά", + "ControllerSettingsStickRight": "Δεξιά", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Invert Stick X", + "ControllerSettingsStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Αριστερός Μοχλός", + "ControllerSettingsRStick": "Δεξιός Μοχλός", + "ControllerSettingsTriggersLeft": "Αριστερή Σκανδάλη", + "ControllerSettingsTriggersRight": "Δεξιά Σκανδάλη", + "ControllerSettingsTriggersButtonsLeft": "Αριστερά Κουμπιά Σκανδάλης", + "ControllerSettingsTriggersButtonsRight": "Δεξιά Κουμπιά Σκανδάλης", + "ControllerSettingsTriggers": "Σκανδάλες", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Αριστερά Κουμπιά", + "ControllerSettingsExtraButtonsRight": "Δεξιά Κουμπιά", + "ControllerSettingsMisc": "Διάφορα", + "ControllerSettingsTriggerThreshold": "Κατώφλι Σκανδάλης:", + "ControllerSettingsMotion": "Κίνηση", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Κίνηση συμβατή με CemuHook", + "ControllerSettingsMotionControllerSlot": "Υποδοχή Χειριστηρίου:", + "ControllerSettingsMotionMirrorInput": "Καθρεπτισμός Χειρισμού", + "ControllerSettingsMotionRightJoyConSlot": "Δεξιά Υποδοχή JoyCon:", + "ControllerSettingsMotionServerHost": "Κεντρικός Υπολογιστής Διακομιστή:", + "ControllerSettingsMotionGyroSensitivity": "Ευαισθησία Γυροσκοπίου:", + "ControllerSettingsMotionGyroDeadzone": "Νεκρή Ζώνη Γυροσκοπίου:", + "ControllerSettingsSave": "Αποθήκευση", + "ControllerSettingsClose": "Κλείσιμο", + "UserProfilesSelectedUserProfile": "Επιλεγμένο Προφίλ Χρήστη:", + "UserProfilesSaveProfileName": "Αποθήκευση Ονόματος Προφίλ", + "UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ", + "UserProfilesAvailableUserProfiles": "Διαθέσιμα Προφίλ Χρηστών:", + "UserProfilesAddNewProfile": "Προσθήκη Νέου Προφίλ", + "UserProfilesDelete": "Διαγράφω", + "UserProfilesClose": "Κλείσιμο", + "ProfileNameSelectionWatermark": "Επιλέξτε ψευδώνυμο", + "ProfileImageSelectionTitle": "Επιλογή Εικόνας Προφίλ", + "ProfileImageSelectionHeader": "Επιλέξτε μία Εικόνα Προφίλ", + "ProfileImageSelectionNote": "Μπορείτε να εισαγάγετε μία προσαρμοσμένη εικόνα προφίλ ή να επιλέξετε ένα avatar από το Firmware", + "ProfileImageSelectionImportImage": "Εισαγωγή Αρχείου Εικόνας", + "ProfileImageSelectionSelectAvatar": "Επιλέξτε Avatar από Firmware", + "InputDialogTitle": "Διάλογος Εισαγωγής", + "InputDialogOk": "ΟΚ", + "InputDialogCancel": "Ακύρωση", + "InputDialogAddNewProfileTitle": "Επιλογή Ονόματος Προφίλ", + "InputDialogAddNewProfileHeader": "Εισαγωγή Ονόματος Προφίλ", + "InputDialogAddNewProfileSubtext": "(Σύνολο Χαρακτήρων: {0})", + "AvatarChoose": "Επιλογή", + "AvatarSetBackgroundColor": "Ορισμός Χρώματος Φόντου", + "AvatarClose": "Κλείσιμο", + "ControllerSettingsLoadProfileToolTip": "Φόρτωση Προφίλ", + "ControllerSettingsAddProfileToolTip": "Προσθήκη Προφίλ", + "ControllerSettingsRemoveProfileToolTip": "Κατάργηση Προφίλ", + "ControllerSettingsSaveProfileToolTip": "Αποθήκευση Προφίλ", + "MenuBarFileToolsTakeScreenshot": "Λήψη Στιγμιότυπου", + "MenuBarFileToolsHideUi": "Απόκρυψη UI", + "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuToggleFavorite": "Εναλλαγή Αγαπημένου", + "GameListContextMenuToggleFavoriteToolTip": "Εναλλαγή της Κατάστασης Αγαπημένο του Παιχνιδιού", + "SettingsTabGeneralTheme": "Θέμα", + "SettingsTabGeneralThemeCustomTheme": "Προσαρμοσμένη Τοποθεσία Θέματος", + "SettingsTabGeneralThemeBaseStyle": "Βασικό Στυλ", + "SettingsTabGeneralThemeBaseStyleDark": "Σκούρο", + "SettingsTabGeneralThemeBaseStyleLight": "Ανοιχτό", + "SettingsTabGeneralThemeEnableCustomTheme": "Ενεργοποίηση Προσαρμοσμένου Θέματος", + "ButtonBrowse": "Αναζήτηση", + "ControllerSettingsConfigureGeneral": "Παραμέτρων", + "ControllerSettingsRumble": "Δόνηση", + "ControllerSettingsRumbleStrongMultiplier": "Ισχυρός Πολλαπλασιαστής Δόνησης", + "ControllerSettingsRumbleWeakMultiplier": "Αδύναμος Πολλαπλασιαστής Δόνησης", + "DialogMessageSaveNotAvailableMessage": "Δεν υπάρχουν αποθηκευμένα δεδομένα για το {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Θέλετε να αποθηκεύσετε δεδομένα για αυτό το παιχνίδι;", + "DialogConfirmationTitle": "Ryujinx - Επιβεβαίωση", + "DialogUpdaterTitle": "Ryujinx - Ενημερωτής", + "DialogErrorTitle": "Ryujinx - Σφάλμα", + "DialogWarningTitle": "Ryujinx - Προειδοποίηση", + "DialogExitTitle": "Ryujinx - Έξοδος", + "DialogErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", + "DialogExitMessage": "Είστε βέβαιοι ότι θέλετε να κλείσετε το Ryujinx;", + "DialogExitSubMessage": "Όλα τα μη αποθηκευμένα δεδομένα θα χαθούν!", + "DialogMessageCreateSaveErrorMessage": "Σφάλμα κατά τη δημιουργία των αποθηκευμένων δεδομένων: {0}", + "DialogMessageFindSaveErrorMessage": "Σφάλμα κατά την εύρεση των αποθηκευμένων δεδομένων: {0}", + "FolderDialogExtractTitle": "Επιλέξτε τον φάκελο στον οποίο θέλετε να εξαγάγετε", + "DialogNcaExtractionMessage": "Εξαγωγή ενότητας {0} από {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Εξαγωγέας Τμημάτων", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Αποτυχία εξαγωγής. Η κύρια NCA δεν υπήρχε στο επιλεγμένο αρχείο.", + "DialogNcaExtractionCheckLogErrorMessage": "Αποτυχία εξαγωγής. Διαβάστε το αρχείο καταγραφής για περισσότερες πληροφορίες.", + "DialogNcaExtractionSuccessMessage": "Η εξαγωγή ολοκληρώθηκε με επιτυχία.", + "DialogUpdaterConvertFailedMessage": "Αποτυχία μετατροπής της τρέχουσας έκδοσης Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Ακύρωση Ενημέρωσης!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Χρησιμοποιείτε ήδη την πιο ενημερωμένη έκδοση του Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Προέκυψε ένα σφάλμα στη λήψη πληροφοριών έκδοσης από τα GitHub Releases. Αυτό δύναται να συμβεί αν μία έκδοση χτίζεται αυτή τη στιγμή στα GitHub Actions. Παρακαλούμε προσπαθήστε αργότερα.", + "DialogUpdaterConvertFailedGithubMessage": "Αποτυχία μετατροπής της ληφθείσας έκδοσης Ryujinx από την έκδοση GitHub.", + "DialogUpdaterDownloadingMessage": "Λήψη Ενημέρωσης...", + "DialogUpdaterExtractionMessage": "Εξαγωγή Ενημέρωσης...", + "DialogUpdaterRenamingMessage": "Μετονομασία Ενημέρωσης...", + "DialogUpdaterAddingFilesMessage": "Προσθήκη Νέας Ενημέρωσης...", + "DialogUpdaterCompleteMessage": "Η Ενημέρωση Ολοκληρώθηκε!", + "DialogUpdaterRestartMessage": "Θέλετε να επανεκκινήσετε το Ryujinx τώρα;", + "DialogUpdaterArchNotSupportedMessage": "Δεν υπάρχει υποστηριζόμενη αρχιτεκτονική συστήματος!", + "DialogUpdaterArchNotSupportedSubMessage": "(Υποστηρίζονται μόνο συστήματα x64!)", + "DialogUpdaterNoInternetMessage": "Δεν είστε συνδεδεμένοι στο Διαδίκτυο!", + "DialogUpdaterNoInternetSubMessage": "Επαληθεύστε ότι έχετε σύνδεση στο Διαδίκτυο που λειτουργεί!", + "DialogUpdaterDirtyBuildMessage": "Δεν μπορείτε να ενημερώσετε μία Πρόχειρη Έκδοση του Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Κάντε λήψη του Ryujinx στη διεύθυνση https://ryujinx.org/ εάν αναζητάτε μία υποστηριζόμενη έκδοση.", + "DialogRestartRequiredMessage": "Απαιτείται Επανεκκίνηση", + "DialogThemeRestartMessage": "Το θέμα έχει αποθηκευτεί. Απαιτείται επανεκκίνηση για την εφαρμογή του θέματος.", + "DialogThemeRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση", + "DialogFirmwareInstallEmbeddedMessage": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Δεν βρέθηκε εγκατεστημένο Firmware, αλλά το Ryujinx μπόρεσε να εγκαταστήσει το Firmware {0} από το παρεχόμενο παιχνίδι.\nΟ εξομοιωτής θα ξεκινήσει τώρα.", + "DialogFirmwareNoFirmwareInstalledMessage": "Δεν έχει εγκατασταθεί Firmware", + "DialogFirmwareInstalledMessage": "Το Firmware {0} εγκαταστάθηκε", + "DialogInstallFileTypesSuccessMessage": "Επιτυχής εγκατάσταση τύπων αρχείων!", + "DialogInstallFileTypesErrorMessage": "Απέτυχε η εγκατάσταση τύπων αρχείων.", + "DialogUninstallFileTypesSuccessMessage": "Επιτυχής απεγκατάσταση τύπων αρχείων!", + "DialogUninstallFileTypesErrorMessage": "Αποτυχία απεγκατάστασης τύπων αρχείων.", + "DialogOpenSettingsWindowLabel": "Άνοιγμα Παραθύρου Ρυθμίσεων", + "DialogControllerAppletTitle": "Applet Χειρισμού", + "DialogMessageDialogErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου Μηνυμάτων: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Σφάλμα εμφάνισης Λογισμικού Πληκτρολογίου: {0}", + "DialogErrorAppletErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nΓια πληροφορίες σχετικά με τον τρόπο διόρθωσης του σφάλματος, ακολουθήστε τον Οδηγό Εγκατάστασης.", + "DialogUserErrorDialogTitle": "Σφάλμα Ryujinx ({0})", + "DialogAmiiboApiTitle": "API για Amiibo.", + "DialogAmiiboApiFailFetchMessage": "Παρουσιάστηκε σφάλμα κατά την ανάκτηση πληροφοριών από το API.", + "DialogAmiiboApiConnectErrorMessage": "Δεν είναι δυνατή η σύνδεση με τον διακομιστή Amiibo API. Η υπηρεσία μπορεί να είναι εκτός λειτουργίας ή μπορεί να χρειαστεί να επαληθεύσετε ότι έχετε ενεργή σύνδεσή στο Διαδίκτυο.", + "DialogProfileInvalidProfileErrorMessage": "Το προφίλ {0} δεν είναι συμβατό με το τρέχον σύστημα χειρισμού.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Το προεπιλεγμένο προφίλ δεν μπορεί να αντικατασταθεί", + "DialogProfileDeleteProfileTitle": "Διαγραφή Προφίλ", + "DialogProfileDeleteProfileMessage": "Αυτή η ενέργεια είναι μη αναστρέψιμη, είστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogWarning": "Προειδοποίηση", + "DialogPPTCDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη PPTC για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogPPTCDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης PPTC στο {0}: {1}", + "DialogShaderDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη Shader για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogShaderDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης Shader στο {0}: {1}", + "DialogRyujinxErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", + "DialogInvalidTitleIdErrorMessage": "Σφάλμα UI: Το επιλεγμένο παιχνίδι δεν έχει έγκυρο αναγνωριστικό τίτλου", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Δεν βρέθηκε έγκυρο Firmware συστήματος στο {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Εγκατάσταση Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Θα εγκατασταθεί η έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nΑυτό θα αντικαταστήσει την τρέχουσα έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nΘέλετε να συνεχίσετε;", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Εγκατάσταση Firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Η έκδοση συστήματος {0} εγκαταστάθηκε με επιτυχία.", + "DialogUserProfileDeletionWarningMessage": "Δεν θα υπάρχουν άλλα προφίλ εάν διαγραφεί το επιλεγμένο", + "DialogUserProfileDeletionConfirmMessage": "Θέλετε να διαγράψετε το επιλεγμένο προφίλ", + "DialogUserProfileUnsavedChangesTitle": "Προσοχή - Μην Αποθηκευμένες Αλλαγές.", + "DialogUserProfileUnsavedChangesMessage": "Έχετε κάνει αλλαγές σε αυτό το προφίλ χρήστη που δεν έχουν αποθηκευτεί.", + "DialogUserProfileUnsavedChangesSubMessage": "Θέλετε να απορρίψετε τις αλλαγές σας;", + "DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;", + "DialogLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}", + "DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!", + "DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Έχετε ενεργοποιήσει το Shader Dumping, το οποίο έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται να απενεργοποιήσετε το Shader Dumping. Θέλετε να απενεργοποιήσετε τώρα το Shader Dumping;", + "DialogLoadAppGameAlreadyLoadedMessage": "Ένα παιχνίδι έχει ήδη φορτωθεί", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Σταματήστε την εξομοίωση ή κλείστε τον εξομοιωτή πριν ξεκινήσετε ένα άλλο παιχνίδι.", + "DialogUpdateAddUpdateErrorMessage": "Το αρχείο δεν περιέχει ενημέρωση για τον επιλεγμένο τίτλο!", + "DialogSettingsBackendThreadingWarningTitle": "Προειδοποίηση - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Το Ryujinx πρέπει να επανεκκινηθεί αφού αλλάξει αυτή η επιλογή για να εφαρμοστεί πλήρως. Ανάλογα με την πλατφόρμα σας, μπορεί να χρειαστεί να απενεργοποιήσετε με μη αυτόματο τρόπο το multithreading του ίδιου του προγράμματος οδήγησης όταν χρησιμοποιείτε το Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Χαρακτηριστικά", + "SettingsTabGraphicsBackendMultithreading": "Πολυνηματική Επεξεργασία Γραφικών:", + "CommonAuto": "Αυτόματο", + "CommonOff": "Ανενεργό", + "CommonOn": "Ενεργό", + "InputDialogYes": "Ναι", + "InputDialogNo": "Όχι", + "DialogProfileInvalidProfileNameErrorMessage": "Το όνομα αρχείου περιέχει μη έγκυρους χαρακτήρες. Παρακαλώ προσπαθήστε ξανά.", + "MenuBarOptionsPauseEmulation": "Παύση", + "MenuBarOptionsResumeEmulation": "Συνέχιση", + "AboutUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τον ιστότοπο Ryujinx στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutDisclaimerMessage": "Το Ryujinx δεν είναι συνδεδεμένο με τη Nintendo™,\nούτε με κανέναν από τους συνεργάτες της, με οποιονδήποτε τρόπο.", + "AboutAmiiboDisclaimerMessage": "Το AmiiboAPI (www.amiiboapi.com) χρησιμοποιείται\nστην προσομοίωση Amiibo.", + "AboutPatreonUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Patreon στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutGithubUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx GitHub στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutDiscordUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε μία πρόσκληση στον διακομιστή Ryujinx Discord στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutTwitterUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Twitter στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutRyujinxAboutTitle": "Σχετικά με:", + "AboutRyujinxAboutContent": "Το Ryujinx είναι ένας εξομοιωτής για το Nintendo Switch™.\nΥποστηρίξτε μας στο Patreon.\nΛάβετε όλα τα τελευταία νέα στο Twitter ή στο Discord.\nΟι προγραμματιστές που ενδιαφέρονται να συνεισφέρουν μπορούν να μάθουν περισσότερα στο GitHub ή στο Discord μας.", + "AboutRyujinxMaintainersTitle": "Συντηρείται από:", + "AboutRyujinxMaintainersContentTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Συνεισφέροντες στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutRyujinxSupprtersTitle": "Υποστηρίζεται στο Patreon από:", + "AmiiboSeriesLabel": "Σειρά Amiibo", + "AmiiboCharacterLabel": "Χαρακτήρας", + "AmiiboScanButtonLabel": "Σαρώστε το", + "AmiiboOptionsShowAllLabel": "Εμφάνιση όλων των Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Χρησιμοποιήστε τυχαίο αναγνωριστικό UUID", + "DlcManagerTableHeadingEnabledLabel": "Ενεργοποιημένο", + "DlcManagerTableHeadingTitleIdLabel": "Αναγνωριστικό τίτλου", + "DlcManagerTableHeadingContainerPathLabel": "Τοποθεσία DLC", + "DlcManagerTableHeadingFullPathLabel": "Πλήρης τοποθεσία", + "DlcManagerRemoveAllButton": "Αφαίρεση όλων", + "DlcManagerEnableAllButton": "Ενεργοποίηση Όλων", + "DlcManagerDisableAllButton": "Απενεργοποίηση Όλων", + "MenuBarOptionsChangeLanguage": "Αλλαξε γλώσσα", + "MenuBarShowFileTypes": "Εμφάνιση Τύπων Αρχείων", + "CommonSort": "Κατάταξη", + "CommonShowNames": "Εμφάνιση ονομάτων", + "CommonFavorite": "Αγαπημένα", + "OrderAscending": "Αύξουσα", + "OrderDescending": "Φθίνουσα", + "SettingsTabGraphicsFeatures": "Χαρακτηριστικά & Βελτιώσεις", + "ErrorWindowTitle": "Παράθυρο σφάλματος", + "ToggleDiscordTooltip": "Ενεργοποιεί ή απενεργοποιεί την Εμπλουτισμένη Παρουσία σας στο Discord", + "AddGameDirBoxTooltip": "Εισαγάγετε μία τοποθεσία παιχνιδιών για προσθήκη στη λίστα", + "AddGameDirTooltip": "Προσθέστε μία τοποθεσία παιχνιδιών στη λίστα", + "RemoveGameDirTooltip": "Αφαιρέστε την επιλεγμένη τοποθεσία παιχνιδιών", + "CustomThemeCheckTooltip": "Ενεργοποίηση ή απενεργοποίηση προσαρμοσμένων θεμάτων στο GUI", + "CustomThemePathTooltip": "Διαδρομή προς το προσαρμοσμένο θέμα GUI", + "CustomThemeBrowseTooltip": "Αναζητήστε ένα προσαρμοσμένο θέμα GUI", + "DockModeToggleTooltip": "Ενεργοποιήστε ή απενεργοποιήστε τη λειτουργία σύνδεσης", + "DirectKeyboardTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης πληκτρολογίου (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο πληκτρολόγιό σας ως συσκευή εισαγωγής κειμένου)", + "DirectMouseTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης ποντικιού (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο ποντίκι σας ως συσκευή κατάδειξης)", + "RegionTooltip": "Αλλαγή Περιοχής Συστήματος", + "LanguageTooltip": "Αλλαγή Γλώσσας Συστήματος", + "TimezoneTooltip": "Αλλαγή Ζώνης Ώρας Συστήματος", + "TimeTooltip": "Αλλαγή Ώρας Συστήματος", + "VSyncToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί τον κατακόρυφο συγχρονισμό", + "PptcToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί το PPTC", + "FsIntegrityToggleTooltip": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού", + "AudioBackendTooltip": "Αλλαγή ήχου υποστήριξης", + "MemoryManagerTooltip": "Αλλάξτε τον τρόπο αντιστοίχισης και πρόσβασης στη μνήμη επισκέπτη. Επηρεάζει σε μεγάλο βαθμό την απόδοση της προσομοίωσης της CPU.", + "MemoryManagerSoftwareTooltip": "Χρησιμοποιήστε έναν πίνακα σελίδων λογισμικού για τη μετάφραση διευθύνσεων. Υψηλότερη ακρίβεια αλλά πιο αργή απόδοση.", + "MemoryManagerHostTooltip": "Απευθείας αντιστοίχιση της μνήμης στον χώρο διευθύνσεων υπολογιστή υποδοχής. Πολύ πιο γρήγορη μεταγλώττιση και εκτέλεση JIT.", + "MemoryManagerUnsafeTooltip": "Απευθείας χαρτογράφηση της μνήμης, αλλά μην καλύπτετε τη διεύθυνση εντός του χώρου διευθύνσεων επισκέπτη πριν από την πρόσβαση. Πιο γρήγορα, αλλά με κόστος ασφάλειας. Η εφαρμογή μπορεί να έχει πρόσβαση στη μνήμη από οπουδήποτε στο Ryujinx, επομένως εκτελείτε μόνο προγράμματα που εμπιστεύεστε με αυτήν τη λειτουργία.", + "UseHypervisorTooltip": "Χρησιμοποιήστε Hypervisor αντί για JIT. Βελτιώνει σημαντικά την απόδοση όταν διατίθεται, αλλά μπορεί να είναι ασταθής στην τρέχουσα κατάστασή του.", + "DRamTooltip": "Επεκτείνει την ποσότητα της μνήμης στο εξομοιούμενο σύστημα από 4 GiB σε 6 GiB", + "IgnoreMissingServicesTooltip": "Ενεργοποίηση ή απενεργοποίηση της αγνοώησης για υπηρεσίες που λείπουν", + "GraphicsBackendThreadingTooltip": "Ενεργοποίηση Πολυνηματικής Επεξεργασίας Γραφικών", + "GalThreadingTooltip": "Εκτελεί εντολές γραφικών σε ένα δεύτερο νήμα. Επιτρέπει την πολυνηματική μεταγλώττιση Shader σε χρόνο εκτέλεσης, μειώνει το τρεμόπαιγμα και βελτιώνει την απόδοση των προγραμμάτων οδήγησης χωρίς τη δική τους υποστήριξη πολλαπλών νημάτων. Ποικίλες κορυφαίες επιδόσεις σε προγράμματα οδήγησης με multithreading. Μπορεί να χρειαστεί επανεκκίνηση του Ryujinx για να απενεργοποιήσετε σωστά την ενσωματωμένη λειτουργία πολλαπλών νημάτων του προγράμματος οδήγησης ή ίσως χρειαστεί να το κάνετε χειροκίνητα για να έχετε την καλύτερη απόδοση.", + "ShaderCacheToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί την Προσωρινή Μνήμη Shader", + "ResolutionScaleTooltip": "Κλίμακα ανάλυσης που εφαρμόστηκε σε ισχύοντες στόχους απόδοσης", + "ResolutionScaleEntryTooltip": "Κλίμακα ανάλυσης κινητής υποδιαστολής, όπως 1,5. Οι μη αναπόσπαστες τιμές είναι πιθανό να προκαλέσουν προβλήματα ή σφάλματα.", + "AnisotropyTooltip": "Επίπεδο Ανισότροπου Φιλτραρίσματος (ρυθμίστε το στο Αυτόματο για να χρησιμοποιηθεί η τιμή που ζητήθηκε από το παιχνίδι)", + "AspectRatioTooltip": "Λόγος διαστάσεων που εφαρμόστηκε στο παράθυρο απόδοσης.", + "ShaderDumpPathTooltip": "Τοποθεσία Εναπόθεσης Προσωρινής Μνήμης Shaders", + "FileLogTooltip": "Ενεργοποιεί ή απενεργοποιεί την καταγραφή σε ένα αρχείο στο δίσκο", + "StubLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής ατελειών", + "InfoLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πληροφοριών", + "WarnLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής προειδοποιήσεων", + "ErrorLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής σφαλμάτων", + "TraceLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής ιχνών", + "GuestLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής επισκεπτών", + "FileAccessLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πρόσβασης", + "FSAccessLogModeTooltip": "Ενεργοποιεί την έξοδο καταγραφής πρόσβασης FS στην κονσόλα. Οι πιθανοί τρόποι λειτουργίας είναι 0-3", + "DeveloperOptionTooltip": "Χρησιμοποιήστε με προσοχή", + "OpenGlLogLevel": "Απαιτεί τα κατάλληλα επίπεδα καταγραφής ενεργοποιημένα", + "DebugLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων", + "LoadApplicationFileTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε ένα αρχείο συμβατό με το Switch για φόρτωση", + "LoadApplicationFolderTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε μία μη συσκευασμένη εφαρμογή, συμβατή με το Switch για φόρτωση", + "OpenRyujinxFolderTooltip": "Ανοίξτε το φάκελο συστήματος αρχείων Ryujinx", + "OpenRyujinxLogsTooltip": "Ανοίξτε το φάκελο στον οποίο διατηρούνται τα αρχεία καταγραφής", + "ExitTooltip": "Έξοδος από το Ryujinx", + "OpenSettingsTooltip": "Ανοίξτε το παράθυρο Ρυθμίσεων", + "OpenProfileManagerTooltip": "Ανοίξτε το παράθυρο Διαχείρισης Προφίλ Χρήστη", + "StopEmulationTooltip": "Σταματήστε την εξομοίωση του τρέχοντος παιχνιδιού και επιστρέψτε στην επιλογή παιχνιδιού", + "CheckUpdatesTooltip": "Ελέγξτε για ενημερώσεις του Ryujinx", + "OpenAboutTooltip": "Ανοίξτε το Παράθυρο Σχετικά", + "GridSize": "Μέγεθος Πλέγματος", + "GridSizeTooltip": "Αλλαγή μεγέθους στοιχείων πλέγματος", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Πορτογαλικά Βραζιλίας", + "AboutRyujinxContributorsButtonHeader": "Δείτε Όλους τους Συντελεστές", + "SettingsTabSystemAudioVolume": "Ενταση Ήχου: ", + "AudioVolumeTooltip": "Αλλαγή Έντασης Ήχου", + "SettingsTabSystemEnableInternetAccess": "Ενεργοποίηση πρόσβασης επισκέπτη στο Διαδίκτυο", + "EnableInternetAccessTooltip": "Επιτρέπει την πρόσβαση επισκέπτη στο Διαδίκτυο. Εάν ενεργοποιηθεί, η εξομοιωμένη κονσόλα Switch θα συμπεριφέρεται σαν να είναι συνδεδεμένη στο Διαδίκτυο. Λάβετε υπόψη ότι σε ορισμένες περιπτώσεις, οι εφαρμογές ενδέχεται να εξακολουθούν να έχουν πρόσβαση στο Διαδίκτυο, ακόμη και όταν αυτή η επιλογή είναι απενεργοποιημένη", + "GameListContextMenuManageCheatToolTip": "Διαχείριση Κόλπων", + "GameListContextMenuManageCheat": "Διαχείριση Κόλπων", + "ControllerSettingsStickRange": "Εύρος:", + "DialogStopEmulationTitle": "Ryujinx - Διακοπή εξομοίωσης", + "DialogStopEmulationMessage": "Είστε βέβαιοι ότι θέλετε να σταματήσετε την εξομοίωση;", + "SettingsTabCpu": "Επεξεργαστής", + "SettingsTabAudio": "Ήχος", + "SettingsTabNetwork": "Δίκτυο", + "SettingsTabNetworkConnection": "Σύνδεση δικτύου", + "SettingsTabCpuCache": "Προσωρινή Μνήμη CPU", + "SettingsTabCpuMemory": "Μνήμη CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Παρακαλούμε ενημερώστε το Ryujinx μέσω FlatHub.", + "UpdaterDisabledWarningTitle": "Ο Διαχειριστής Ενημερώσεων Είναι Απενεργοποιημένος!", + "GameListContextMenuOpenSdModsDirectory": "Άνοιγμα Της Τοποθεσίας Των Atmosphere Mods", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Ανοίγει τον εναλλακτικό SD card Atmosphere κατάλογο που περιέχει Mods για το Application. Χρήσιμο για mods τα οποία συσκευάστηκαν για την πραγματική κονσόλα.", + "ControllerSettingsRotate90": "Περιστροφή 90° Δεξιόστροφα", + "IconSize": "Μέγεθος Εικονιδίου", + "IconSizeTooltip": "Αλλάξτε μέγεθος εικονιδίων των παιχνιδιών", + "MenuBarOptionsShowConsole": "Εμφάνιση Κονσόλας", + "ShaderCachePurgeError": "Σφάλμα κατά την εκκαθάριση του shader cache στο {0}: {1}", + "UserErrorNoKeys": "Τα κλειδιά δεν βρέθηκαν", + "UserErrorNoFirmware": "Το firmware δε βρέθηκε", + "UserErrorFirmwareParsingFailed": "Σφάλμα ανάλυσης firmware", + "UserErrorApplicationNotFound": "Η εφαρμογή δε βρέθηκε", + "UserErrorUnknown": "Άγνωστο σφάλμα", + "UserErrorUndefined": "Αόριστο σφάλμα", + "UserErrorNoKeysDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει το αρχείο 'prod.keys'", + "UserErrorNoFirmwareDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει κανένα εγκατεστημένο firmware", + "UserErrorFirmwareParsingFailedDescription": "Το Ryujinx δεν κατάφερε να αναλύσει το συγκεκριμένο firmware. Αυτό συνήθως οφείλετε σε ξεπερασμένα/παλιά κλειδιά.", + "UserErrorApplicationNotFoundDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει έγκυρη εφαρμογή στη συγκεκριμένη διαδρομή.", + "UserErrorUnknownDescription": "Παρουσιάστηκε άγνωστο σφάλμα.", + "UserErrorUndefinedDescription": "Παρουσιάστηκε ένα άγνωστο σφάλμα! Αυτό δεν πρέπει να συμβεί, παρακαλώ επικοινωνήστε με έναν προγραμματιστή!", + "OpenSetupGuideMessage": "Ανοίξτε τον Οδηγό Εγκατάστασης.", + "NoUpdate": "Καμία Eνημέρωση", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Πληροφορίες", + "RyujinxConfirm": "Ryujinx - Επιβεβαίωση", + "FileDialogAllTypes": "Όλοι οι τύποι", + "Never": "Ποτέ", + "SwkbdMinCharacters": "Πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρες", + "SwkbdMinRangeCharacters": "Πρέπει να έχει μήκος {0}-{1} χαρακτήρες", + "SoftwareKeyboard": "Εικονικό Πληκτρολόγιο", + "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "DialogControllerAppletMessagePlayerRange": "Η εφαρμογή ζητά {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", + "DialogControllerAppletMessage": "Η εφαρμογή ζητά ακριβώς {0} παίκτη(ες) με:\n\nΤΥΠΟΥΣ: {1}\n\nΠΑΙΚΤΕΣ: {2}\n\n{3}Παρακαλώ ανοίξτε τις ρυθμίσεις και αλλάξτε τις Ρυθμίσεις εισαγωγής τώρα ή πατήστε Κλείσιμο.", + "DialogControllerAppletDockModeSet": "Η κατάσταση Docked ενεργοποιήθηκε. Το συσκευή παλάμης είναι επίσης μην αποδεκτό.", + "UpdaterRenaming": "Μετονομασία Παλαιών Αρχείων...", + "UpdaterRenameFailed": "Δεν ήταν δυνατή η μετονομασία του αρχείου: {0}", + "UpdaterAddingFiles": "Προσθήκη Νέων Αρχείων...", + "UpdaterExtracting": "Εξαγωγή Ενημέρωσης...", + "UpdaterDownloading": "Λήψη Ενημέρωσης...", + "Game": "Παιχνίδι", + "Docked": "Προσκολλημένο", + "Handheld": "Χειροκίνητο", + "ConnectionError": "Σφάλμα Σύνδεσης.", + "AboutPageDeveloperListMore": "{0} και περισσότερα...", + "ApiError": "Σφάλμα API.", + "LoadingHeading": "Φόρτωση {0}", + "CompilingPPTC": "Μεταγλώττιση του PTC", + "CompilingShaders": "Σύνταξη των Shaders", + "AllKeyboards": "Όλα τα πληκτρολόγια", + "OpenFileDialogTitle": "Επιλέξτε ένα υποστηριζόμενο αρχείο για άνοιγμα", + "OpenFolderDialogTitle": "Επιλέξτε ένα φάκελο με ένα αποσυμπιεσμένο παιχνίδι", + "AllSupportedFormats": "Όλες Οι Υποστηριζόμενες Μορφές", + "RyujinxUpdater": "Ryujinx Ενημερωτής", + "SettingsTabHotkeys": "Συντομεύσεις Πληκτρολογίου", + "SettingsTabHotkeysHotkeys": "Συντομεύσεις Πληκτρολογίου", + "SettingsTabHotkeysToggleVsyncHotkey": "Εναλλαγή VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Στιγμιότυπο Οθόνης:", + "SettingsTabHotkeysShowUiHotkey": "Εμφάνιση Διεπαφής Χρήστη:", + "SettingsTabHotkeysPauseHotkey": "Παύση:", + "SettingsTabHotkeysToggleMuteHotkey": "Σίγαση:", + "ControllerMotionTitle": "Ρυθμίσεις Ελέγχου Κίνησης", + "ControllerRumbleTitle": "Ρυθμίσεις Δόνησης", + "SettingsSelectThemeFileDialogTitle": "Επιλογή Αρχείου Θέματος", + "SettingsXamlThemeFile": "Αρχείο Θέματος Xaml", + "AvatarWindowTitle": "Διαχείριση Λογαριασμών - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Άγνωστο", + "Usage": "Χρήση", + "Writable": "Εγγράψιμο", + "SelectDlcDialogTitle": "Επιλογή αρχείων DLC", + "SelectUpdateDialogTitle": "Επιλογή αρχείων ενημέρωσης", + "UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη", + "CheatWindowTitle": "Διαχειριστής των Cheats", + "DlcWindowTitle": "Downloadable Content Manager", + "UpdateWindowTitle": "Διαχειριστής Ενημερώσεων Τίτλου", + "CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "UserProfilesEditProfile": "Επεξεργασία Επιλεγμένων", + "Cancel": "Ακύρωση", + "Save": "Αποθήκευση", + "Discard": "Απόρριψη", + "UserProfilesSetProfileImage": "Ορισμός Εικόνας Προφίλ", + "UserProfileEmptyNameError": "Απαιτείται όνομα", + "UserProfileNoImageError": "Η εικόνα προφίλ πρέπει να οριστεί", + "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Αύξηση της ανάλυσης:", + "SettingsTabHotkeysResScaleDownHotkey": "Μείωση της ανάλυσης:", + "UserProfilesName": "Όνομα:", + "UserProfilesUserId": "User Id:", + "SettingsTabGraphicsBackend": "Σύστημα Υποστήριξης Γραφικών", + "SettingsTabGraphicsBackendTooltip": "Backend Γραφικών που θα χρησιμοποιηθεί", + "SettingsEnableTextureRecompression": "Ενεργοποίηση Επανασυμπίεσης Των Texture", + "SettingsEnableTextureRecompressionTooltip": "Συμπιέζει συγκεκριμένα texture για να μειωθεί η χρήση της VRAM.\nΣυνίσταται για χρήση σε κάρτες γραφικών με λιγότερο από 4 GiB VRAM.\nΑφήστε το Απενεργοποιημένο αν είστε αβέβαιοι.", + "SettingsTabGraphicsPreferredGpu": "Προτιμώμενη GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Επιλέξτε την κάρτα γραφικών η οποία θα χρησιμοποιηθεί από το Vulkan.\n\nΔεν επηρεάζει το OpenGL.\n\nΔιαλέξτε την GPU που διαθέτει την υπόδειξη \"dGPU\" αν δεν είστε βέβαιοι. Αν δεν υπάρχει κάποιαν, το πειράξετε", + "SettingsAppRequiredRestartMessage": "Απαιτείται Επανεκκίνηση Του Ryujinx", + "SettingsGpuBackendRestartMessage": "Οι ρυθμίσεις GPU έχουν αλλαχτεί. Θα χρειαστεί επανεκκίνηση του Ryujinx για να τεθούν σε ισχύ.", + "SettingsGpuBackendRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση τώρα;", + "RyujinxUpdaterMessage": "Θέλετε να ενημερώσετε το Ryujinx στην πιο πρόσφατη έκδοση:", + "SettingsTabHotkeysVolumeUpHotkey": "Αύξηση Έντασης:", + "SettingsTabHotkeysVolumeDownHotkey": "Μείωση Έντασης:", + "SettingsEnableMacroHLE": "Ενεργοποίηση του Macro HLE", + "SettingsEnableMacroHLETooltip": "Προσομοίωση του κώδικα GPU Macro .\n\nΒελτιώνει την απόδοση, αλλά μπορεί να προκαλέσει γραφικά προβλήματα σε μερικά παιχνίδια.\n\nΑφήστε ΕΝΕΡΓΟ αν δεν είστε σίγουροι.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Έντ.", + "UserProfilesManageSaves": "Διαχείριση Των Save", + "DeleteUserSave": "Επιθυμείτε να διαγράψετε το save χρήστη για το συγκεκριμένο παιχνίδι;", + "IrreversibleActionNote": "Αυτή η ενέργεια είναι μη αναστρέψιμη.", + "SaveManagerHeading": "Manage Saves for {0}", + "SaveManagerTitle": "Διαχειριστής Save", + "Name": "Όνομα", + "Size": "Μέγεθος", + "Search": "Αναζήτηση", + "UserProfilesRecoverLostAccounts": "Ανάκτηση Χαμένων Λογαριασμών", + "Recover": "Ανάκτηση", + "UserProfilesRecoverHeading": "Βρέθηκαν save για τους ακόλουθους λογαριασμούς", + "UserProfilesRecoverEmptyList": "Δεν υπάρχουν προφίλ για ανάκτηση", + "GraphicsAATooltip": "Εφαρμόζει anti-aliasing στην απόδοση του παιχνιδιού", + "GraphicsAALabel": "Anti-Aliasing", + "GraphicsScalingFilterLabel": "Φίλτρο Κλιμάκωσης:", + "GraphicsScalingFilterTooltip": "Ενεργοποιεί Κλίμακα Framebuffer", + "GraphicsScalingFilterLevelLabel": "Επίπεδο", + "GraphicsScalingFilterLevelTooltip": "Ορισμός Επιπέδου Φίλτρου Κλιμάκωσης", + "SmaaLow": "Χαμηλό SMAA", + "SmaaMedium": " Μεσαίο SMAA", + "SmaaHigh": "Υψηλό SMAA", + "SmaaUltra": "Oύλτρα SMAA", + "UserEditorTitle": "Επεξεργασία Χρήστη", + "UserEditorTitleCreate": "Δημιουργία Χρήστη", + "SettingsTabNetworkInterface": "Διεπαφή Δικτύου", + "NetworkInterfaceTooltip": "Η διεπαφή δικτύου που χρησιμοποιείται για τα χαρακτηριστικά LAN", + "NetworkInterfaceDefault": "Προεπιλογή", + "PackagingShaders": "Shaders Συσκευασίας", + "AboutChangelogButton": "View Changelog on GitHub", + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json new file mode 100644 index 000000000..493aaa81f --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -0,0 +1,662 @@ +{ + "Language": "English (US)", + "MenuBarFileOpenApplet": "Open Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Open Mii Editor Applet in Standalone mode", + "SettingsTabInputDirectMouseAccess": "Direct Mouse Access", + "SettingsTabSystemMemoryManagerMode": "Memory Manager Mode:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (fast)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)", + "SettingsTabSystemUseHypervisor": "Use Hypervisor", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Load Application From File", + "MenuBarFileOpenUnpacked": "Load _Unpacked Game", + "MenuBarFileOpenEmuFolder": "Open Ryujinx Folder", + "MenuBarFileOpenLogsFolder": "Open Logs Folder", + "MenuBarFileExit": "_Exit", + "MenuBarOptions": "_Options", + "MenuBarOptionsToggleFullscreen": "Toggle Fullscreen", + "MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode", + "MenuBarOptionsStopEmulation": "Stop Emulation", + "MenuBarOptionsSettings": "_Settings", + "MenuBarOptionsManageUserProfiles": "_Manage User Profiles", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simulate Wake-up message", + "MenuBarActionsScanAmiibo": "Scan An Amiibo", + "MenuBarTools": "_Tools", + "MenuBarToolsInstallFirmware": "Install Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory", + "MenuBarToolsManageFileTypes": "Manage file types", + "MenuBarToolsInstallFileTypes": "Install file types", + "MenuBarToolsUninstallFileTypes": "Uninstall file types", + "MenuBarHelp": "_Help", + "MenuBarHelpCheckForUpdates": "Check for Updates", + "MenuBarHelpAbout": "About", + "MenuSearch": "Search...", + "GameListHeaderFavorite": "Fav", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Developer", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Play Time", + "GameListHeaderLastPlayed": "Last Played", + "GameListHeaderFileExtension": "File Ext", + "GameListHeaderFileSize": "File Size", + "GameListHeaderPath": "Path", + "GameListContextMenuOpenUserSaveDirectory": "Open User Save Directory", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Opens the directory which contains Application's User Save", + "GameListContextMenuOpenDeviceSaveDirectory": "Open Device Save Directory", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Opens the directory which contains Application's Device Save", + "GameListContextMenuOpenBcatSaveDirectory": "Open BCAT Save Directory", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Opens the directory which contains Application's BCAT Save", + "GameListContextMenuManageTitleUpdates": "Manage Title Updates", + "GameListContextMenuManageTitleUpdatesToolTip": "Opens the Title Update management window", + "GameListContextMenuManageDlc": "Manage DLC", + "GameListContextMenuManageDlcToolTip": "Opens the DLC management window", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuCacheManagement": "Cache Management", + "GameListContextMenuCacheManagementPurgePptc": "Queue PPTC Rebuild", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Trigger PPTC to rebuild at boot time on the next game launch", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purge Shader Cache", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deletes Application's shader cache", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Open PPTC Directory", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Opens the directory which contains Application's PPTC cache", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Open Shader Cache Directory", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Opens the directory which contains Application's shader cache", + "GameListContextMenuExtractData": "Extract Data", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extract the ExeFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)", + "GameListContextMenuCreateShortcut": "Create Application Shortcut", + "GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application", + "StatusBarGamesLoaded": "{0}/{1} Games Loaded", + "StatusBarSystemVersion": "System Version: {0}", + "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", + "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", + "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", + "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "Settings": "Settings", + "SettingsTabGeneral": "User Interface", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch", + "SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog", + "SettingsTabGeneralHideCursor": "Hide Cursor:", + "SettingsTabGeneralHideCursorNever": "Never", + "SettingsTabGeneralHideCursorOnIdle": "On Idle", + "SettingsTabGeneralHideCursorAlways": "Always", + "SettingsTabGeneralGameDirectories": "Game Directories", + "SettingsTabGeneralAdd": "Add", + "SettingsTabGeneralRemove": "Remove", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Core", + "SettingsTabSystemSystemRegion": "System Region:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "System Language:", + "SettingsTabSystemSystemLanguageJapanese": "Japanese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "American English", + "SettingsTabSystemSystemLanguageFrench": "French", + "SettingsTabSystemSystemLanguageGerman": "German", + "SettingsTabSystemSystemLanguageItalian": "Italian", + "SettingsTabSystemSystemLanguageSpanish": "Spanish", + "SettingsTabSystemSystemLanguageChinese": "Chinese", + "SettingsTabSystemSystemLanguageKorean": "Korean", + "SettingsTabSystemSystemLanguageDutch": "Dutch", + "SettingsTabSystemSystemLanguagePortuguese": "Portuguese", + "SettingsTabSystemSystemLanguageRussian": "Russian", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "British English", + "SettingsTabSystemSystemLanguageCanadianFrench": "Canadian French", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin American Spanish", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Simplified Chinese", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese", + "SettingsTabSystemSystemTimeZone": "System TimeZone:", + "SettingsTabSystemSystemTime": "System Time:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks", + "SettingsTabSystemAudioBackend": "Audio Backend:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": "May cause instability", + "SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)", + "SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services", + "SettingsTabGraphics": "Graphics", + "SettingsTabGraphicsAPI": "Graphics API", + "SettingsTabGraphicsEnableShaderCache": "Enable Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotropic Filtering:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Resolution Scale:", + "SettingsTabGraphicsResolutionScaleCustom": "Custom (Not recommended)", + "SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Aspect Ratio:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Stretch to Fit Window", + "SettingsTabGraphicsDeveloperOptions": "Developer Options", + "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", + "SettingsTabLogging": "Logging", + "SettingsTabLoggingLogging": "Logging", + "SettingsTabLoggingEnableLoggingToFile": "Enable Logging to File", + "SettingsTabLoggingEnableStubLogs": "Enable Stub Logs", + "SettingsTabLoggingEnableInfoLogs": "Enable Info Logs", + "SettingsTabLoggingEnableWarningLogs": "Enable Warning Logs", + "SettingsTabLoggingEnableErrorLogs": "Enable Error Logs", + "SettingsTabLoggingEnableTraceLogs": "Enable Trace Logs", + "SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", + "SettingsTabLoggingDeveloperOptions": "Developer Options", + "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", + "SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "None", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Error", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Slowdowns", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "All", + "SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access", + "SettingsButtonSave": "Save", + "SettingsButtonClose": "Close", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancel", + "SettingsButtonApply": "Apply", + "ControllerSettingsPlayer": "Player", + "ControllerSettingsPlayer1": "Player 1", + "ControllerSettingsPlayer2": "Player 2", + "ControllerSettingsPlayer3": "Player 3", + "ControllerSettingsPlayer4": "Player 4", + "ControllerSettingsPlayer5": "Player 5", + "ControllerSettingsPlayer6": "Player 6", + "ControllerSettingsPlayer7": "Player 7", + "ControllerSettingsPlayer8": "Player 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Input Device", + "ControllerSettingsRefresh": "Refresh", + "ControllerSettingsDeviceDisabled": "Disabled", + "ControllerSettingsControllerType": "Controller Type", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Pair", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Left", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Right", + "ControllerSettingsProfile": "Profile", + "ControllerSettingsProfileDefault": "Default", + "ControllerSettingsLoad": "Load", + "ControllerSettingsAdd": "Add", + "ControllerSettingsRemove": "Remove", + "ControllerSettingsButtons": "Buttons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Directional Pad", + "ControllerSettingsDPadUp": "Up", + "ControllerSettingsDPadDown": "Down", + "ControllerSettingsDPadLeft": "Left", + "ControllerSettingsDPadRight": "Right", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Up", + "ControllerSettingsStickDown": "Down", + "ControllerSettingsStickLeft": "Left", + "ControllerSettingsStickRight": "Right", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Invert Stick X", + "ControllerSettingsStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Left Stick", + "ControllerSettingsRStick": "Right Stick", + "ControllerSettingsTriggersLeft": "Triggers Left", + "ControllerSettingsTriggersRight": "Triggers Right", + "ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left", + "ControllerSettingsTriggersButtonsRight": "Trigger Buttons Right", + "ControllerSettingsTriggers": "Triggers", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Buttons Left", + "ControllerSettingsExtraButtonsRight": "Buttons Right", + "ControllerSettingsMisc": "Miscellaneous", + "ControllerSettingsTriggerThreshold": "Trigger Threshold:", + "ControllerSettingsMotion": "Motion", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Use CemuHook compatible motion", + "ControllerSettingsMotionControllerSlot": "Controller Slot:", + "ControllerSettingsMotionMirrorInput": "Mirror Input", + "ControllerSettingsMotionRightJoyConSlot": "Right JoyCon Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Sensitivity:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", + "ControllerSettingsSave": "Save", + "ControllerSettingsClose": "Close", + "UserProfilesSelectedUserProfile": "Selected User Profile:", + "UserProfilesSaveProfileName": "Save Profile Name", + "UserProfilesChangeProfileImage": "Change Profile Image", + "UserProfilesAvailableUserProfiles": "Available User Profiles:", + "UserProfilesAddNewProfile": "Create Profile", + "UserProfilesDelete": "Delete", + "UserProfilesClose": "Close", + "ProfileNameSelectionWatermark": "Choose a nickname", + "ProfileImageSelectionTitle": "Profile Image Selection", + "ProfileImageSelectionHeader": "Choose a profile Image", + "ProfileImageSelectionNote": "You may import a custom profile image, or select an avatar from system firmware", + "ProfileImageSelectionImportImage": "Import Image File", + "ProfileImageSelectionSelectAvatar": "Select Firmware Avatar", + "InputDialogTitle": "Input Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancel", + "InputDialogAddNewProfileTitle": "Choose the Profile Name", + "InputDialogAddNewProfileHeader": "Please Enter a Profile Name", + "InputDialogAddNewProfileSubtext": "(Max Length: {0})", + "AvatarChoose": "Choose Avatar", + "AvatarSetBackgroundColor": "Set Background Color", + "AvatarClose": "Close", + "ControllerSettingsLoadProfileToolTip": "Load Profile", + "ControllerSettingsAddProfileToolTip": "Add Profile", + "ControllerSettingsRemoveProfileToolTip": "Remove Profile", + "ControllerSettingsSaveProfileToolTip": "Save Profile", + "MenuBarFileToolsTakeScreenshot": "Take Screenshot", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuToggleFavorite": "Toggle Favorite", + "GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game", + "SettingsTabGeneralTheme": "Theme", + "SettingsTabGeneralThemeCustomTheme": "Custom Theme Path", + "SettingsTabGeneralThemeBaseStyle": "Base Style", + "SettingsTabGeneralThemeBaseStyleDark": "Dark", + "SettingsTabGeneralThemeBaseStyleLight": "Light", + "SettingsTabGeneralThemeEnableCustomTheme": "Enable Custom Theme", + "ButtonBrowse": "Browse", + "ControllerSettingsConfigureGeneral": "Configure", + "ControllerSettingsRumble": "Rumble", + "ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier", + "ControllerSettingsRumbleWeakMultiplier": "Weak Rumble Multiplier", + "DialogMessageSaveNotAvailableMessage": "There is no savedata for {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Would you like to create savedata for this game?", + "DialogConfirmationTitle": "Ryujinx - Confirmation", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Warning", + "DialogExitTitle": "Ryujinx - Exit", + "DialogErrorMessage": "Ryujinx has encountered an error", + "DialogExitMessage": "Are you sure you want to close Ryujinx?", + "DialogExitSubMessage": "All unsaved data will be lost!", + "DialogMessageCreateSaveErrorMessage": "There was an error creating the specified savedata: {0}", + "DialogMessageFindSaveErrorMessage": "There was an error finding the specified savedata: {0}", + "FolderDialogExtractTitle": "Choose the folder to extract into", + "DialogNcaExtractionMessage": "Extracting {0} section from {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Section Extractor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraction failure. The main NCA was not present in the selected file.", + "DialogNcaExtractionCheckLogErrorMessage": "Extraction failure. Read the log file for further information.", + "DialogNcaExtractionSuccessMessage": "Extraction completed successfully.", + "DialogUpdaterConvertFailedMessage": "Failed to convert the current Ryujinx version.", + "DialogUpdaterCancelUpdateMessage": "Cancelling Update!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the most updated version of Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Failed to convert the received Ryujinx version from Github Release.", + "DialogUpdaterDownloadingMessage": "Downloading Update...", + "DialogUpdaterExtractionMessage": "Extracting Update...", + "DialogUpdaterRenamingMessage": "Renaming Update...", + "DialogUpdaterAddingFilesMessage": "Adding New Update...", + "DialogUpdaterCompleteMessage": "Update Complete!", + "DialogUpdaterRestartMessage": "Do you want to restart Ryujinx now?", + "DialogUpdaterArchNotSupportedMessage": "You are not running a supported system architecture!", + "DialogUpdaterArchNotSupportedSubMessage": "(Only x64 systems are supported!)", + "DialogUpdaterNoInternetMessage": "You are not connected to the Internet!", + "DialogUpdaterNoInternetSubMessage": "Please verify that you have a working Internet connection!", + "DialogUpdaterDirtyBuildMessage": "You Cannot update a Dirty build of Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.", + "DialogRestartRequiredMessage": "Restart Required", + "DialogThemeRestartMessage": "Theme has been saved. A restart is needed to apply the theme.", + "DialogThemeRestartSubMessage": "Do you want to restart", + "DialogFirmwareInstallEmbeddedMessage": "Would you like to install the firmware embedded in this game? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.", + "DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed", + "DialogFirmwareInstalledMessage": "Firmware {0} was installed", + "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", + "DialogInstallFileTypesErrorMessage": "Failed to install file types.", + "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", + "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", + "DialogOpenSettingsWindowLabel": "Open Settings Window", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error displaying Software Keyboard: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nFor more information on how to fix this error, follow our Setup Guide.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "An error occured while fetching information from the API.", + "DialogAmiiboApiConnectErrorMessage": "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.", + "DialogProfileInvalidProfileErrorMessage": "Profile {0} is incompatible with the current input configuration system.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Default Profile can not be overwritten", + "DialogProfileDeleteProfileTitle": "Deleting Profile", + "DialogProfileDeleteProfileMessage": "This action is irreversible, are you sure you want to continue?", + "DialogWarning": "Warning", + "DialogPPTCDeletionMessage": "You are about to queue a PPTC rebuild on the next boot of:\n\n{0}\n\nAre you sure you want to proceed?", + "DialogPPTCDeletionErrorMessage": "Error purging PPTC cache at {0}: {1}", + "DialogShaderDeletionMessage": "You are about to delete the Shader cache for :\n\n{0}\n\nAre you sure you want to proceed?", + "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx has encountered an error", + "DialogInvalidTitleIdErrorMessage": "UI error: The selected game did not have a valid title ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "A valid system firmware was not found in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Install Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "System version {0} will be installed.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nThis will replace the current system version {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDo you want to continue?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installing firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.", + "DialogUserProfileDeletionWarningMessage": "There would be no other profiles to be opened if selected profile is deleted", + "DialogUserProfileDeletionConfirmMessage": "Do you want to delete the selected profile", + "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", + "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", + "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", + "DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?", + "DialogLoadNcaErrorMessage": "{0}. Errored File: {1}", + "DialogDlcNoDlcErrorMessage": "The specified file does not contain a DLC for the selected title!", + "DialogPerformanceCheckLoggingEnabledMessage": "You have trace logging enabled, which is designed to be used by developers only.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "You have shader dumping enabled, which is designed to be used by developers only.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + "DialogLoadAppGameAlreadyLoadedMessage": "A game has already been loaded", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Please stop emulation or close the emulator before launching another game.", + "DialogUpdateAddUpdateErrorMessage": "The specified file does not contain an update for the selected title!", + "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", + "SettingsTabGraphicsFeaturesOptions": "Features", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Off", + "CommonOn": "On", + "InputDialogYes": "Yes", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "The file name contains invalid characters. Please try again.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Resume", + "AboutUrlTooltipMessage": "Click to open the Ryujinx website in your default browser.", + "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", + "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", + "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", + "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", + "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", + "AboutRyujinxAboutTitle": "About:", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", + "AboutRyujinxMaintainersTitle": "Maintained By:", + "AboutRyujinxMaintainersContentTooltipMessage": "Click to open the Contributors page in your default browser.", + "AboutRyujinxSupprtersTitle": "Supported on Patreon By:", + "AmiiboSeriesLabel": "Amiibo Series", + "AmiiboCharacterLabel": "Character", + "AmiiboScanButtonLabel": "Scan It", + "AmiiboOptionsShowAllLabel": "Show All Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", + "DlcManagerTableHeadingEnabledLabel": "Enabled", + "DlcManagerTableHeadingTitleIdLabel": "Title ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Path", + "DlcManagerTableHeadingFullPathLabel": "Full Path", + "DlcManagerRemoveAllButton": "Remove All", + "DlcManagerEnableAllButton": "Enable All", + "DlcManagerDisableAllButton": "Disable All", + "MenuBarOptionsChangeLanguage": "Change Language", + "MenuBarShowFileTypes": "Show File Types", + "CommonSort": "Sort", + "CommonShowNames": "Show Names", + "CommonFavorite": "Favorite", + "OrderAscending": "Ascending", + "OrderDescending": "Descending", + "SettingsTabGraphicsFeatures": "Features & Enhancements", + "ErrorWindowTitle": "Error Window", + "ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity", + "AddGameDirBoxTooltip": "Enter a game directory to add to the list", + "AddGameDirTooltip": "Add a game directory to the list", + "RemoveGameDirTooltip": "Remove selected game directory", + "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", + "CustomThemePathTooltip": "Path to custom GUI theme", + "CustomThemeBrowseTooltip": "Browse for a custom GUI theme", + "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.", + "RegionTooltip": "Change System Region", + "LanguageTooltip": "Change System Language", + "TimezoneTooltip": "Change System TimeZone", + "TimeTooltip": "Change System Time", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", + "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", + "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", + "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", + "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", + "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", + "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", + "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", + "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", + "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", + "ResolutionScaleTooltip": "Resolution Scale applied to applicable render targets", + "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", + "AnisotropyTooltip": "Level of Anisotropic Filtering (set to Auto to use the value requested by the game)", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.", + "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", + "FileLogTooltip": "Saves console logging to a log file on disk. Does not affect performance.", + "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", + "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.", + "WarnLogTooltip": "Prints warning log messages in the console. Does not affect performance.", + "ErrorLogTooltip": "Prints error log messages in the console. Does not affect performance.", + "TraceLogTooltip": "Prints trace log messages in the console. Does not affect performance.", + "GuestLogTooltip": "Prints guest log messages in the console. Does not affect performance.", + "FileAccessLogTooltip": "Prints file access log messages in the console.", + "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "DeveloperOptionTooltip": "Use with care", + "OpenGlLogLevel": "Requires appropriate log levels enabled", + "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", + "LoadApplicationFileTooltip": "Open a file explorer to choose a Switch compatible file to load", + "LoadApplicationFolderTooltip": "Open a file explorer to choose a Switch compatible, unpacked application to load", + "OpenRyujinxFolderTooltip": "Open Ryujinx filesystem folder", + "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", + "ExitTooltip": "Exit Ryujinx", + "OpenSettingsTooltip": "Open settings window", + "OpenProfileManagerTooltip": "Open User Profiles Manager window", + "StopEmulationTooltip": "Stop emulation of the current game and return to game selection", + "CheckUpdatesTooltip": "Check for updates to Ryujinx", + "OpenAboutTooltip": "Open About Window", + "GridSize": "Grid Size", + "GridSizeTooltip": "Change the size of grid items", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazilian Portuguese", + "AboutRyujinxContributorsButtonHeader": "See All Contributors", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Change Audio Volume", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode", + "EnableInternetAccessTooltip": "Allows the emulated application to connect to the Internet.\n\nGames with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well.\n\nDoes NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet.\n\nLeave OFF if unsure.", + "GameListContextMenuManageCheatToolTip": "Manage Cheats", + "GameListContextMenuManageCheat": "Manage Cheats", + "ControllerSettingsStickRange": "Range:", + "DialogStopEmulationTitle": "Ryujinx - Stop Emulation", + "DialogStopEmulationMessage": "Are you sure you want to stop emulation?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Network", + "SettingsTabNetworkConnection": "Network Connection", + "SettingsTabCpuCache": "CPU Cache", + "SettingsTabCpuMemory": "CPU Mode", + "DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Updater Disabled!", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "ControllerSettingsRotate90": "Rotate 90° Clockwise", + "IconSize": "Icon Size", + "IconSizeTooltip": "Change the size of game icons", + "MenuBarOptionsShowConsole": "Show Console", + "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "UserErrorNoKeys": "Keys not found", + "UserErrorNoFirmware": "Firmware not found", + "UserErrorFirmwareParsingFailed": "Firmware parsing error", + "UserErrorApplicationNotFound": "Application not found", + "UserErrorUnknown": "Unknown error", + "UserErrorUndefined": "Undefined error", + "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", + "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorUnknownDescription": "An unknown error occured!", + "UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!", + "OpenSetupGuideMessage": "Open the Setup Guide", + "NoUpdate": "No Update", + "TitleUpdateVersionLabel": "Version {0}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "All types", + "Never": "Never", + "SwkbdMinCharacters": "Must be at least {0} characters long", + "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SoftwareKeyboard": "Software Keyboard", + "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n", + "UpdaterRenaming": "Renaming Old Files...", + "UpdaterRenameFailed": "Updater was unable to rename file: {0}", + "UpdaterAddingFiles": "Adding New Files...", + "UpdaterExtracting": "Extracting Update...", + "UpdaterDownloading": "Downloading Update...", + "Game": "Game", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Connection Error.", + "AboutPageDeveloperListMore": "{0} and more...", + "ApiError": "API Error.", + "LoadingHeading": "Loading {0}", + "CompilingPPTC": "Compiling PTC", + "CompilingShaders": "Compiling Shaders", + "AllKeyboards": "All keyboards", + "OpenFileDialogTitle": "Select a supported file to open", + "OpenFolderDialogTitle": "Select a folder with an unpacked game", + "AllSupportedFormats": "All Supported Formats", + "RyujinxUpdater": "Ryujinx Updater", + "SettingsTabHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Show UI:", + "SettingsTabHotkeysPauseHotkey": "Pause:", + "SettingsTabHotkeysToggleMuteHotkey": "Mute:", + "ControllerMotionTitle": "Motion Control Settings", + "ControllerRumbleTitle": "Rumble Settings", + "SettingsSelectThemeFileDialogTitle": "Select Theme File", + "SettingsXamlThemeFile": "Xaml Theme File", + "AvatarWindowTitle": "Manage Accounts - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unknown", + "Usage": "Usage", + "Writable": "Writable", + "SelectDlcDialogTitle": "Select DLC files", + "SelectUpdateDialogTitle": "Select update files", + "UserProfileWindowTitle": "User Profiles Manager", + "CheatWindowTitle": "Cheats Manager", + "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", + "UpdateWindowTitle": "Title Update Manager", + "CheatWindowHeading": "Cheats Available for {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} Downloadable Content(s)", + "UserProfilesEditProfile": "Edit Selected", + "Cancel": "Cancel", + "Save": "Save", + "Discard": "Discard", + "Paused": "Paused", + "UserProfilesSetProfileImage": "Set Profile Image", + "UserProfileEmptyNameError": "Name is required", + "UserProfileNoImageError": "Profile image must be set", + "GameUpdateWindowHeading": "Manage Updates for {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", + "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "User ID:", + "SettingsTabGraphicsBackend": "Graphics Backend", + "SettingsTabGraphicsBackendTooltip": "Graphics Backend to use", + "SettingsEnableTextureRecompression": "Enable Texture Recompression", + "SettingsEnableTextureRecompressionTooltip": "Compresses certain textures in order to reduce VRAM usage.\n\nRecommended for use with GPUs that have less than 4GiB VRAM.\n\nLeave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "Preferred GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", + "SettingsAppRequiredRestartMessage": "Ryujinx Restart Required", + "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsGpuBackendRestartSubMessage": "Do you want to restart now?", + "RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?", + "SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Manage Saves", + "DeleteUserSave": "Do you want to delete user save for this game?", + "IrreversibleActionNote": "This action is not reversible.", + "SaveManagerHeading": "Manage Saves for {0} ({1})", + "SaveManagerTitle": "Save Manager", + "Name": "Name", + "Size": "Size", + "Search": "Search", + "UserProfilesRecoverLostAccounts": "Recover Lost Accounts", + "Recover": "Recover", + "UserProfilesRecoverHeading": "Saves were found for the following accounts", + "UserProfilesRecoverEmptyList": "No profiles to recover", + "GraphicsAATooltip": "Applies anti-aliasing to the game render", + "GraphicsAALabel": "Anti-Aliasing:", + "GraphicsScalingFilterLabel": "Scaling Filter:", + "GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling", + "GraphicsScalingFilterLevelLabel": "Level", + "GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level", + "SmaaLow": "SMAA Low", + "SmaaMedium": "SMAA Medium", + "SmaaHigh": "SMAA High", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Edit User", + "UserEditorTitleCreate": "Create User", + "SettingsTabNetworkInterface": "Network Interface:", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features", + "NetworkInterfaceDefault": "Default", + "PackagingShaders": "Packaging Shaders", + "AboutChangelogButton": "View Changelog on GitHub", + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.", + "SettingsTabNetworkMultiplayer": "Multiplayer", + "MultiplayerMode": "Mode:", + "MultiplayerModeTooltip": "Change multiplayer mode" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/es_ES.json b/src/Ryujinx.Ava/Assets/Locales/es_ES.json new file mode 100644 index 000000000..91bcd8f11 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/es_ES.json @@ -0,0 +1,656 @@ +{ + "Language": "Español (ES)", + "MenuBarFileOpenApplet": "Abrir applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo", + "SettingsTabInputDirectMouseAccess": "Acceso directo al ratón", + "SettingsTabSystemMemoryManagerMode": "Modo del administrador de memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host sin verificación (más rápido, inseguro)", + "SettingsTabSystemUseHypervisor": "Usar hipervisor", + "MenuBarFile": "_Archivo", + "MenuBarFileOpenFromFile": "_Cargar aplicación desde un archivo", + "MenuBarFileOpenUnpacked": "Cargar juego _desempaquetado", + "MenuBarFileOpenEmuFolder": "Abrir carpeta de Ryujinx", + "MenuBarFileOpenLogsFolder": "Abrir carpeta de registros", + "MenuBarFileExit": "_Salir", + "MenuBarOptions": "_Opciones", + "MenuBarOptionsToggleFullscreen": "Cambiar a pantalla completa.", + "MenuBarOptionsStartGamesInFullscreen": "Iniciar juegos en pantalla completa", + "MenuBarOptionsStopEmulation": "Detener emulación", + "MenuBarOptionsSettings": "_Configuración", + "MenuBarOptionsManageUserProfiles": "_Gestionar perfiles de usuario", + "MenuBarActions": "_Acciones", + "MenuBarOptionsSimulateWakeUpMessage": "Simular mensaje de reactivación", + "MenuBarActionsScanAmiibo": "Escanear Amiibo", + "MenuBarTools": "_Herramientas", + "MenuBarToolsInstallFirmware": "Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware desde un archivo XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware desde una carpeta", + "MenuBarToolsManageFileTypes": "Administrar tipos de archivo", + "MenuBarToolsInstallFileTypes": "Instalar tipos de archivo", + "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de archivo", + "MenuBarHelp": "Ayuda", + "MenuBarHelpCheckForUpdates": "Buscar actualizaciones", + "MenuBarHelpAbout": "Acerca de", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Favoritos", + "GameListHeaderIcon": "Icono", + "GameListHeaderApplication": "Nombre", + "GameListHeaderDeveloper": "Desarrollador", + "GameListHeaderVersion": "Versión", + "GameListHeaderTimePlayed": "Tiempo jugado", + "GameListHeaderLastPlayed": "Jugado por última vez", + "GameListHeaderFileExtension": "Extensión", + "GameListHeaderFileSize": "Tamaño del archivo", + "GameListHeaderPath": "Directorio", + "GameListContextMenuOpenUserSaveDirectory": "Abrir carpeta de guardado de este usuario", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del usuario para esta aplicación", + "GameListContextMenuOpenDeviceSaveDirectory": "Abrir carpeta de guardado del sistema para el usuario actual", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del sistema para esta aplicación", + "GameListContextMenuOpenBcatSaveDirectory": "Abrir carpeta de guardado BCAT del usuario", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abrir la carpeta que contiene el guardado BCAT de esta aplicación", + "GameListContextMenuManageTitleUpdates": "Gestionar actualizaciones del juego", + "GameListContextMenuManageTitleUpdatesToolTip": "Abrir la ventana de gestión de actualizaciones de esta aplicación", + "GameListContextMenuManageDlc": "Gestionar DLC", + "GameListContextMenuManageDlcToolTip": "Abrir la ventana de gestión del DLC", + "GameListContextMenuOpenModsDirectory": "Abrir carpeta de mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abrir la carpeta que contiene los mods (archivos modificantes) de esta aplicación", + "GameListContextMenuCacheManagement": "Gestión de caché ", + "GameListContextMenuCacheManagementPurgePptc": "Reconstruir PPTC en cola", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpiar caché de sombras", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Eliminar la caché de sombras de esta aplicación", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir carpeta de PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abrir la carpeta que contiene la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombras", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abrir la carpeta que contiene la caché de sombras de esta aplicación", + "GameListContextMenuExtractData": "Extraer datos", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extraer la sección ExeFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extraer la sección RomFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataLogo": "Logotipo", + "GameListContextMenuExtractDataLogoToolTip": "Extraer la sección Logo de la configuración actual de la aplicación (incluyendo actualizaciones)", + "StatusBarGamesLoaded": "{0}/{1} juegos cargados", + "StatusBarSystemVersion": "Versión del sistema: {0}", + "LinuxVmMaxMapCountDialogTitle": "Límite inferior para mapeos de memoria detectado", + "LinuxVmMaxMapCountDialogTextPrimary": "¿Quieres aumentar el valor de vm.max_map_count a {0}?", + "LinuxVmMaxMapCountDialogTextSecondary": "Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos. Ryujinx se bloqueará tan pronto como se supere este límite.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sí, hasta el próximo reinicio", + "LinuxVmMaxMapCountDialogButtonPersistent": "Si, permanentemente", + "LinuxVmMaxMapCountWarningTextPrimary": "La cantidad máxima de mapeos de memoria es menor de lo recomendado.", + "LinuxVmMaxMapCountWarningTextSecondary": "El valor actual de vm.max_map_count ({0}) es menor que {1}. Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos actualmente. Ryujinx se bloqueará tan pronto como se supere este límite.\n\nPuede que desee aumentar manualmente el límite o instalar pkexec, lo que permite a Ryujinx ayudar con eso.", + "Settings": "Configuración", + "SettingsTabGeneral": "Interfaz de usuario", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar estado en Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Buscar actualizaciones al iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Mostrar diálogo de confirmación al cerrar", + "SettingsTabGeneralHideCursor": "Esconder el cursor:", + "SettingsTabGeneralHideCursorNever": "Nunca", + "SettingsTabGeneralHideCursorOnIdle": "Ocultar cursor cuando esté inactivo", + "SettingsTabGeneralHideCursorAlways": "Siempre", + "SettingsTabGeneralGameDirectories": "Carpetas de juegos", + "SettingsTabGeneralAdd": "Agregar", + "SettingsTabGeneralRemove": "Quitar", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Núcleo", + "SettingsTabSystemSystemRegion": "Región del sistema:", + "SettingsTabSystemSystemRegionJapan": "Japón", + "SettingsTabSystemSystemRegionUSA": "EEUU", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwán", + "SettingsTabSystemSystemLanguage": "Idioma del sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonés", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglés americano", + "SettingsTabSystemSystemLanguageFrench": "Francés", + "SettingsTabSystemSystemLanguageGerman": "Alemán", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Español", + "SettingsTabSystemSystemLanguageChinese": "Chino", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Neerlandés/Holandés", + "SettingsTabSystemSystemLanguagePortuguese": "Portugués", + "SettingsTabSystemSystemLanguageRussian": "Ruso", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanés", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglés británico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francés canadiense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Español latinoamericano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chino simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chino tradicional", + "SettingsTabSystemSystemTimeZone": "Zona horaria del sistema:", + "SettingsTabSystemSystemTime": "Hora del sistema:", + "SettingsTabSystemEnableVsync": "Sincronización vertical", + "SettingsTabSystemEnablePptc": "PPTC (Cache de Traducción de Perfil Persistente)", + "SettingsTabSystemEnableFsIntegrityChecks": "Comprobar integridad de los archivos", + "SettingsTabSystemAudioBackend": "Motor de audio:", + "SettingsTabSystemAudioBackendDummy": "Vacio", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Pueden causar inestabilidad)", + "SettingsTabSystemExpandDramSize": "Usar diseño alternativo de memoria (Desarrolladores)", + "SettingsTabSystemIgnoreMissingServices": "Ignorar servicios no implementados", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsAPI": "API de gráficos", + "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombras", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", + "SettingsTabGraphicsAnisotropicFiltering2x": "x2", + "SettingsTabGraphicsAnisotropicFiltering4x": "x4", + "SettingsTabGraphicsAnisotropicFiltering8x": "x8", + "SettingsTabGraphicsAnisotropicFiltering16x": "x16", + "SettingsTabGraphicsResolutionScale": "Escala de resolución:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizada (no recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Relación de aspecto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Estirar a la ventana", + "SettingsTabGraphicsDeveloperOptions": "Opciones de desarrollador", + "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombras:", + "SettingsTabLogging": "Registros", + "SettingsTabLoggingLogging": "Registros", + "SettingsTabLoggingEnableLoggingToFile": "Habilitar registro a archivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar registros de Stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar registros de Info", + "SettingsTabLoggingEnableWarningLogs": "Habilitar registros de Advertencia", + "SettingsTabLoggingEnableErrorLogs": "Habilitar registros de Error", + "SettingsTabLoggingEnableTraceLogs": "Habilitar registros de Rastro", + "SettingsTabLoggingEnableGuestLogs": "Habilitar registros de Guest", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:", + "SettingsTabLoggingDeveloperOptions": "Opciones de desarrollador (ADVERTENCIA: empeorarán el rendimiento)", + "SettingsTabLoggingDeveloperOptionsNote": "ADVERTENCIA: Reducirá el rendimiento", + "SettingsTabLoggingGraphicsBackendLogLevel": "Nivel de registro de backend gráficos:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nada", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Errores", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentizaciones", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todo", + "SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug", + "SettingsTabInput": "Entrada", + "SettingsTabInputEnableDockedMode": "Modo dock/TV", + "SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado", + "SettingsButtonSave": "Guardar", + "SettingsButtonClose": "Cerrar", + "SettingsButtonOk": "Aceptar", + "SettingsButtonCancel": "Cancelar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jugador", + "ControllerSettingsPlayer1": "Jugador 1", + "ControllerSettingsPlayer2": "Jugador 2", + "ControllerSettingsPlayer3": "Jugador 3", + "ControllerSettingsPlayer4": "Jugador 4", + "ControllerSettingsPlayer5": "Jugador 5", + "ControllerSettingsPlayer6": "Jugador 6", + "ControllerSettingsPlayer7": "Jugador 7", + "ControllerSettingsPlayer8": "Jugador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Actualizar", + "ControllerSettingsDeviceDisabled": "Deshabilitado", + "ControllerSettingsControllerType": "Tipo de Mando", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Mando Pro", + "ControllerSettingsControllerTypeJoyConPair": "Doble Joy-Con", + "ControllerSettingsControllerTypeJoyConLeft": "Joy-Con Izquierdo", + "ControllerSettingsControllerTypeJoyConRight": "Joy-Con Derecho", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Predeterminado", + "ControllerSettingsLoad": "Cargar", + "ControllerSettingsAdd": "Agregar", + "ControllerSettingsRemove": "Quitar", + "ControllerSettingsButtons": "Botones", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Pad direccional", + "ControllerSettingsDPadUp": "Arriba", + "ControllerSettingsDPadDown": "Abajo", + "ControllerSettingsDPadLeft": "Izquierda", + "ControllerSettingsDPadRight": "Derecha", + "ControllerSettingsStickButton": "Botón", + "ControllerSettingsStickUp": "Arriba", + "ControllerSettingsStickDown": "Abajo", + "ControllerSettingsStickLeft": "Izquierda", + "ControllerSettingsStickRight": "Derecha", + "ControllerSettingsStickStick": "Palanca", + "ControllerSettingsStickInvertXAxis": "Invertir eje X", + "ControllerSettingsStickInvertYAxis": "Invertir eje Y", + "ControllerSettingsStickDeadzone": "Zona muerta:", + "ControllerSettingsLStick": "Palanca izquierda", + "ControllerSettingsRStick": "Palanca derecha", + "ControllerSettingsTriggersLeft": "Gatillos izquierdos", + "ControllerSettingsTriggersRight": "Gatillos derechos", + "ControllerSettingsTriggersButtonsLeft": "Botones de gatillo izquierdos", + "ControllerSettingsTriggersButtonsRight": "Botones de gatillo derechos", + "ControllerSettingsTriggers": "Gatillos", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botones izquierdos", + "ControllerSettingsExtraButtonsRight": "Botones derechos", + "ControllerSettingsMisc": "Misceláneo", + "ControllerSettingsTriggerThreshold": "Límite de gatillos:", + "ControllerSettingsMotion": "Movimiento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar movimiento compatible con CemuHook", + "ControllerSettingsMotionControllerSlot": "Puerto del mando:", + "ControllerSettingsMotionMirrorInput": "Paralelizar derecho e izquierdo", + "ControllerSettingsMotionRightJoyConSlot": "Puerto del Joy-Con derecho:", + "ControllerSettingsMotionServerHost": "Host del servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidad de Gyro:", + "ControllerSettingsMotionGyroDeadzone": "Zona muerta de Gyro:", + "ControllerSettingsSave": "Guardar", + "ControllerSettingsClose": "Cerrar", + "UserProfilesSelectedUserProfile": "Perfil de usuario seleccionado:", + "UserProfilesSaveProfileName": "Guardar nombre de perfil", + "UserProfilesChangeProfileImage": "Cambiar imagen de perfil", + "UserProfilesAvailableUserProfiles": "Perfiles de usuario disponibles:", + "UserProfilesAddNewProfile": "Añadir nuevo perfil", + "UserProfilesDelete": "Eliminar", + "UserProfilesClose": "Cerrar", + "ProfileNameSelectionWatermark": "Escoge un apodo", + "ProfileImageSelectionTitle": "Selección de imagen de perfil", + "ProfileImageSelectionHeader": "Elige una imagen de perfil", + "ProfileImageSelectionNote": "Puedes importar una imagen de perfil personalizada, o seleccionar un avatar del firmware de sistema", + "ProfileImageSelectionImportImage": "Importar imagen", + "ProfileImageSelectionSelectAvatar": "Seleccionar avatar del firmware", + "InputDialogTitle": "Cuadro de diálogo de entrada", + "InputDialogOk": "Aceptar", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Introducir nombre de perfil", + "InputDialogAddNewProfileHeader": "Por favor elige un nombre de usuario", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escoger", + "AvatarSetBackgroundColor": "Establecer color de fondo", + "AvatarClose": "Cerrar", + "ControllerSettingsLoadProfileToolTip": "Cargar perfil", + "ControllerSettingsAddProfileToolTip": "Agregar perfil", + "ControllerSettingsRemoveProfileToolTip": "Eliminar perfil", + "ControllerSettingsSaveProfileToolTip": "Guardar perfil", + "MenuBarFileToolsTakeScreenshot": "Captura de pantalla", + "MenuBarFileToolsHideUi": "Ocultar interfaz", + "GameListContextMenuRunApplication": "Ejecutar aplicación", + "GameListContextMenuToggleFavorite": "Marcar favorito", + "GameListContextMenuToggleFavoriteToolTip": "Marca o desmarca el juego como favorito", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Directorio de tema personalizado", + "SettingsTabGeneralThemeBaseStyle": "Estilo base", + "SettingsTabGeneralThemeBaseStyleDark": "Oscuro", + "SettingsTabGeneralThemeBaseStyleLight": "Claro", + "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema personalizado", + "ButtonBrowse": "Buscar", + "ControllerSettingsConfigureGeneral": "Configurar", + "ControllerSettingsRumble": "Vibración", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibraciones fuertes", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibraciones débiles", + "DialogMessageSaveNotAvailableMessage": "No hay datos de guardado para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "¿Quieres crear datos de guardado para este juego?", + "DialogConfirmationTitle": "Ryujinx - Confirmación", + "DialogUpdaterTitle": "Ryujinx - Actualizador", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Advertencia", + "DialogExitTitle": "Ryujinx - Salir", + "DialogErrorMessage": "Ryujinx encontró un error", + "DialogExitMessage": "¿Seguro que quieres cerrar Ryujinx?", + "DialogExitSubMessage": "¡Se perderán los datos no guardados!", + "DialogMessageCreateSaveErrorMessage": "Hubo un error al crear los datos de guardado especificados: {0}", + "DialogMessageFindSaveErrorMessage": "Hubo un error encontrando los datos de guardado especificados: {0}", + "FolderDialogExtractTitle": "Elige la carpeta en la que deseas extraer", + "DialogNcaExtractionMessage": "Extrayendo {0} sección de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extractor de sección NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Fallo de extracción. El NCA principal no estaba presente en el archivo seleccionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Fallo de extracción. Lee el registro para más información.", + "DialogNcaExtractionSuccessMessage": "Se completó la extracción con éxito.", + "DialogUpdaterConvertFailedMessage": "No se pudo convertir la versión actual de Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "¡Cancelando actualización!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "¡Ya tienes la versión más reciente de Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Se ha producido un error al intentar obtener información de liberación de GitHub Release. Esto puede ser causado si una nueva versión está siendo compilada por GitHub Actions. Inténtalo de nuevo en unos minutos.", + "DialogUpdaterConvertFailedGithubMessage": "No se pudo convertir la versión de Ryujinx recibida de GitHub Release.", + "DialogUpdaterDownloadingMessage": "Descargando actualización...", + "DialogUpdaterExtractionMessage": "Extrayendo actualización...", + "DialogUpdaterRenamingMessage": "Renombrando actualización...", + "DialogUpdaterAddingFilesMessage": "Aplicando actualización...", + "DialogUpdaterCompleteMessage": "¡Actualización completa!", + "DialogUpdaterRestartMessage": "¿Quieres reiniciar Ryujinx?", + "DialogUpdaterArchNotSupportedMessage": "¡Tu arquitectura de sistema no es compatible!", + "DialogUpdaterArchNotSupportedSubMessage": "(¡Solo son compatibles los sistemas x64!)", + "DialogUpdaterNoInternetMessage": "¡No estás conectado a internet!", + "DialogUpdaterNoInternetSubMessage": "¡Por favor, verifica que tu conexión a Internet funciona!", + "DialogUpdaterDirtyBuildMessage": "¡No puedes actualizar una versión \"dirty\" de Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, descarga Ryujinx en https://ryujinx.org/ si buscas una versión con soporte.", + "DialogRestartRequiredMessage": "Se necesita reiniciar", + "DialogThemeRestartMessage": "Tema guardado. Se necesita reiniciar para aplicar el tema.", + "DialogThemeRestartSubMessage": "¿Quieres reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "¿Quieres instalar el firmware incluido en este juego? (Firmware versión {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No se encontró firmware instalado pero Ryujinx pudo instalar el firmware {0} a partir de este juego.\nA continuación, se iniciará el emulador.", + "DialogFirmwareNoFirmwareInstalledMessage": "No hay firmware instalado", + "DialogFirmwareInstalledMessage": "Se instaló el firmware {0}", + "DialogInstallFileTypesSuccessMessage": "¡Tipos de archivos instalados con éxito!", + "DialogInstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", + "DialogUninstallFileTypesSuccessMessage": "¡Tipos de archivos desinstalados con éxito!", + "DialogUninstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", + "DialogOpenSettingsWindowLabel": "Abrir ventana de opciones", + "DialogControllerAppletTitle": "Applet de mandos", + "DialogMessageDialogErrorExceptionMessage": "Error al mostrar cuadro de diálogo: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error al mostrar teclado de software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error al mostrar díalogo ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara más información sobre cómo arreglar este error, sigue nuestra Guía de Instalación.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Ocurrió un error al recibir información de la API.", + "DialogAmiiboApiConnectErrorMessage": "No se pudo conectar al servidor de la API Amiibo. El servicio puede estar caído o tu conexión a internet puede haberse desconectado.", + "DialogProfileInvalidProfileErrorMessage": "El perfil {0} no es compatible con el sistema actual de configuración de entrada.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "El perfil predeterminado no se puede sobreescribir", + "DialogProfileDeleteProfileTitle": "Eliminando perfil", + "DialogProfileDeleteProfileMessage": "Esta acción es irreversible, ¿estás seguro de querer continuar?", + "DialogWarning": "Advertencia", + "DialogPPTCDeletionMessage": "Vas a borrar la caché de PPTC para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogPPTCDeletionErrorMessage": "Error purgando la caché de PPTC en {0}: {1}", + "DialogShaderDeletionMessage": "Vas a borrar la caché de sombreadores para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogShaderDeletionErrorMessage": "Error purgando la caché de sombreadores en {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha encontrado un error", + "DialogInvalidTitleIdErrorMessage": "Error de interfaz: El juego seleccionado no tiene una ID válida", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "No se pudo encontrar un firmware válido en {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Se instalará la versión de sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nEsto reemplazará la versión de sistema actual, {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n¿Continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versión de sistema {0} instalada con éxito.", + "DialogUserProfileDeletionWarningMessage": "Si eliminas el perfil seleccionado no quedará ningún otro perfil", + "DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?", + "DialogUserProfileUnsavedChangesTitle": "Advertencia - Cambios sin guardar", + "DialogUserProfileUnsavedChangesMessage": "Ha realizado cambios en este perfil de usuario que no han sido guardados.", + "DialogUserProfileUnsavedChangesSubMessage": "¿Quieres descartar los cambios realizados?", + "DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.", + "DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?", + "DialogLoadNcaErrorMessage": "{0}. Archivo con error: {1}", + "DialogDlcNoDlcErrorMessage": "¡Ese archivo no contiene contenido descargable para el título seleccionado!", + "DialogPerformanceCheckLoggingEnabledMessage": "Has habilitado los registros debug, diseñados solo para uso de los desarrolladores.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Has habilitado el volcado de sombras, diseñado solo para uso de los desarrolladores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar el volcado de sombraa. ¿Quieres deshabilitarlo ahora?", + "DialogLoadAppGameAlreadyLoadedMessage": "Ya has cargado un juego", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, detén la emulación o cierra el emulador antes de iniciar otro juego.", + "DialogUpdateAddUpdateErrorMessage": "¡Ese archivo no contiene una actualización para el título seleccionado!", + "DialogSettingsBackendThreadingWarningTitle": "Advertencia - multihilado de gráficos", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx debe reiniciarse para aplicar este cambio. Dependiendo de tu plataforma, puede que tengas que desactivar manualmente la optimización enlazada de tus controladores gráficos para usar el multihilo de Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Funcionalidades", + "SettingsTabGraphicsBackendMultithreading": "Multihilado del motor gráfico:", + "CommonAuto": "Automático", + "CommonOff": "Desactivado", + "CommonOn": "Activado", + "InputDialogYes": "Sí", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "El nombre de archivo contiene caracteres inválidos. Por favor, inténtalo de nuevo.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Reanudar", + "AboutUrlTooltipMessage": "Haz clic para abrir el sitio web de Ryujinx en tu navegador predeterminado.", + "AboutDisclaimerMessage": "Ryujinx no tiene afiliación alguna con Nintendo™,\nni con ninguno de sus socios.", + "AboutAmiiboDisclaimerMessage": "Utilizamos AmiiboAPI (www.amiiboapi.com)\nen nuestra emulación de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Haz clic para abrir el Patreon de Ryujinx en tu navegador predeterminado.", + "AboutGithubUrlTooltipMessage": "Haz clic para abrir el GitHub de Ryujinx en tu navegador predeterminado.", + "AboutDiscordUrlTooltipMessage": "Haz clic para recibir una invitación al Discord de Ryujinx en tu navegador predeterminado.", + "AboutTwitterUrlTooltipMessage": "Haz clic para abrir el Twitter de Ryujinx en tu navegador predeterminado.", + "AboutRyujinxAboutTitle": "Acerca de:", + "AboutRyujinxAboutContent": "Ryujinx es un emulador para Nintendo Switch™.\nPor favor, apóyanos en Patreon.\nEncuentra las noticias más recientes en nuestro Twitter o Discord.\nDesarrolladores interesados en contribuir pueden encontrar más información en GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Haz clic para abrir la página de contribuidores en tu navegador predeterminado.", + "AboutRyujinxSupprtersTitle": "Apoyado en Patreon Por:", + "AmiiboSeriesLabel": "Serie de Amiibo", + "AmiiboCharacterLabel": "Personaje", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Mostrar todos los Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: usar etiqueta aleatoria Uuid", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID de título", + "DlcManagerTableHeadingContainerPathLabel": "Directorio del contenedor", + "DlcManagerTableHeadingFullPathLabel": "Directorio completo", + "DlcManagerRemoveAllButton": "Quitar todo", + "DlcManagerEnableAllButton": "Activar todas", + "DlcManagerDisableAllButton": "Desactivar todos", + "MenuBarOptionsChangeLanguage": "Cambiar idioma", + "MenuBarShowFileTypes": "Mostrar tipos de archivo", + "CommonSort": "Orden", + "CommonShowNames": "Mostrar nombres", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Funcionalidades Y Mejoras", + "ErrorWindowTitle": "Ventana de error", + "ToggleDiscordTooltip": "Elige si muestras Ryujinx o no en tu actividad de Discord cuando lo estés usando", + "AddGameDirBoxTooltip": "Elige un directorio de juegos para mostrar en la ventana principal", + "AddGameDirTooltip": "Agrega un directorio de juegos a la lista", + "RemoveGameDirTooltip": "Quita el directorio seleccionado de la lista", + "CustomThemeCheckTooltip": "Activa o desactiva los temas personalizados para la interfaz", + "CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz", + "CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz", + "DockModeToggleTooltip": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.", + "DirectKeyboardTooltip": "Activa o desactiva \"soporte para acceso directo al teclado (HID)\" (Permite a los juegos utilizar tu teclado como entrada de texto)", + "DirectMouseTooltip": "Activa o desactiva \"soporte para acceso directo al ratón (HID)\" (Permite a los juegos utilizar tu ratón como cursor)", + "RegionTooltip": "Cambia la región del sistema", + "LanguageTooltip": "Cambia el idioma del sistema", + "TimezoneTooltip": "Cambia la zona horaria del sistema", + "TimeTooltip": "Cambia la hora del sistema", + "VSyncToggleTooltip": "Sincronización vertical del sistema emulado. A efectos prácticos es un límite de fotogramas para la mayoría de juegos; deshabilitarlo puede hacer que los juegos se aceleren o que las pantallas de carga tarden más o se atasquen.\n\nPuedes activar y desactivar esto mientras el emulador está funcionando con un atajo de teclado a tu elección. Recomendamos hacer esto en vez de deshabilitarlo.\n\nActívalo si no sabes qué hacer.", + "PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.", + "FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.", + "AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.", + "MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.", + "MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.", + "MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.", + "MemoryManagerUnsafeTooltip": "Mapea la memoria directamente, pero no enmascara la dirección dentro del espacio de dirección del guest antes del acceso. El modo más rápido, pero a costa de seguridad. La aplicación guest puede acceder a la memoria desde cualquier parte en Ryujinx, así que ejecuta solo programas en los que confíes cuando uses este modo.", + "UseHypervisorTooltip": "Usar Hypervisor en lugar de JIT. Mejora enormemente el rendimiento cuando está disponible, pero puede ser inestable en su estado actual.", + "DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GiB a 6GiB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.", + "IgnoreMissingServicesTooltip": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.", + "GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "ShaderCacheToggleTooltip": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.", + "ResolutionScaleTooltip": "Escala de resolución aplicada a objetivos aplicables en el renderizado", + "ResolutionScaleEntryTooltip": "Escalado de resolución de coma flotante, como por ejemplo 1,5. Los valores no íntegros pueden causar errores gráficos o crashes.", + "AnisotropyTooltip": "Nivel de filtrado anisotrópico (selecciona Auto para utilizar el valor solicitado por el juego)", + "AspectRatioTooltip": "Relación de aspecto aplicada a la ventana de renderizado.", + "ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos", + "FileLogTooltip": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.", + "StubLogTooltip": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.", + "InfoLogTooltip": "Escribe mensajes de Info en la consola. No afectan al rendimiento.", + "WarnLogTooltip": "Escribe mensajes de Advertencia en la consola. No afectan al rendimiento.", + "ErrorLogTooltip": "Escribe mensajes de Error en la consola. No afectan al rendimiento.", + "TraceLogTooltip": "Escribe mensajes de Rastro en la consola. No afectan al rendimiento.", + "GuestLogTooltip": "Escribe mensajes de Guest en la consola. No afectan al rendimiento.", + "FileAccessLogTooltip": "Activa mensajes de acceso a archivo en la consola", + "FSAccessLogModeTooltip": "Activa registros FS Access en la consola. Los modos posibles son entre 0 y 3", + "DeveloperOptionTooltip": "Usar con cuidado", + "OpenGlLogLevel": "Requiere activar los niveles de registro apropiados", + "DebugLogTooltip": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.", + "LoadApplicationFileTooltip": "Abre el explorador de archivos para elegir un archivo compatible con Switch para cargar", + "LoadApplicationFolderTooltip": "Abre el explorador de archivos para elegir un archivo desempaquetado y compatible con Switch para cargar", + "OpenRyujinxFolderTooltip": "Abre la carpeta de sistema de Ryujinx", + "OpenRyujinxLogsTooltip": "Abre la carpeta en la que se guardan los registros", + "ExitTooltip": "Cierra Ryujinx", + "OpenSettingsTooltip": "Abre la ventana de configuración", + "OpenProfileManagerTooltip": "Abre la ventana para gestionar los perfiles de usuario", + "StopEmulationTooltip": "Detiene la emulación del juego actual y regresa a la selección de juegos", + "CheckUpdatesTooltip": "Busca actualizaciones para Ryujinx", + "OpenAboutTooltip": "Abre la ventana \"Acerca de\"", + "GridSize": "Tamaño de cuadrícula", + "GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño", + "AboutRyujinxContributorsButtonHeader": "Ver todos los contribuidores", + "SettingsTabSystemAudioVolume": "Volumen: ", + "AudioVolumeTooltip": "Ajusta el nivel de volumen", + "SettingsTabSystemEnableInternetAccess": "Conectar guest a Internet/Modo LAN", + "EnableInternetAccessTooltip": "Permite a la aplicación emulada conectarse a Internet.\n\nLos juegos que tengan modo LAN podrán conectarse entre sí habilitando esta opción y estando conectados al mismo módem. Asimismo, esto permite conexiones con consolas reales.\n\nNO permite conectar con los servidores de Nintendo Online. Puede causar que ciertos juegos crasheen al intentar conectarse a sus servidores.\n\nDesactívalo si no estás seguro.", + "GameListContextMenuManageCheatToolTip": "Activa o desactiva los cheats", + "GameListContextMenuManageCheat": "Administrar cheats", + "ControllerSettingsStickRange": "Alcance:", + "DialogStopEmulationTitle": "Ryujinx - Detener emulación", + "DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Sonido", + "SettingsTabNetwork": "Red", + "SettingsTabNetworkConnection": "Conexión de red", + "SettingsTabCpuCache": "Caché de CPU", + "SettingsTabCpuMemory": "Memoria de CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, actualiza Ryujinx a través de FlatHub.", + "UpdaterDisabledWarningTitle": "¡Actualizador deshabilitado!", + "GameListContextMenuOpenSdModsDirectory": "Abrir carpeta de mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre la carpeta alternativa de mods en la que puedes insertar mods de Atmosphere. Útil para mods que vengan organizados para uso en consola.", + "ControllerSettingsRotate90": "Rotar 90° en el sentido de las agujas del reloj", + "IconSize": "Tamaño de iconos", + "IconSizeTooltip": "Cambia el tamaño de los iconos de juegos", + "MenuBarOptionsShowConsole": "Mostrar consola", + "ShaderCachePurgeError": "Error al eliminar la caché en {0}: {1}", + "UserErrorNoKeys": "No se encontraron keys", + "UserErrorNoFirmware": "No se encontró firmware", + "UserErrorFirmwareParsingFailed": "Error al analizar el firmware", + "UserErrorApplicationNotFound": "No se encontró la aplicación", + "UserErrorUnknown": "Error desconocido", + "UserErrorUndefined": "Error indefinido", + "UserErrorNoKeysDescription": "Ryujinx no pudo encontrar tus 'prod.keys'.", + "UserErrorNoFirmwareDescription": "Ryujinx no pudo encontrar un firmware instalado.", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx no pudo analizar el firmware. Normalmente esto ocurre debido a keys desfasadas.", + "UserErrorApplicationNotFoundDescription": "Ryujinx no pudo encontrar una aplicación válida en ese camino.", + "UserErrorUnknownDescription": "¡Ocurrió un error desconocido!", + "UserErrorUndefinedDescription": "¡Ocurrió un error indefinido! Esto no debería pasar, por favor, ¡contacta con un desarrollador!", + "OpenSetupGuideMessage": "Abrir la guía de instalación", + "NoUpdate": "No actualizado", + "TitleUpdateVersionLabel": "Versión {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmación", + "FileDialogAllTypes": "Todos los tipos", + "Never": "Nunca", + "SwkbdMinCharacters": "Debe tener al menos {0} caracteres", + "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres", + "SoftwareKeyboard": "Teclado de software", + "SoftwareKeyboardModeNumbersOnly": "Solo deben ser números", + "SoftwareKeyboardModeAlphabet": "Solo deben ser caracteres no CJK", + "SoftwareKeyboardModeASCII": "Solo deben ser texto ASCII", + "DialogControllerAppletMessagePlayerRange": "La aplicación require {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", + "DialogControllerAppletMessage": "La aplicación require exactamente {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", + "DialogControllerAppletDockModeSet": "Modo dock/TV activo. El control portátil también es inválido.\n\n", + "UpdaterRenaming": "Renombrando archivos viejos...", + "UpdaterRenameFailed": "El actualizador no pudo renombrar el archivo: {0}", + "UpdaterAddingFiles": "Añadiendo nuevos archivos...", + "UpdaterExtracting": "Extrayendo actualización...", + "UpdaterDownloading": "Descargando actualización...", + "Game": "Juego", + "Docked": "Dock/TV", + "Handheld": "Portátil", + "ConnectionError": "Error de conexión.", + "AboutPageDeveloperListMore": "{0} y más...", + "ApiError": "Error de API.", + "LoadingHeading": "Cargando {0}", + "CompilingPPTC": "Compilando PTC", + "CompilingShaders": "Compilando sombreadores", + "AllKeyboards": "Todos los teclados", + "OpenFileDialogTitle": "Selecciona un archivo soportado para cargar", + "OpenFolderDialogTitle": "Selecciona una carpeta con un juego desempaquetado", + "AllSupportedFormats": "Todos los formatos soportados", + "RyujinxUpdater": "Actualizador de Ryujinx", + "SettingsTabHotkeys": "Atajos de teclado", + "SettingsTabHotkeysHotkeys": "Atajos de teclado", + "SettingsTabHotkeysToggleVsyncHotkey": "Alternar la sincronización vertical:", + "SettingsTabHotkeysScreenshotHotkey": "Captura de pantalla:", + "SettingsTabHotkeysShowUiHotkey": "Mostrar interfaz:", + "SettingsTabHotkeysPauseHotkey": "Pausar:", + "SettingsTabHotkeysToggleMuteHotkey": "Silenciar:", + "ControllerMotionTitle": "Opciones de controles de movimiento", + "ControllerRumbleTitle": "Opciones de vibración", + "SettingsSelectThemeFileDialogTitle": "Selecciona un archivo de tema", + "SettingsXamlThemeFile": "Archivo de tema Xaml", + "AvatarWindowTitle": "Administrar cuentas - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Desconocido", + "Usage": "Uso", + "Writable": "Escribible", + "SelectDlcDialogTitle": "Selecciona archivo(s) de DLC", + "SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización", + "UserProfileWindowTitle": "Administrar perfiles de usuario", + "CheatWindowTitle": "Administrar cheats", + "DlcWindowTitle": "Administrar contenido descargable", + "UpdateWindowTitle": "Administrar actualizaciones", + "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", + "BuildId": "Id de compilación:", + "DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]", + "UserProfilesEditProfile": "Editar selección", + "Cancel": "Cancelar", + "Save": "Guardar", + "Discard": "Descartar", + "UserProfilesSetProfileImage": "Elegir Imagen de Perfil ", + "UserProfileEmptyNameError": "El nombre es obligatorio", + "UserProfileNoImageError": "Debe establecerse la imagen de perfil", + "GameUpdateWindowHeading": "Actualizaciones disponibles para {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentar la resolución:", + "SettingsTabHotkeysResScaleDownHotkey": "Disminuir la resolución:", + "UserProfilesName": "Nombre:", + "UserProfilesUserId": "Id de Usuario:", + "SettingsTabGraphicsBackend": "Fondo de gráficos", + "SettingsTabGraphicsBackendTooltip": "Back-end de los gráficos a utilizar", + "SettingsEnableTextureRecompression": "Activar recompresión de texturas", + "SettingsEnableTextureRecompressionTooltip": "Comprime ciertas texturas para reducir el uso de la VRAM.\n\nRecomendado para GPUs que tienen menos de 4 GB de VRAM.\n\nMantén esta opción desactivada si no estás seguro.", + "SettingsTabGraphicsPreferredGpu": "GPU preferida", + "SettingsTabGraphicsPreferredGpuTooltip": "Selecciona la tarjeta gráfica que se utilizará con los back-end de gráficos Vulkan.\n\nNo afecta la GPU que utilizará OpenGL.\n\nFije a la GPU marcada como \"dGUP\" ante dudas. Si no hay una, no haga modificaciones.", + "SettingsAppRequiredRestartMessage": "Reinicio de Ryujinx requerido.", + "SettingsGpuBackendRestartMessage": "La configuración de la GPU o del back-end de los gráficos fue modificada. Es necesario reiniciar para que se aplique.", + "SettingsGpuBackendRestartSubMessage": "¿Quieres reiniciar ahora?", + "RyujinxUpdaterMessage": "¿Quieres actualizar Ryujinx a la última versión?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentar volumen:", + "SettingsTabHotkeysVolumeDownHotkey": "Disminuir volumen:", + "SettingsEnableMacroHLE": "Activar Macros HLE", + "SettingsEnableMacroHLETooltip": "Emulación alto-nivel del código de Macros de GPU\n\nIncrementa el rendimiento, pero puede causar errores gráficos en algunos juegos.\n\nDeja esta opción activada si no estás seguro.", + "SettingsEnableColorSpacePassthrough": "Paso de espacio de color", + "SettingsEnableColorSpacePassthroughTooltip": "Dirige el backend de Vulkan a pasar a través de la información del color sin especificar un espacio de color. Para los usuarios con pantallas de gran gama, esto puede resultar en colores más vibrantes, a costa de la corrección del color.", + "VolumeShort": "Volumen", + "UserProfilesManageSaves": "Administrar mis partidas guardadas", + "DeleteUserSave": "¿Quieres borrar los datos de usuario de este juego?", + "IrreversibleActionNote": "Esta acción no es reversible.", + "SaveManagerHeading": "Manage Saves for {0}", + "SaveManagerTitle": "Administrador de datos de guardado.", + "Name": "Nombre", + "Size": "Tamaño", + "Search": "Buscar", + "UserProfilesRecoverLostAccounts": "Recuperar cuentas perdidas", + "Recover": "Recuperar", + "UserProfilesRecoverHeading": "Datos de guardado fueron encontrados para las siguientes cuentas", + "UserProfilesRecoverEmptyList": "No hay perfiles a recuperar", + "GraphicsAATooltip": "Aplica el suavizado de bordes al procesamiento del juego", + "GraphicsAALabel": "Suavizado de bordes:", + "GraphicsScalingFilterLabel": "Filtro de escalado:", + "GraphicsScalingFilterTooltip": "Activa el escalado de búfer de fotogramas", + "GraphicsScalingFilterLevelLabel": "Nivel", + "GraphicsScalingFilterLevelTooltip": "Establecer nivel del filtro de escalado", + "SmaaLow": "SMAA Bajo", + "SmaaMedium": "SMAA Medio", + "SmaaHigh": "SMAA Alto", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Editar usuario", + "UserEditorTitleCreate": "Crear Usuario", + "SettingsTabNetworkInterface": "Interfaz de Red", + "NetworkInterfaceTooltip": "Interfaz de red usada para las características LAN", + "NetworkInterfaceDefault": "Predeterminado", + "PackagingShaders": "Empaquetando sombreadores", + "AboutChangelogButton": "Ver registro de cambios en GitHub", + "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/fr_FR.json b/src/Ryujinx.Ava/Assets/Locales/fr_FR.json new file mode 100644 index 000000000..5bab6f7b2 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/fr_FR.json @@ -0,0 +1,656 @@ +{ + "Language": "Français", + "MenuBarFileOpenApplet": "Ouvrir Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'Éditeur Applet Mii en mode autonome", + "SettingsTabInputDirectMouseAccess": "Accès direct à la souris", + "SettingsTabSystemMemoryManagerMode": "Mode de gestion de la mémoire :", + "SettingsTabSystemMemoryManagerModeSoftware": "Logiciel", + "SettingsTabSystemMemoryManagerModeHost": "Hôte (rapide)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hôte non vérifié (plus rapide, non sécurisé)", + "SettingsTabSystemUseHypervisor": "Utiliser l'Hyperviseur", + "MenuBarFile": "_Fichier", + "MenuBarFileOpenFromFile": "_Charger un jeu depuis un fichier", + "MenuBarFileOpenUnpacked": "Charger un jeu extrait", + "MenuBarFileOpenEmuFolder": "Ouvrir le dossier Ryujinx", + "MenuBarFileOpenLogsFolder": "Ouvrir le dossier des journaux", + "MenuBarFileExit": "_Quitter", + "MenuBarOptions": "Options", + "MenuBarOptionsToggleFullscreen": "Basculer en plein écran", + "MenuBarOptionsStartGamesInFullscreen": "Démarrer jeux en plein écran", + "MenuBarOptionsStopEmulation": "Arrêter l'émulation", + "MenuBarOptionsSettings": "_Paramètres", + "MenuBarOptionsManageUserProfiles": "_Gérer les profils d'utilisateurs", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simuler un message de réveil", + "MenuBarActionsScanAmiibo": "Scanner un Amiibo", + "MenuBarTools": "_Outils", + "MenuBarToolsInstallFirmware": "Installer un firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installer un firmware depuis un fichier XCI ou ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installer un firmware depuis un dossier", + "MenuBarToolsManageFileTypes": "Gérer les types de fichiers", + "MenuBarToolsInstallFileTypes": "Installer les types de fichiers", + "MenuBarToolsUninstallFileTypes": "Désinstaller les types de fichiers", + "MenuBarHelp": "Aide", + "MenuBarHelpCheckForUpdates": "Vérifier les mises à jour", + "MenuBarHelpAbout": "Á propos", + "MenuSearch": "Rechercher...", + "GameListHeaderFavorite": "Favoris", + "GameListHeaderIcon": "Icône", + "GameListHeaderApplication": "Nom", + "GameListHeaderDeveloper": "Développeur", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Temps de jeu", + "GameListHeaderLastPlayed": "jouer la dernière fois", + "GameListHeaderFileExtension": "Fichier externe", + "GameListHeaderFileSize": "Taille du Fichier", + "GameListHeaderPath": "Chemin", + "GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde utilisateur du jeu", + "GameListContextMenuOpenDeviceSaveDirectory": "Ouvrir le dossier de sauvegarde console", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde console du jeu", + "GameListContextMenuOpenBcatSaveDirectory": "Ouvrir le dossier de sauvegarde BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu", + "GameListContextMenuManageTitleUpdates": "Gérer la mise à jour des titres", + "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion de la mise à jour des titres", + "GameListContextMenuManageDlc": "Gérer les DLC", + "GameListContextMenuManageDlcToolTip": "Ouvre la fenêtre de gestion des DLC", + "GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods du jeu", + "GameListContextMenuCacheManagement": "Gestion des caches", + "GameListContextMenuCacheManagementPurgePptc": "Purger le PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Supprime le PPTC du jeu", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des Shaders", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Supprime le cache des shaders du jeu", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Ouvrir le dossier du PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ouvre le dossier contenant le PPTC du jeu", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Ouvrir le dossier du cache des shaders", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ouvre le dossier contenant le cache des shaders du jeu", + "GameListContextMenuExtractData": "Extraire les données", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrait la section ExeFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrait la section RomFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrait la section Logo du jeu (mise à jour incluse)", + "StatusBarGamesLoaded": "{0}/{1} Jeux chargés", + "StatusBarSystemVersion": "Version du Firmware: {0}", + "LinuxVmMaxMapCountDialogTitle": "Limite basse pour les mappages de mémoire détectés", + "LinuxVmMaxMapCountDialogTextPrimary": "Voulez-vous augmenter la valeur de vm.max_map_count à {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Certains jeux peuvent essayer de créer plus de mappages de mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Oui, jusqu'au prochain redémarrage", + "LinuxVmMaxMapCountDialogButtonPersistent": "Oui, en permanence", + "LinuxVmMaxMapCountWarningTextPrimary": "La quantité maximale de mappings mémoire est inférieure à la valeur recommandée.", + "LinuxVmMaxMapCountWarningTextSecondary": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux peuvent essayer de créer plus de mappings mémoire que ceux actuellement autorisés. Ryujinx s'écrasera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement la limite, soit installer pkexec, ce qui permet à Ryujinx de l'aider.", + "Settings": "Paramètres", + "SettingsTabGeneral": "Interface Utilisateur", + "SettingsTabGeneralGeneral": "Général", + "SettingsTabGeneralEnableDiscordRichPresence": "Activer Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Vérifier les mises à jour au démarrage", + "SettingsTabGeneralShowConfirmExitDialog": "Afficher le message de \"Confirmation de sortie\"", + "SettingsTabGeneralHideCursor": "Masquer le Curseur :", + "SettingsTabGeneralHideCursorNever": "Jamais", + "SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif", + "SettingsTabGeneralHideCursorAlways": "Toujours", + "SettingsTabGeneralGameDirectories": "Dossiers de Jeux", + "SettingsTabGeneralAdd": "Ajouter", + "SettingsTabGeneralRemove": "Supprimer", + "SettingsTabSystem": "Système", + "SettingsTabSystemCore": "Cœur", + "SettingsTabSystemSystemRegion": "Région du système:", + "SettingsTabSystemSystemRegionJapan": "Japon", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australie", + "SettingsTabSystemSystemRegionChina": "Chine", + "SettingsTabSystemSystemRegionKorea": "Corée", + "SettingsTabSystemSystemRegionTaiwan": "Taïwan", + "SettingsTabSystemSystemLanguage": "Langue du système:", + "SettingsTabSystemSystemLanguageJapanese": "Japonais", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Anglais Américain", + "SettingsTabSystemSystemLanguageFrench": "Français", + "SettingsTabSystemSystemLanguageGerman": "Allemand", + "SettingsTabSystemSystemLanguageItalian": "Italien", + "SettingsTabSystemSystemLanguageSpanish": "Espagnol", + "SettingsTabSystemSystemLanguageChinese": "Chinois", + "SettingsTabSystemSystemLanguageKorean": "Coréen", + "SettingsTabSystemSystemLanguageDutch": "Néerlandais", + "SettingsTabSystemSystemLanguagePortuguese": "Portugais", + "SettingsTabSystemSystemLanguageRussian": "Russe", + "SettingsTabSystemSystemLanguageTaiwanese": "Taïwanais", + "SettingsTabSystemSystemLanguageBritishEnglish": "Anglais britannique ", + "SettingsTabSystemSystemLanguageCanadianFrench": "Français Canadien", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espagnol latino-américain", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinois simplifié", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinois traditionnel", + "SettingsTabSystemSystemTimeZone": "Fuseau horaire du système :", + "SettingsTabSystemSystemTime": "Heure du système:", + "SettingsTabSystemEnableVsync": "Activer le VSync", + "SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers", + "SettingsTabSystemAudioBackend": "Back-end audio", + "SettingsTabSystemAudioBackendDummy": "Factice", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Cela peut causer des instabilités)", + "SettingsTabSystemExpandDramSize": "Utiliser disposition alternative de la mémoire (développeur)", + "SettingsTabSystemIgnoreMissingServices": "Ignorer les services manquants", + "SettingsTabGraphics": "Graphique", + "SettingsTabGraphicsAPI": "API Graphique", + "SettingsTabGraphicsEnableShaderCache": "Activer le cache des shaders", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrage anisotrope:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "x2", + "SettingsTabGraphicsAnisotropicFiltering4x": "x4", + "SettingsTabGraphicsAnisotropicFiltering8x": "x8", + "SettingsTabGraphicsAnisotropicFiltering16x": "x16", + "SettingsTabGraphicsResolutionScale": "Échelle de résolution:", + "SettingsTabGraphicsResolutionScaleCustom": "Personnalisée (Non recommandée)", + "SettingsTabGraphicsResolutionScaleNative": "Natif (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Format :", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Écran étiré", + "SettingsTabGraphicsDeveloperOptions": "Options développeur", + "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de dump des shaders:", + "SettingsTabLogging": "Journaux", + "SettingsTabLoggingLogging": "Journaux", + "SettingsTabLoggingEnableLoggingToFile": "Activer la sauvegarde des journaux vers un fichier", + "SettingsTabLoggingEnableStubLogs": "Activer les journaux stub", + "SettingsTabLoggingEnableInfoLogs": "Activer les journaux d'informations", + "SettingsTabLoggingEnableWarningLogs": "Activer les journaux d'avertissements", + "SettingsTabLoggingEnableErrorLogs": "Activer les journaux d'erreurs", + "SettingsTabLoggingEnableTraceLogs": "Activer journaux d'erreurs Trace", + "SettingsTabLoggingEnableGuestLogs": "Activer les journaux du programme simulé", + "SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux des accès au système de fichiers", + "SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux des accès au système de fichiers:", + "SettingsTabLoggingDeveloperOptions": "Options développeur (ATTENTION: Cela peut réduire les performances)", + "SettingsTabLoggingDeveloperOptionsNote": "ATTENTION : Réduira les performances", + "SettingsTabLoggingGraphicsBackendLogLevel": "Niveau du journal du backend graphique :", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Erreur", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentissements", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tout", + "SettingsTabLoggingEnableDebugLogs": "Activer les journaux de debug", + "SettingsTabInput": "Contrôles", + "SettingsTabInputEnableDockedMode": "Active le mode station d'accueil", + "SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier", + "SettingsButtonSave": "Enregistrer", + "SettingsButtonClose": "Fermer", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Annuler", + "SettingsButtonApply": "Appliquer", + "ControllerSettingsPlayer": "Joueur", + "ControllerSettingsPlayer1": "Joueur 1", + "ControllerSettingsPlayer2": "Joueur 2", + "ControllerSettingsPlayer3": "Joueur 3", + "ControllerSettingsPlayer4": "Joueur 4", + "ControllerSettingsPlayer5": "Joueur 5", + "ControllerSettingsPlayer6": "Joueur 6", + "ControllerSettingsPlayer7": "Joueur 7", + "ControllerSettingsPlayer8": "Joueur 8", + "ControllerSettingsHandheld": "Portable", + "ControllerSettingsInputDevice": "Périphériques", + "ControllerSettingsRefresh": "Actualiser", + "ControllerSettingsDeviceDisabled": "Désactivé", + "ControllerSettingsControllerType": "Type de Controleur", + "ControllerSettingsControllerTypeHandheld": "Portable", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Joints", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Gauche", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Droite", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Défaut", + "ControllerSettingsLoad": "Charger", + "ControllerSettingsAdd": "Ajouter", + "ControllerSettingsRemove": "Supprimer", + "ControllerSettingsButtons": "Boutons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croix Directionnelle", + "ControllerSettingsDPadUp": "Haut", + "ControllerSettingsDPadDown": "Bas", + "ControllerSettingsDPadLeft": "Gauche", + "ControllerSettingsDPadRight": "Droite", + "ControllerSettingsStickButton": "Bouton", + "ControllerSettingsStickUp": "Haut", + "ControllerSettingsStickDown": "Bas", + "ControllerSettingsStickLeft": "Gauche", + "ControllerSettingsStickRight": "Droite", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Inverser l'axe X", + "ControllerSettingsStickInvertYAxis": "Inverser l'axe Y", + "ControllerSettingsStickDeadzone": "Zone morte :", + "ControllerSettingsLStick": "Joystick Gauche", + "ControllerSettingsRStick": "Joystick Droit", + "ControllerSettingsTriggersLeft": "Gachettes Gauche", + "ControllerSettingsTriggersRight": "Gachettes Droite", + "ControllerSettingsTriggersButtonsLeft": "Boutons Gachettes Gauche", + "ControllerSettingsTriggersButtonsRight": "Boutons Gachettes Droite", + "ControllerSettingsTriggers": "Gachettes", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Boutons Gauche", + "ControllerSettingsExtraButtonsRight": "Boutons Droite", + "ControllerSettingsMisc": "Divers", + "ControllerSettingsTriggerThreshold": "Seuil de gachettes:", + "ControllerSettingsMotion": "Mouvements", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Utiliser un capteur de mouvements CemuHook", + "ControllerSettingsMotionControllerSlot": "Contrôleur ID:", + "ControllerSettingsMotionMirrorInput": "Inverser les contrôles", + "ControllerSettingsMotionRightJoyConSlot": "JoyCon Droit ID:", + "ControllerSettingsMotionServerHost": "Addresse du Server:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilitée du gyroscope:", + "ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:", + "ControllerSettingsSave": "Enregistrer", + "ControllerSettingsClose": "Fermer", + "UserProfilesSelectedUserProfile": "Choisir un profil utilisateur:", + "UserProfilesSaveProfileName": "Enregistrer le nom du profil", + "UserProfilesChangeProfileImage": "Changer l'image du profil", + "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:", + "UserProfilesAddNewProfile": "Ajouter un nouveau profil", + "UserProfilesDelete": "Supprimer", + "UserProfilesClose": "Fermer", + "ProfileNameSelectionWatermark": "Choisir un pseudo", + "ProfileImageSelectionTitle": "Sélection de l'image du profil", + "ProfileImageSelectionHeader": "Choisir l'image du profil", + "ProfileImageSelectionNote": "Vous pouvez importer une image de profil personnalisée ou sélectionner un avatar à partir du firmware", + "ProfileImageSelectionImportImage": "Importer une image", + "ProfileImageSelectionSelectAvatar": "Choisir un avatar du firmware", + "InputDialogTitle": "Fenêtre d'entrée de texte", + "InputDialogOk": "OK", + "InputDialogCancel": "Annuler", + "InputDialogAddNewProfileTitle": "Choisir un nom de profil", + "InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil", + "InputDialogAddNewProfileSubtext": "(Longueur max.: {0})", + "AvatarChoose": "Choisir", + "AvatarSetBackgroundColor": "Choisir une couleur de fond", + "AvatarClose": "Fermer", + "ControllerSettingsLoadProfileToolTip": "Charger un profil", + "ControllerSettingsAddProfileToolTip": "Ajouter un profil", + "ControllerSettingsRemoveProfileToolTip": "Supprimer un profil", + "ControllerSettingsSaveProfileToolTip": "Enregistrer un profil", + "MenuBarFileToolsTakeScreenshot": "Prendre une Capture d'Écran", + "MenuBarFileToolsHideUi": "Masquer l'interface utilisateur", + "GameListContextMenuRunApplication": "Démarrer l'application", + "GameListContextMenuToggleFavorite": "Ajouter/Retirer des favoris", + "GameListContextMenuToggleFavoriteToolTip": "Activer/désactiver le statut favori du jeu", + "SettingsTabGeneralTheme": "Thème", + "SettingsTabGeneralThemeCustomTheme": "Chemin du thème personnalisé", + "SettingsTabGeneralThemeBaseStyle": "Style par défaut", + "SettingsTabGeneralThemeBaseStyleDark": "Sombre", + "SettingsTabGeneralThemeBaseStyleLight": "Lumière", + "SettingsTabGeneralThemeEnableCustomTheme": "Activer un Thème Personnalisé", + "ButtonBrowse": "Parcourir", + "ControllerSettingsConfigureGeneral": "Configurer", + "ControllerSettingsRumble": "Vibreur", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicateur de vibrations fortes", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicateur de vibrations faibles", + "DialogMessageSaveNotAvailableMessage": "Il n'y a aucune sauvegarde pour {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Voulez-vous créer une sauvegarde pour ce jeu ?", + "DialogConfirmationTitle": "Ryujinx - Confirmation", + "DialogUpdaterTitle": "Ryujinx - Mise à Jour", + "DialogErrorTitle": "Ryujinx - Erreur", + "DialogWarningTitle": "Ryujinx - Avertissement", + "DialogExitTitle": "Ryujinx - Quitter", + "DialogErrorMessage": "Ryujinx a rencontré une erreur", + "DialogExitMessage": "Êtes-vous sûr de vouloir fermer Ryujinx ?", + "DialogExitSubMessage": "Toute progression non sauvegardée sera perdue.", + "DialogMessageCreateSaveErrorMessage": "Une erreur s'est produite lors de la création de la sauvegarde spécifiée : {0}", + "DialogMessageFindSaveErrorMessage": "Une erreur s'est produite lors de la recherche de la sauvegarde spécifiée : {0}", + "FolderDialogExtractTitle": "Choisissez le dossier dans lequel extraire", + "DialogNcaExtractionMessage": "Extraction de la section {0} depuis {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extracteur de la section NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Échec de l'extraction. Le NCA principal n'était pas présent dans le fichier sélectionné.", + "DialogNcaExtractionCheckLogErrorMessage": "Échec de l'extraction. Lisez le fichier journal pour plus d'informations.", + "DialogNcaExtractionSuccessMessage": "Extraction terminée avec succès.", + "DialogUpdaterConvertFailedMessage": "Échec de la conversion de la version actuelle de Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Annuler la mise à jour!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Vous utilisez déjà la version la plus mise à jour de Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Une erreur s'est produite lors de la tentative d'obtention des informations de publication de la version GitHub. Cela peut survenir lorsqu'une nouvelle version est en cours de compilation par GitHub Actions. Réessayez dans quelques minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Impossible de convertir la version reçue de Ryujinx depuis Github Release.", + "DialogUpdaterDownloadingMessage": "Téléchargement de la mise à jour...", + "DialogUpdaterExtractionMessage": "Extraction de la mise à jour…", + "DialogUpdaterRenamingMessage": "Renommage de la mise à jour...", + "DialogUpdaterAddingFilesMessage": "Ajout d'une nouvelle mise à jour...", + "DialogUpdaterCompleteMessage": "Mise à jour terminée !", + "DialogUpdaterRestartMessage": "Voulez-vous redémarrer Ryujinx maintenant ?", + "DialogUpdaterArchNotSupportedMessage": "Vous n'utilisez pas d'architecture système prise en charge !", + "DialogUpdaterArchNotSupportedSubMessage": "(Seuls les systèmes x64 sont pris en charge !)", + "DialogUpdaterNoInternetMessage": "Vous n'êtes pas connecté à Internet !", + "DialogUpdaterNoInternetSubMessage": "Veuillez vérifier que vous avez une connexion Internet fonctionnelle!", + "DialogUpdaterDirtyBuildMessage": "Vous ne pouvez pas mettre à jour une version Dirty de Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Veuillez télécharger Ryujinx sur https://ryujinx.org/ si vous recherchez une version prise en charge.", + "DialogRestartRequiredMessage": "Redémarrage Requis", + "DialogThemeRestartMessage": "Le thème a été enregistré. Un redémarrage est requis pour appliquer le thème.", + "DialogThemeRestartSubMessage": "Voulez-vous redémarrer", + "DialogFirmwareInstallEmbeddedMessage": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\\nL'émulateur va maintenant démarrer.", + "DialogFirmwareNoFirmwareInstalledMessage": "Aucun Firmware installé", + "DialogFirmwareInstalledMessage": "Le firmware {0} a été installé", + "DialogInstallFileTypesSuccessMessage": "Types de fichiers installés avec succès!", + "DialogInstallFileTypesErrorMessage": "Échec de l'installation des types de fichiers.", + "DialogUninstallFileTypesSuccessMessage": "Types de fichiers désinstallés avec succès!", + "DialogUninstallFileTypesErrorMessage": "Échec de la désinstallation des types de fichiers.", + "DialogOpenSettingsWindowLabel": "Ouvrir la fenêtre de configuration", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue : {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Erreur lors de l'affichage du clavier logiciel: {0}", + "DialogErrorAppletErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPour plus d'informations sur la manière de corriger cette erreur, suivez notre Guide d'Installation.", + "DialogUserErrorDialogTitle": "Erreur Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Une erreur est survenue lors de la récupération des informations de l'API.", + "DialogAmiiboApiConnectErrorMessage": "Impossible de se connecter au serveur API Amiibo. Le service est peut-être hors service ou vous devriez peut-être vérifier que votre connexion internet est connectée.", + "DialogProfileInvalidProfileErrorMessage": "Le profil {0} est incompatible avec le système de configuration de manette actuel.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Le profil par défaut ne peut pas être écrasé", + "DialogProfileDeleteProfileTitle": "Supprimer le profil", + "DialogProfileDeleteProfileMessage": "Cette action est irréversible, êtes-vous sûr de vouloir continuer ?", + "DialogWarning": "Avertissement", + "DialogPPTCDeletionMessage": "Vous êtes sur le point de mettre en file d'attente une reconstruction PPTC au prochain démarrage de :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogPPTCDeletionErrorMessage": "Erreur lors de la purge du cache PPTC à {0}: {1}", + "DialogShaderDeletionMessage": "Vous êtes sur le point de supprimer le cache du Shader pour :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogShaderDeletionErrorMessage": "Erreur lors de la purge du cache du Shader à {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx a rencontré une erreur", + "DialogInvalidTitleIdErrorMessage": "Erreur d'UI : le jeu sélectionné n'a pas d'ID de titre valide", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware valide n'a pas été trouvé dans {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installer le Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "La version {0} du système sera installée.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nCela remplacera la version actuelle du système {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVoulez-vous continuer ?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installation du firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Version du système {0} installée avec succès.", + "DialogUserProfileDeletionWarningMessage": "Il n'y aurait aucun autre profil à ouvrir si le profil sélectionné est supprimé", + "DialogUserProfileDeletionConfirmMessage": "Voulez-vous supprimer le profil sélectionné ?", + "DialogUserProfileUnsavedChangesTitle": "Avertissement - Modifications non enregistrées", + "DialogUserProfileUnsavedChangesMessage": "Vous avez effectué des modifications sur ce profil d'utilisateur qui n'ont pas été enregistrées.", + "DialogUserProfileUnsavedChangesSubMessage": "Voulez-vous annuler les modifications ?", + "DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder?", + "DialogLoadNcaErrorMessage": "{0}. Fichier erroné : {1}", + "DialogDlcNoDlcErrorMessage": "Le fichier spécifié ne contient pas de DLC pour le titre sélectionné !", + "DialogPerformanceCheckLoggingEnabledMessage": "Vous avez activé la journalisation des traces, conçue pour être utilisée uniquement par les développeurs.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Vous avez activé l'extraction des shaders, qui est conçu pour être utilisé par les développeurs uniquement.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver l'extraction des shaders. Souhaitez-vous désactiver l'extraction des shaders maintenant ?", + "DialogLoadAppGameAlreadyLoadedMessage": "Un jeu a déjà été chargé", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Veuillez arrêter l'émulation ou fermer l'émulateur avant de lancer un autre jeu.", + "DialogUpdateAddUpdateErrorMessage": "Le fichier spécifié ne contient pas de mise à jour pour le titre sélectionné !", + "DialogSettingsBackendThreadingWarningTitle": "Avertissement - Backend Threading ", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx doit être redémarré après avoir changé cette option pour qu'elle s'applique complètement. Selon votre plate-forme, vous devrez peut-être désactiver manuellement le multithreading de votre pilote lorsque vous utilisez Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Fonctionnalités", + "SettingsTabGraphicsBackendMultithreading": "Interface graphique multithread", + "CommonAuto": "Auto", + "CommonOff": "Désactivé", + "CommonOn": "Activé", + "InputDialogYes": "Oui", + "InputDialogNo": "Non", + "DialogProfileInvalidProfileNameErrorMessage": "Le nom du fichier contient des caractères invalides. Veuillez réessayer.", + "MenuBarOptionsPauseEmulation": "Suspendre", + "MenuBarOptionsResumeEmulation": "Reprendre", + "AboutUrlTooltipMessage": "Cliquez pour ouvrir le site de Ryujinx dans votre navigateur par défaut.", + "AboutDisclaimerMessage": "Ryujinx n'est pas affilié à Nintendo™,\nou à aucun de ses partenaires, de quelque manière que ce soit.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) est utilisé\ndans notre émulation Amiibo.", + "AboutPatreonUrlTooltipMessage": "Cliquez pour ouvrir la page Patreon de Ryujinx dans votre navigateur par défaut.", + "AboutGithubUrlTooltipMessage": "Cliquez pour ouvrir la page GitHub de Ryujinx dans votre navigateur par défaut.", + "AboutDiscordUrlTooltipMessage": "Cliquez pour ouvrir une invitation au serveur Discord de Ryujinx dans votre navigateur par défaut.", + "AboutTwitterUrlTooltipMessage": "Cliquez pour ouvrir la page Twitter de Ryujinx dans votre navigateur par défaut.", + "AboutRyujinxAboutTitle": "Á propos:", + "AboutRyujinxAboutContent": "Ryujinx est un émulateur pour la Nintendo Switch™.\nMerci de nous soutenir sur Patreon.\nObtenez toutes les dernières actualités sur notre Twitter ou notre Discord.\nLes développeurs intéressés à contribuer peuvent en savoir plus sur notre GitHub ou notre Discord.", + "AboutRyujinxMaintainersTitle": "Maintenu par :", + "AboutRyujinxMaintainersContentTooltipMessage": "Cliquez pour ouvrir la page Contributeurs dans votre navigateur par défaut.", + "AboutRyujinxSupprtersTitle": "Supporté sur Patreon par :", + "AmiiboSeriesLabel": "Séries Amiibo", + "AmiiboCharacterLabel": "Personnage", + "AmiiboScanButtonLabel": "Scanner", + "AmiiboOptionsShowAllLabel": "Afficher tous les Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack : Utiliser un tag Uuid aléatoire", + "DlcManagerTableHeadingEnabledLabel": "Activé", + "DlcManagerTableHeadingTitleIdLabel": "ID du titre", + "DlcManagerTableHeadingContainerPathLabel": "Chemin du conteneur", + "DlcManagerTableHeadingFullPathLabel": "Chemin complet", + "DlcManagerRemoveAllButton": "Tout supprimer", + "DlcManagerEnableAllButton": "Activer Tout", + "DlcManagerDisableAllButton": "Désactiver Tout", + "MenuBarOptionsChangeLanguage": "Changer la Langue", + "MenuBarShowFileTypes": "Afficher les types de fichiers", + "CommonSort": "Trier", + "CommonShowNames": "Afficher les noms", + "CommonFavorite": "Favoris", + "OrderAscending": "Croissant", + "OrderDescending": "Décroissant", + "SettingsTabGraphicsFeatures": "Fonctionnalités & Améliorations", + "ErrorWindowTitle": "Fenêtre d'erreur", + "ToggleDiscordTooltip": "Choisissez d'afficher ou non Ryujinx sur votre activité « en cours de jeu » Discord", + "AddGameDirBoxTooltip": "Entrez un répertoire de jeux à ajouter à la liste", + "AddGameDirTooltip": "Ajouter un répertoire de jeux à la liste", + "RemoveGameDirTooltip": "Supprimer le dossier sélectionné", + "CustomThemeCheckTooltip": "Utilisez un thème personnalisé Avalonia pour modifier l'apparence des menus de l'émulateur", + "CustomThemePathTooltip": "Chemin vers le thème personnalisé de l'interface utilisateur", + "CustomThemeBrowseTooltip": "Parcourir vers un thème personnalisé pour l'interface utilisateur", + "DockModeToggleTooltip": "Le mode station d'accueil permet à la console émulée de se comporter comme une Nintendo Switch en mode station d'accueil, ce qui améliore la fidélité graphique dans la plupart des jeux. Inversement, la désactivation de cette option rendra la console émulée comme une console Nintendo Switch portable, réduisant la qualité graphique.\n\nConfigurer les controles du joueur 1 si vous prévoyez d'utiliser le mode station d'accueil; configurez les commandes portable si vous prévoyez d'utiliser le mode portable.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "DirectKeyboardTooltip": "Prise en charge de l'accès direct au clavier (HID). Fournit aux jeux l'accès à votre clavier en tant que périphérique de saisie de texte.", + "DirectMouseTooltip": "Prise en charge de l'accès à la souris (HID). Permet aux jeux d'accéder a votre souris en tant que périphérique de pointage.", + "RegionTooltip": "Changer la région du système", + "LanguageTooltip": "Changer la langue du système", + "TimezoneTooltip": "Changer le fuseau horaire du système", + "TimeTooltip": "Changer l'heure du système", + "VSyncToggleTooltip": "Synchronisation verticale de l'émulateur. Généralement un limiteur d'FPS pour la majorité des jeux, le désactiver peut accélérer les jeux pour rendre les écrans de chargement plus court, mais augemente le risque de crash lors des chargements.\n\nPeut être activer ou desactiver en jeu avec un raccourci de votre choix. Nous vous recommandons de le laisser.\n\nLaissez activer, si vous n'êtes pas sûr.", + "PptcToggleTooltip": "Sauvegarde les fonctions JIT afin qu'elles n'aient pas besoin d'être à chaque fois recompiler lorsque le jeu se charge.\n\nRéduit les lags et accélère considérablement le temps de chargement après le premier lancement d'un jeu.\n\nLaissez par défaut si vous n'êtes pas sûr.", + "FsIntegrityToggleTooltip": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activer en cas d'incertitude.", + "AudioBackendTooltip": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL2 est préféré, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Factice) ne rends aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.", + "MemoryManagerTooltip": "Change la façon dont la mémoire émulée est mappée et utiliser. Cela affecte grandement les performances du processeur.\n\nRéglez sur Host Uncheked en cas d'incertitude.", + "MemoryManagerSoftwareTooltip": "Utilisez une table logicielle pour la traduction d'adresses. La plus grande précision est fournie, mais les performances en seront impacter.", + "MemoryManagerHostTooltip": "Mappez directement la mémoire dans l'espace d'adresses de l'hôte. Compilation et exécution JIT beaucoup plus rapides.", + "MemoryManagerUnsafeTooltip": "Mapper directement la mémoire dans la carte, mais ne pas masquer l'adresse dans l'espace d'adressage du client avant l'accès. Plus rapide, mais la sécurité sera négliger. L'application peut accéder à la mémoire depuis n'importe où dans Ryujinx, donc exécutez uniquement les programmes en qui vous avez confiance avec ce mode.", + "UseHypervisorTooltip": "Utiliser l'Hyperviseur au lieu du JIT. Améliore considérablement les performances lorsqu'il est disponible, mais peut être instable dans son état actuel.", + "DRamTooltip": "Utilise une disposition alternative de la mémoire pour imiter le kit de développeur de la Switch.\n\nActiver cette option uniquement pour les packs de textures 4k ou les mods à résolution 4k.\nN'améliore pas les performances, cause des crashs dans certains jeux si activer.\n\nLaissez Désactiver en cas d'incertitude.", + "IgnoreMissingServicesTooltip": "Ignore les services Horizon OS non-intégré. Cela peut aider à contourner les plantages lors du démarrage de certains jeux.\n\nActivez-le en cas d'incertitude.", + "GraphicsBackendThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", + "GalThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", + "ShaderCacheToggleTooltip": "Enregistre un cache de shaders sur le disque dur, réduit le lag lors de multiples exécutions.\n\nLaissez Activer si vous n'êtes pas sûr.", + "ResolutionScaleTooltip": "Échelle de résolution appliquer au rendu du jeu", + "ResolutionScaleEntryTooltip": "Échelle de résolution à virgule flottante, telle que : 1.5. Les échelles non intégrales sont plus susceptibles de causer des problèmes ou des crashs.", + "AnisotropyTooltip": "Niveau de Filtrage Anisotropique (mettre sur Auto pour utiliser la valeur demandée par le jeu)", + "AspectRatioTooltip": "Ratio d'aspect appliqué à la fenêtre de rendu", + "ShaderDumpPathTooltip": "Chemin de copie des Shaders Graphiques", + "FileLogTooltip": "Sauver le journal de la console dans un fichier journal sur le disque. Cela n'affecte pas les performances.", + "StubLogTooltip": "Affiche les messages de log dans la console. N'affecte pas les performances.", + "InfoLogTooltip": "Affiche les messages de log d'informations dans la console. N'affecte pas les performances.", + "WarnLogTooltip": "Affiche les messages d'avertissement dans la console. N'affecte pas les performances.", + "ErrorLogTooltip": "Affiche les messages de log d'erreur dans la console. N'affecte pas les performances.", + "TraceLogTooltip": "Affiche la trace des messages de log dans la console. N'affecte pas les performances.", + "GuestLogTooltip": "Affiche les messages de log des invités dans la console. N'affecte pas les performances.", + "FileAccessLogTooltip": "Affiche les messages de log d'accès aux fichiers dans la console.", + "FSAccessLogModeTooltip": "Active la sortie du journal d'accès FS de la console. Les modes possibles sont 0-3", + "DeveloperOptionTooltip": "Utiliser avec précaution", + "OpenGlLogLevel": "Nécessite l'activation des niveaux de journalisation appropriés", + "DebugLogTooltip": "Affiche les messages de débogage dans la console.\n\nN'utilisez ceci que si un membre du personnel le demande, car cela rendra les logs difficiles à lire et réduit les performances de l'émulateur.", + "LoadApplicationFileTooltip": "Ouvrir un explorateur de fichiers pour choisir un fichier compatible Switch à charger", + "LoadApplicationFolderTooltip": "Ouvrir un explorateur de fichiers pour choisir une application Switch compatible et décompressée à charger", + "OpenRyujinxFolderTooltip": "Ouvrir le dossier du système de fichiers Ryujinx", + "OpenRyujinxLogsTooltip": "Ouvre le dossier dans lequel les journaux sont écrits", + "ExitTooltip": "Quitter Ryujinx", + "OpenSettingsTooltip": "Ouvrir la fenêtre de configuration", + "OpenProfileManagerTooltip": "Ouvrir la fenêtre du gestionnaire de profils d'utilisateurs", + "StopEmulationTooltip": "Arrêter l'émulation du jeu en cours et revenir à la sélection des jeux", + "CheckUpdatesTooltip": "Vérifier les mises à jour de Ryujinx", + "OpenAboutTooltip": "Ouvrir la fenêtre À Propos", + "GridSize": "Taille de la grille", + "GridSizeTooltip": "Modifier la taille des éléments de la grille", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugais brésilien", + "AboutRyujinxContributorsButtonHeader": "Voir tous les contributeurs", + "SettingsTabSystemAudioVolume": "Volume :", + "AudioVolumeTooltip": "Modifier le volume audio", + "SettingsTabSystemEnableInternetAccess": "Accès Internet Invité/Mode LAN", + "EnableInternetAccessTooltip": "Permet à l'application émulée de se connecter à Internet.\n\nLes jeux avec un mode LAN peuvent se connecter les uns aux autres lorsque cette option est cochée et que les systèmes sont connectés au même point d'accès. Cela inclut également les vrais consoles.\n\nCette option n'autorise PAS la connexion aux serveurs Nintendo. Elle peut faire planter certains jeux qui essaient de se connecter à l'Internet.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "GameListContextMenuManageCheatToolTip": "Gérer la triche", + "GameListContextMenuManageCheat": "Gérer la triche", + "ControllerSettingsStickRange": "Intervalle:", + "DialogStopEmulationTitle": "Ryujinx - Arrêt de l'émulation", + "DialogStopEmulationMessage": "Êtes-vous sûr de vouloir arrêter l'émulation ?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Réseau", + "SettingsTabNetworkConnection": "Connexion réseau", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Mémoire CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Merci de mettre à jour Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Mise à jour désactivée !", + "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier Mods d'Atmosphère", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le répertoire alternatif de carte SD Atmosphère qui contient les Mods d'Application. Utile pour les mods qui sont conçu pour le vrai matériel.", + "ControllerSettingsRotate90": "Rotation 90° horaire", + "IconSize": "Taille d'icône", + "IconSizeTooltip": "Changer la taille des icônes de jeu", + "MenuBarOptionsShowConsole": "Afficher la console", + "ShaderCachePurgeError": "Erreur lors de la purge du cache du Shader à {0}: {1}", + "UserErrorNoKeys": "Clés introuvables", + "UserErrorNoFirmware": "Firmware introuvable", + "UserErrorFirmwareParsingFailed": "Erreur d'analyse du firmware", + "UserErrorApplicationNotFound": " Application introuvable", + "UserErrorUnknown": "Erreur inconnue", + "UserErrorUndefined": "Erreur non définie", + "UserErrorNoKeysDescription": "Ryujinx n'a pas pu trouver votre fichier 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx n'a pas trouvé de firmwares installés", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx n'a pas pu analyser le firmware fourni. Cela est généralement dû à des clés obsolètes.", + "UserErrorApplicationNotFoundDescription": "Ryujinx n'a pas pu trouver une application valide dans le chemin indiqué.", + "UserErrorUnknownDescription": "Une erreur inconnue est survenue!", + "UserErrorUndefinedDescription": "Une erreur inconnue est survenue ! Cela ne devrait pas se produire, merci de contacter un développeur !", + "OpenSetupGuideMessage": "Ouvrir le guide d'installation", + "NoUpdate": "Aucune mise à jour", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "Tous les types", + "Never": "Jamais", + "SwkbdMinCharacters": "Doit comporter au moins {0} caractères", + "SwkbdMinRangeCharacters": "Doit contenir {0}-{1} caractères en longueur", + "SoftwareKeyboard": "Clavier logiciel", + "SoftwareKeyboardModeNumbersOnly": "Doit être uniquement des chiffres", + "SoftwareKeyboardModeAlphabet": "Doit être uniquement des caractères non CJK", + "SoftwareKeyboardModeASCII": "Doit être uniquement du texte ASCII", + "DialogControllerAppletMessagePlayerRange": "L'application demande {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", + "DialogControllerAppletMessage": "L'application demande exactement {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", + "DialogControllerAppletDockModeSet": "Mode station d'accueil défini. Le portable est également invalide.\n\n", + "UpdaterRenaming": "Renommage des anciens fichiers...", + "UpdaterRenameFailed": "Impossible de renommer le fichier : {0}", + "UpdaterAddingFiles": "Ajout des nouveaux fichiers...", + "UpdaterExtracting": "Extraction de la mise à jour…", + "UpdaterDownloading": "Téléchargement de la mise à jour...", + "Game": "Jeu", + "Docked": "Attaché", + "Handheld": "Portable", + "ConnectionError": "Erreur de connexion.", + "AboutPageDeveloperListMore": "{0} et plus...", + "ApiError": "Erreur API.", + "LoadingHeading": "Chargement {0}", + "CompilingPPTC": "Compilation PTC", + "CompilingShaders": "Compilation des Shaders", + "AllKeyboards": "Tous les claviers", + "OpenFileDialogTitle": "Sélectionnez un fichier supporté à ouvrir", + "OpenFolderDialogTitle": "Sélectionnez un dossier avec un jeu décompressé", + "AllSupportedFormats": "Tous les formats supportés", + "RyujinxUpdater": "Mise à jour de Ryujinx", + "SettingsTabHotkeys": "Raccourcis clavier", + "SettingsTabHotkeysHotkeys": "Raccourcis clavier", + "SettingsTabHotkeysToggleVsyncHotkey": "Activer/désactiver la VSync :", + "SettingsTabHotkeysScreenshotHotkey": "Captures d'écran :", + "SettingsTabHotkeysShowUiHotkey": "Afficher UI :", + "SettingsTabHotkeysPauseHotkey": "Suspendre :", + "SettingsTabHotkeysToggleMuteHotkey": "Muet : ", + "ControllerMotionTitle": "Réglages du contrôle par mouvement", + "ControllerRumbleTitle": "Paramètres de Vibration", + "SettingsSelectThemeFileDialogTitle": "Sélectionnez un Fichier de Thème", + "SettingsXamlThemeFile": "Fichier thème Xaml", + "AvatarWindowTitle": "Gérer les Comptes - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Inconnu", + "Usage": "Utilisation", + "Writable": "Ecriture possible", + "SelectDlcDialogTitle": "Sélectionner les fichiers DLC", + "SelectUpdateDialogTitle": "Sélectionner les fichiers de mise à jour", + "UserProfileWindowTitle": "Gestionnaire de profils utilisateur", + "CheatWindowTitle": "Gestionnaire de triches", + "DlcWindowTitle": "Gestionnaire de contenus téléchargeables", + "UpdateWindowTitle": "Gestionnaire de mises à jour", + "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s) disponible pour {1} ({2})", + "UserProfilesEditProfile": "Éditer la sélection", + "Cancel": "Annuler", + "Save": "Enregistrer", + "Discard": "Abandonner", + "UserProfilesSetProfileImage": "Modifier l'image du profil", + "UserProfileEmptyNameError": "Le nom est requis", + "UserProfileNoImageError": "L'image du profil doit être définie", + "GameUpdateWindowHeading": "{0} mise(s) à jour disponible pour {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Augmenter la résolution:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuer la résolution :", + "UserProfilesName": "Nom :", + "UserProfilesUserId": "Identifiant de l'utilisateur :", + "SettingsTabGraphicsBackend": "API de Rendu", + "SettingsTabGraphicsBackendTooltip": "Interface Graphique à utiliser", + "SettingsEnableTextureRecompression": "Activer la recompression des textures", + "SettingsEnableTextureRecompressionTooltip": "Compresse certaines textures afin de réduire l'utilisation de la VRAM.\n\nRecommandé pour une utilisation avec des GPU qui ont moins de 4 Go de VRAM.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "SettingsTabGraphicsPreferredGpu": "GPU préféré", + "SettingsTabGraphicsPreferredGpuTooltip": "Sélectionnez la carte graphique qui sera utilisée avec l'interface graphique Vulkan.\n\nCela ne change pas le GPU qu'OpenGL utilisera.\n\nChoisissez le GPU noté \"dGPU\" si vous n'êtes pas sûr. S'il n'y en a pas, ne pas modifier.", + "SettingsAppRequiredRestartMessage": "Redémarrage de Ryujinx requis", + "SettingsGpuBackendRestartMessage": "Les paramètres de l'interface graphique ou du GPU ont été modifiés. Cela nécessitera un redémarrage pour être appliqué", + "SettingsGpuBackendRestartSubMessage": "\n\nVoulez-vous redémarrer maintenant?", + "RyujinxUpdaterMessage": "Voulez-vous mettre à jour Ryujinx vers la dernière version ?", + "SettingsTabHotkeysVolumeUpHotkey": "Augmenter le volume :", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuer le volume :", + "SettingsEnableMacroHLE": "Activer les macros HLE", + "SettingsEnableMacroHLETooltip": "Émulation de haut niveau du code de Macro GPU.\n\nAméliore les performances, mais peut causer des bugs graphiques dans certains jeux.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "SettingsEnableColorSpacePassthrough": "Traversée de l'espace colorimétrique", + "SettingsEnableColorSpacePassthroughTooltip": "Dirige l'interface graphique Vulkan pour qu'il transmette les informations de couleur sans spécifier d'espace colorimétrique. Pour les utilisateurs possédant des écrans haut de gamme, cela peut entraîner des couleurs plus vives, au détriment de l'exactitude des couleurs.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gérer les sauvegardes", + "DeleteUserSave": "Voulez-vous supprimer la sauvegarde de l'utilisateur pour ce jeu?", + "IrreversibleActionNote": "Cette action n'est pas réversible.", + "SaveManagerHeading": "Gérer les sauvegardes pour {0}", + "SaveManagerTitle": "Gestionnaire de sauvegarde", + "Name": "Nom ", + "Size": "Taille", + "Search": "Rechercher", + "UserProfilesRecoverLostAccounts": "Récupérer les comptes perdus", + "Recover": "Récupérer", + "UserProfilesRecoverHeading": "Des sauvegardes ont été trouvées pour les comptes suivants", + "UserProfilesRecoverEmptyList": "Aucun profil à restaurer", + "GraphicsAATooltip": "Applique l'anticrénelage au rendu du jeu", + "GraphicsAALabel": "Anticrénelage :", + "GraphicsScalingFilterLabel": "Filtre de mise à l'échelle :", + "GraphicsScalingFilterTooltip": "Active la mise à l'échelle du tampon d'image", + "GraphicsScalingFilterLevelLabel": "Niveau ", + "GraphicsScalingFilterLevelTooltip": "Définir le niveau du filtre de mise à l'échelle", + "SmaaLow": "SMAA Faible", + "SmaaMedium": "SMAA moyen", + "SmaaHigh": "SMAA Élevé", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Modifier Utilisateur", + "UserEditorTitleCreate": "Créer Utilisateur", + "SettingsTabNetworkInterface": "Interface Réseau :", + "NetworkInterfaceTooltip": "Interface réseau pour les fonctionnalités LAN", + "NetworkInterfaceDefault": "Par défaut", + "PackagingShaders": "Empaquetage des Shaders", + "AboutChangelogButton": "Voir le Changelog sur GitHub", + "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/he_IL.json b/src/Ryujinx.Ava/Assets/Locales/he_IL.json new file mode 100644 index 000000000..e5caf445a --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/he_IL.json @@ -0,0 +1,656 @@ +{ + "Language": "אנגלית (ארה\"ב)", + "MenuBarFileOpenApplet": "פתח יישומון", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "פתח את יישומון עורך ה- Mii במצב עצמאי", + "SettingsTabInputDirectMouseAccess": "גישה ישירה לעכבר", + "SettingsTabSystemMemoryManagerMode": "מצב מנהל זיכרון:", + "SettingsTabSystemMemoryManagerModeSoftware": "תוכנה", + "SettingsTabSystemMemoryManagerModeHost": "מארח (מהיר)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "מארח לא מבוקר (המהיר ביותר, לא בטוח)", + "SettingsTabSystemUseHypervisor": "השתמש ב Hypervisor", + "MenuBarFile": "_קובץ", + "MenuBarFileOpenFromFile": "_טען יישום מקובץ", + "MenuBarFileOpenUnpacked": "טען משחק _שאינו ארוז", + "MenuBarFileOpenEmuFolder": "פתח את תיקיית ריוג'ינקס", + "MenuBarFileOpenLogsFolder": "פתח את תיקיית קבצי הלוג", + "MenuBarFileExit": "_יציאה", + "MenuBarOptions": "הגדרות", + "MenuBarOptionsToggleFullscreen": "שנה מצב- מסך מלא", + "MenuBarOptionsStartGamesInFullscreen": "התחל משחקים במסך מלא", + "MenuBarOptionsStopEmulation": "עצור אמולציה", + "MenuBarOptionsSettings": "_הגדרות", + "MenuBarOptionsManageUserProfiles": "_נהל פרופילי משתמש", + "MenuBarActions": "_פעולות", + "MenuBarOptionsSimulateWakeUpMessage": "דמה הודעת השכמה", + "MenuBarActionsScanAmiibo": "סרוק אמיבו", + "MenuBarTools": "_כלים", + "MenuBarToolsInstallFirmware": "התקן קושחה", + "MenuBarFileToolsInstallFirmwareFromFile": "התקן קושחה מקובץ- ZIP/XCI", + "MenuBarFileToolsInstallFirmwareFromDirectory": "התקן קושחה מתוך תקייה", + "MenuBarToolsManageFileTypes": "ניהול סוגי קבצים", + "MenuBarToolsInstallFileTypes": "סוגי קבצי התקנה", + "MenuBarToolsUninstallFileTypes": "סוגי קבצי הסרה", + "MenuBarHelp": "עזרה", + "MenuBarHelpCheckForUpdates": "חפש עדכונים", + "MenuBarHelpAbout": "אודות", + "MenuSearch": "חפש...", + "GameListHeaderFavorite": "אהוב", + "GameListHeaderIcon": "סמל", + "GameListHeaderApplication": "שם", + "GameListHeaderDeveloper": "מפתח", + "GameListHeaderVersion": "גרסה", + "GameListHeaderTimePlayed": "זמן משחק", + "GameListHeaderLastPlayed": "שוחק לאחרונה", + "GameListHeaderFileExtension": "סיומת קובץ", + "GameListHeaderFileSize": "גודל הקובץ", + "GameListHeaderPath": "נתיב", + "GameListContextMenuOpenUserSaveDirectory": "פתח את תקיית השמור של המשתמש", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "פותח את תקיית השמור של המשתמש ביישום הנוכחי", + "GameListContextMenuOpenDeviceSaveDirectory": "פתח את תקיית השמור של המכשיר", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "פותח את הספרייה המכילה את שמור המכשיר של היישום", + "GameListContextMenuOpenBcatSaveDirectory": "פתח את תקיית השמור של ה-BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "פותח את תקיית שמור ה-BCAT של היישום", + "GameListContextMenuManageTitleUpdates": "מנהל עדכוני משחקים", + "GameListContextMenuManageTitleUpdatesToolTip": "פותח את חלון מנהל עדכוני המשחקים", + "GameListContextMenuManageDlc": "מנהל הרחבות", + "GameListContextMenuManageDlcToolTip": "פותח את חלון מנהל הרחבות המשחקים", + "GameListContextMenuOpenModsDirectory": "פתח את תקיית המודים", + "GameListContextMenuOpenModsDirectoryToolTip": "פותח את התקייה המכילה את המודים של היישום", + "GameListContextMenuCacheManagement": "ניהול מטמון", + "GameListContextMenuCacheManagementPurgePptc": "הוסף PPTC לתור בנייה מחדש", + "GameListContextMenuCacheManagementPurgePptcToolTip": "גרום ל-PPTC להבנות מחדש בפתיחה הבאה של המשחק", + "GameListContextMenuCacheManagementPurgeShaderCache": "ניקוי מטמון הצללות", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "מוחק את מטמון ההצללות של היישום", + "GameListContextMenuCacheManagementOpenPptcDirectory": "פתח את תקיית PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "פותח את התקייה של מטמון ה-PPTC של האפליקציה", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "פתח את תקיית המטמון של ההצללות", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "פותח את תקיית מטמון ההצללות של היישום", + "GameListContextMenuExtractData": "חילוץ נתונים", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "חלץ את קטע ה-ExeFS מתצורת היישום הנוכחית (כולל עדכונים)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "חלץ את קטע ה-RomFS מתצורת היישום הנוכחית (כולל עדכונים)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "חלץ את קטע ה-Logo מתצורת היישום הנוכחית (כולל עדכונים)", + "StatusBarGamesLoaded": "{1}/{0} משחקים נטענו", + "StatusBarSystemVersion": "גרסת מערכת: {0}", + "LinuxVmMaxMapCountDialogTitle": "זוהתה מגבלה נמוכה עבור מיפויי זיכרון", + "LinuxVmMaxMapCountDialogTextPrimary": "האם תרצה להגביר את הערך של vm.max_map_count ל{0}", + "LinuxVmMaxMapCountDialogTextSecondary": "משחקים מסוימים עלולים לייצר עוד מיפויי זיכרון ממה שמתאפשר. Ryujinx יקרוס ברגע שהמגבלה תחרוג.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "כן, עד האתחול הבא", + "LinuxVmMaxMapCountDialogButtonPersistent": "כן, לצמיתות", + "LinuxVmMaxMapCountWarningTextPrimary": "הכמות המירבית של מיפויי הזיכרון נמוכה מהמומלץ.", + "LinuxVmMaxMapCountWarningTextSecondary": "הערך הנוכחי של vm.max_map_count {0} נמוך מ{1}. משחקים מסוימים עלולים לייצר עוד מיפוי זיכרון ממה שמתאפשר.Ryujinx יקרוס ברגע שהמגבלה תחרוג.\n\nיתכן ותרצה להעלות את המגבלה הנוכחית או להתקין את pkexec, אשר יאפשר לRyujinx לסייע בכך.", + "Settings": "הגדרות", + "SettingsTabGeneral": "ממשק משתמש", + "SettingsTabGeneralGeneral": "כללי", + "SettingsTabGeneralEnableDiscordRichPresence": "הפעלת תצוגה עשירה בדיסקורד", + "SettingsTabGeneralCheckUpdatesOnLaunch": "בדוק אם קיימים עדכונים בהפעלה", + "SettingsTabGeneralShowConfirmExitDialog": "הראה דיאלוג \"אשר יציאה\"", + "SettingsTabGeneralHideCursor": "הסתר את הסמן", + "SettingsTabGeneralHideCursorNever": "אף פעם", + "SettingsTabGeneralHideCursorOnIdle": "במצב סרק", + "SettingsTabGeneralHideCursorAlways": "תמיד", + "SettingsTabGeneralGameDirectories": "תקיות משחקים", + "SettingsTabGeneralAdd": "הוסף", + "SettingsTabGeneralRemove": "הסר", + "SettingsTabSystem": "מערכת", + "SettingsTabSystemCore": "ליבה", + "SettingsTabSystemSystemRegion": "אזור מערכת:", + "SettingsTabSystemSystemRegionJapan": "יפן", + "SettingsTabSystemSystemRegionUSA": "ארה\"ב", + "SettingsTabSystemSystemRegionEurope": "אירופה", + "SettingsTabSystemSystemRegionAustralia": "אוסטרליה", + "SettingsTabSystemSystemRegionChina": "סין", + "SettingsTabSystemSystemRegionKorea": "קוריאה", + "SettingsTabSystemSystemRegionTaiwan": "טייוואן", + "SettingsTabSystemSystemLanguage": "שפת המערכת:", + "SettingsTabSystemSystemLanguageJapanese": "יפנית", + "SettingsTabSystemSystemLanguageAmericanEnglish": "אנגלית אמריקאית", + "SettingsTabSystemSystemLanguageFrench": "צרפתית", + "SettingsTabSystemSystemLanguageGerman": "גרמנית", + "SettingsTabSystemSystemLanguageItalian": "איטלקית", + "SettingsTabSystemSystemLanguageSpanish": "ספרדית", + "SettingsTabSystemSystemLanguageChinese": "סינית", + "SettingsTabSystemSystemLanguageKorean": "קוריאנית", + "SettingsTabSystemSystemLanguageDutch": "הולנדית", + "SettingsTabSystemSystemLanguagePortuguese": "פורטוגזית", + "SettingsTabSystemSystemLanguageRussian": "רוסית", + "SettingsTabSystemSystemLanguageTaiwanese": "טייוואנית", + "SettingsTabSystemSystemLanguageBritishEnglish": "אנגלית בריטית", + "SettingsTabSystemSystemLanguageCanadianFrench": "צרפתית קנדית", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "ספרדית אמריקה הלטינית", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "סינית פשוטה", + "SettingsTabSystemSystemLanguageTraditionalChinese": "סינית מסורתית", + "SettingsTabSystemSystemTimeZone": "אזור זמן מערכת:", + "SettingsTabSystemSystemTime": "זמן מערכת:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS בדיקות תקינות", + "SettingsTabSystemAudioBackend": "אחראי שמע:", + "SettingsTabSystemAudioBackendDummy": "גולם", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "האצות", + "SettingsTabSystemHacksNote": "עלול לגרום לאי יציבות", + "SettingsTabSystemExpandDramSize": "השתמש בפריסת זיכרון חלופית (נועד למפתחים)", + "SettingsTabSystemIgnoreMissingServices": "התעלם משירותים חסרים", + "SettingsTabGraphics": "גרפיקה", + "SettingsTabGraphicsAPI": "ממשק גראפי", + "SettingsTabGraphicsEnableShaderCache": "הפעל מטמון הצללות", + "SettingsTabGraphicsAnisotropicFiltering": "סינון אניסוטרופי:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "אוטומטי", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "קנה מידה של רזולוציה:", + "SettingsTabGraphicsResolutionScaleCustom": "מותאם אישית (לא מומלץ)", + "SettingsTabGraphicsResolutionScaleNative": "מקורי (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "יחס גובה-רוחב:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "מתח לגודל חלון", + "SettingsTabGraphicsDeveloperOptions": "אפשרויות מפתח", + "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", + "SettingsTabLogging": "רישום", + "SettingsTabLoggingLogging": "רישום", + "SettingsTabLoggingEnableLoggingToFile": "אפשר רישום לקובץ", + "SettingsTabLoggingEnableStubLogs": "אפשר רישום בדל", + "SettingsTabLoggingEnableInfoLogs": "אפשר רישום מידע", + "SettingsTabLoggingEnableWarningLogs": "אפשר רישום אזהרות", + "SettingsTabLoggingEnableErrorLogs": "אפשר רישום שגיאות", + "SettingsTabLoggingEnableTraceLogs": "הפעל רישום מעקבי", + "SettingsTabLoggingEnableGuestLogs": "הפעל רישום מארח", + "SettingsTabLoggingEnableFsAccessLogs": "אפשר רישום גישת קבצי מערכת", + "SettingsTabLoggingFsGlobalAccessLogMode": "מצב רישום גלובלי של גישת קבצי מערכת", + "SettingsTabLoggingDeveloperOptions": "אפשרויות מפתח", + "SettingsTabLoggingDeveloperOptionsNote": "אזהרה: יפחית ביצועים", + "SettingsTabLoggingGraphicsBackendLogLevel": "רישום גרפיקת קצה אחורי:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "כלום", + "SettingsTabLoggingGraphicsBackendLogLevelError": "שגיאה", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "האטות", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "הכל", + "SettingsTabLoggingEnableDebugLogs": "אפשר רישום ניפוי באגים", + "SettingsTabInput": "קלט", + "SettingsTabInputEnableDockedMode": "מצב עגינה", + "SettingsTabInputDirectKeyboardAccess": "גישה ישירה למקלדת", + "SettingsButtonSave": "שמירה", + "SettingsButtonClose": "סגירה", + "SettingsButtonOk": "אישור", + "SettingsButtonCancel": "ביטול", + "SettingsButtonApply": "החל", + "ControllerSettingsPlayer": "שחקן/ית", + "ControllerSettingsPlayer1": "שחקן/ית 1", + "ControllerSettingsPlayer2": "שחקן/ית 2", + "ControllerSettingsPlayer3": "שחקן/ית 3", + "ControllerSettingsPlayer4": "שחקן/ית 4", + "ControllerSettingsPlayer5": "שחקן/ית 5", + "ControllerSettingsPlayer6": "שחקן/ית 6", + "ControllerSettingsPlayer7": "שחקן/ית 7", + "ControllerSettingsPlayer8": "שחקן/ית 8", + "ControllerSettingsHandheld": "נייד", + "ControllerSettingsInputDevice": "מכשיר קלט", + "ControllerSettingsRefresh": "רענון", + "ControllerSettingsDeviceDisabled": "מושבת", + "ControllerSettingsControllerType": "סוג שלט", + "ControllerSettingsControllerTypeHandheld": "נייד", + "ControllerSettingsControllerTypeProController": "שלט פרו ", + "ControllerSettingsControllerTypeJoyConPair": "ג'ויקון הותאם", + "ControllerSettingsControllerTypeJoyConLeft": "ג'ויקון שמאלי ", + "ControllerSettingsControllerTypeJoyConRight": "ג'ויקון ימני", + "ControllerSettingsProfile": "פרופיל", + "ControllerSettingsProfileDefault": "ברירת המחדל", + "ControllerSettingsLoad": "טעינה", + "ControllerSettingsAdd": "הוספה", + "ControllerSettingsRemove": "הסר", + "ControllerSettingsButtons": "כפתורים", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "כפתורי כיוונים", + "ControllerSettingsDPadUp": "מעלה", + "ControllerSettingsDPadDown": "מטה", + "ControllerSettingsDPadLeft": "שמאלה", + "ControllerSettingsDPadRight": "ימינה", + "ControllerSettingsStickButton": "כפתור", + "ControllerSettingsStickUp": "למעלה", + "ControllerSettingsStickDown": "למטה", + "ControllerSettingsStickLeft": "שמאלה", + "ControllerSettingsStickRight": "ימינה", + "ControllerSettingsStickStick": "סטיק", + "ControllerSettingsStickInvertXAxis": "הפיכת הX של הסטיק", + "ControllerSettingsStickInvertYAxis": "הפיכת הY של הסטיק", + "ControllerSettingsStickDeadzone": "שטח מת:", + "ControllerSettingsLStick": "מקל שמאלי", + "ControllerSettingsRStick": "מקל ימני", + "ControllerSettingsTriggersLeft": "הדק שמאלי", + "ControllerSettingsTriggersRight": "הדק ימני", + "ControllerSettingsTriggersButtonsLeft": "כפתור הדק שמאלי", + "ControllerSettingsTriggersButtonsRight": "כפתור הדק ימני", + "ControllerSettingsTriggers": "הדקים", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "כפתורים משמאל", + "ControllerSettingsExtraButtonsRight": "כפתורים מימין", + "ControllerSettingsMisc": "שונות", + "ControllerSettingsTriggerThreshold": "סף הדק:", + "ControllerSettingsMotion": "תנועה", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "השתמש בתנועת CemuHook תואמת ", + "ControllerSettingsMotionControllerSlot": "מיקום שלט", + "ControllerSettingsMotionMirrorInput": "קלט מראה", + "ControllerSettingsMotionRightJoyConSlot": "מיקום ג'ויקון ימני", + "ControllerSettingsMotionServerHost": "מארח השרת:", + "ControllerSettingsMotionGyroSensitivity": "רגישות ג'ירוסקופ:", + "ControllerSettingsMotionGyroDeadzone": "שטח מת של הג'ירוסקופ:", + "ControllerSettingsSave": "שמירה", + "ControllerSettingsClose": "סגירה", + "UserProfilesSelectedUserProfile": "פרופיל המשתמש הנבחר:", + "UserProfilesSaveProfileName": "שמור שם פרופיל", + "UserProfilesChangeProfileImage": "שנה תמונת פרופיל", + "UserProfilesAvailableUserProfiles": "פרופילי משתמש זמינים:", + "UserProfilesAddNewProfile": "צור פרופיל", + "UserProfilesDelete": "מחיקה", + "UserProfilesClose": "סגור", + "ProfileNameSelectionWatermark": "בחרו כינוי", + "ProfileImageSelectionTitle": "בחירת תמונת פרופיל", + "ProfileImageSelectionHeader": "בחרו תמונת פרופיל", + "ProfileImageSelectionNote": "אתם יכולים לייבא תמונת פרופיל מותאמת אישית, או לבחור אווטאר מקושחת המערכת", + "ProfileImageSelectionImportImage": "ייבוא קובץ תמונה", + "ProfileImageSelectionSelectAvatar": "בחרו אוואטר קושחה", + "InputDialogTitle": "דיאלוג קלט", + "InputDialogOk": "בסדר", + "InputDialogCancel": "ביטול", + "InputDialogAddNewProfileTitle": "בחרו את שם הפרופיל", + "InputDialogAddNewProfileHeader": "אנא הזינו שם לפרופיל", + "InputDialogAddNewProfileSubtext": "(אורך מרבי: {0})", + "AvatarChoose": "בחרו דמות", + "AvatarSetBackgroundColor": "הגדר צבע רקע", + "AvatarClose": "סגור", + "ControllerSettingsLoadProfileToolTip": "טען פרופיל", + "ControllerSettingsAddProfileToolTip": "הוסף פרופיל", + "ControllerSettingsRemoveProfileToolTip": "הסר פרופיל", + "ControllerSettingsSaveProfileToolTip": "שמור פרופיל", + "MenuBarFileToolsTakeScreenshot": "צלם מסך", + "MenuBarFileToolsHideUi": "הסתר ממשק משתמש ", + "GameListContextMenuRunApplication": "הרץ יישום", + "GameListContextMenuToggleFavorite": "למתג העדפה", + "GameListContextMenuToggleFavoriteToolTip": "למתג סטטוס העדפה של משחק", + "SettingsTabGeneralTheme": "ערכת נושא", + "SettingsTabGeneralThemeCustomTheme": "נתיב ערכת נושא מותאמת אישית", + "SettingsTabGeneralThemeBaseStyle": "סגנון בסיס", + "SettingsTabGeneralThemeBaseStyleDark": "כהה", + "SettingsTabGeneralThemeBaseStyleLight": "בהיר", + "SettingsTabGeneralThemeEnableCustomTheme": "אפשר ערכת נושא מותאמת אישית", + "ButtonBrowse": "עיין", + "ControllerSettingsConfigureGeneral": "הגדר", + "ControllerSettingsRumble": "רטט", + "ControllerSettingsRumbleStrongMultiplier": "העצמת רטט חזק", + "ControllerSettingsRumbleWeakMultiplier": "מכפיל רטט חלש", + "DialogMessageSaveNotAvailableMessage": "אין שמור משחק עבור [{1:x16}] {0}", + "DialogMessageSaveNotAvailableCreateSaveMessage": "האם תרצה ליצור שמור משחק עבור המשחק הזה?", + "DialogConfirmationTitle": "ריוג'ינקס - אישור", + "DialogUpdaterTitle": "ריוג'ינקס - מעדכן", + "DialogErrorTitle": "ריוג'ינקס - שגיאה", + "DialogWarningTitle": "ריוג'ינקס - אזהרה", + "DialogExitTitle": "ריוג'ינקס - יציאה", + "DialogErrorMessage": "ריוג'ינקס נתקל בשגיאה", + "DialogExitMessage": "האם אתם בטוחים שאתם רוצים לסגור את ריוג'ינקס?", + "DialogExitSubMessage": "כל הנתונים שלא נשמרו יאבדו!", + "DialogMessageCreateSaveErrorMessage": "אירעה שגיאה ביצירת שמור המשחק שצויין: {0}", + "DialogMessageFindSaveErrorMessage": "אירעה שגיאה במציאת שמור המשחק שצויין: {0}", + "FolderDialogExtractTitle": "בחרו את התיקייה לחילוץ", + "DialogNcaExtractionMessage": "מלחץ {0} ממקטע {1}...", + "DialogNcaExtractionTitle": "ריוג'ינקס - מחלץ מקטע NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "כשל בחילוץ. ה-NCA הראשי לא היה קיים בקובץ שנבחר.", + "DialogNcaExtractionCheckLogErrorMessage": "כשל בחילוץ. קרא את קובץ הרישום למידע נוסף.", + "DialogNcaExtractionSuccessMessage": "החילוץ הושלם בהצלחה.", + "DialogUpdaterConvertFailedMessage": "המרת הגרסה הנוכחית של ריוג'ינקס נכשלה.", + "DialogUpdaterCancelUpdateMessage": "מבטל עדכון!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "אתם כבר משתמשים בגרסה המעודכנת ביותר של ריוג'ינקס!", + "DialogUpdaterFailedToGetVersionMessage": "אירעה שגיאה בעת ניסיון לקבל עדכונים מ-גיטהב. זה יכול להיגרם אם הגרסה המעודכנת האחרונה נוצרה על ידי פעולות של גיטהב. נסה שוב בעוד מספר דקות.", + "DialogUpdaterConvertFailedGithubMessage": "המרת גרסת ריוג'ינקס שהתקבלה מ-עדכון הגרסאות של גיטהב נכשלה.", + "DialogUpdaterDownloadingMessage": "מוריד עדכון...", + "DialogUpdaterExtractionMessage": "מחלץ עדכון...", + "DialogUpdaterRenamingMessage": "משנה את שם העדכון...", + "DialogUpdaterAddingFilesMessage": "מוסיף עדכון חדש...", + "DialogUpdaterCompleteMessage": "העדכון הושלם!", + "DialogUpdaterRestartMessage": "האם אתם רוצים להפעיל מחדש את ריוג'ינקס עכשיו?", + "DialogUpdaterArchNotSupportedMessage": "אינך מריץ ארכיטקטורת מערכת נתמכת!", + "DialogUpdaterArchNotSupportedSubMessage": "(רק מערכות x64 נתמכות!)", + "DialogUpdaterNoInternetMessage": "אתם לא מחוברים לאינטרנט!", + "DialogUpdaterNoInternetSubMessage": "אנא ודא שיש לך חיבור אינטרנט תקין!", + "DialogUpdaterDirtyBuildMessage": "אתם לא יכולים לעדכן מבנה מלוכלך של ריוג'ינקס!", + "DialogUpdaterDirtyBuildSubMessage": "אם אתם מחפשים גרסא נתמכת, אנא הורידו את ריוג'ינקס בכתובת https://ryujinx.org", + "DialogRestartRequiredMessage": "אתחול נדרש", + "DialogThemeRestartMessage": "ערכת הנושא נשמרה. יש צורך בהפעלה מחדש כדי להחיל את ערכת הנושא.", + "DialogThemeRestartSubMessage": "האם ברצונך להפעיל מחדש?", + "DialogFirmwareInstallEmbeddedMessage": "האם תרצו להתקין את הקושחה המוטמעת במשחק הזה? (קושחה {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \\nהאמולטור יופעל כעת.", + "DialogFirmwareNoFirmwareInstalledMessage": "לא מותקנת קושחה", + "DialogFirmwareInstalledMessage": "הקושחה {0} הותקנה", + "DialogInstallFileTypesSuccessMessage": "סוגי קבצים הותקנו בהצלחה!", + "DialogInstallFileTypesErrorMessage": "נכשל בהתקנת סוגי קבצים.", + "DialogUninstallFileTypesSuccessMessage": "סוגי קבצים הוסרו בהצלחה!", + "DialogUninstallFileTypesErrorMessage": "נכשל בהסרת סוגי קבצים.", + "DialogOpenSettingsWindowLabel": "פתח את חלון ההגדרות", + "DialogControllerAppletTitle": "יישומון בקר", + "DialogMessageDialogErrorExceptionMessage": "שגיאה בהצגת דיאלוג ההודעה: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "שגיאה בהצגת תוכנת המקלדת: {0}", + "DialogErrorAppletErrorExceptionMessage": "שגיאה בהצגת דיאלוג ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nלמידע נוסף על איך לתקן שגיאה זו, עקוב אחר מדריך ההתקנה שלנו.", + "DialogUserErrorDialogTitle": "שגיאת Ryujinx ({0})", + "DialogAmiiboApiTitle": "ממשק תכנות אמיבו", + "DialogAmiiboApiFailFetchMessage": "אירעה שגיאה בעת שליפת מידע מהממשק.", + "DialogAmiiboApiConnectErrorMessage": "לא ניתן להתחבר לממשק שרת האמיבו. ייתכן שהשירות מושבת או שתצטרך לוודא שהחיבור לאינטרנט שלך מקוון.", + "DialogProfileInvalidProfileErrorMessage": "הפרופיל {0} אינו תואם למערכת תצורת הקלט הנוכחית.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "לא ניתן להחליף את פרופיל ברירת המחדל", + "DialogProfileDeleteProfileTitle": "מוחק פרופיל", + "DialogProfileDeleteProfileMessage": "פעולה זו היא בלתי הפיכה, האם אתם בטוחים שברצונכם להמשיך?", + "DialogWarning": "אזהרה", + "DialogPPTCDeletionMessage": "אם תמשיכו אתם עומדים לגרום לבנייה מחדש של מטמון ה-PPTC עבור:\n\n{0}", + "DialogPPTCDeletionErrorMessage": "שגיאה בטיהור מטמון PPTC ב-{0}: {1}", + "DialogShaderDeletionMessage": "אם תמשיכו אתם עומדים למחוק את מטמון ההצללות עבור:\n\n{0}", + "DialogShaderDeletionErrorMessage": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}", + "DialogRyujinxErrorMessage": "ריוג'ינקס נתקלה בשגיאה", + "DialogInvalidTitleIdErrorMessage": "שגיאת ממשק משתמש: למשחק שנבחר לא קיים מזהה משחק", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "לא נמצאה קושחת מערכת תקפה ב-{0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "התקן קושחה {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "גירסת המערכת {0} תותקן.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nזה יחליף את גרסת המערכת הנוכחית {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nהאם ברצונך להמשיך?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "מתקין קושחה...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "גרסת המערכת {0} הותקנה בהצלחה.", + "DialogUserProfileDeletionWarningMessage": "לא יהיו פרופילים אחרים שייפתחו אם הפרופיל שנבחר יימחק", + "DialogUserProfileDeletionConfirmMessage": "האם ברצונך למחוק את הפרופיל שנבחר", + "DialogUserProfileUnsavedChangesTitle": "אזהרה - שינויים לא שמורים", + "DialogUserProfileUnsavedChangesMessage": "ביצעת שינויים בפרופיל משתמש זה שלא נשמרו.", + "DialogUserProfileUnsavedChangesSubMessage": "האם ברצונך למחוק את השינויים האחרונים?", + "DialogControllerSettingsModifiedConfirmMessage": "הגדרות השלט הנוכחי עודכנו.", + "DialogControllerSettingsModifiedConfirmSubMessage": "האם ברצונך לשמור?", + "DialogLoadNcaErrorMessage": "{0}. קובץ שגוי: {1}", + "DialogDlcNoDlcErrorMessage": "הקובץ שצוין אינו מכיל DLC עבור המשחק שנבחר!", + "DialogPerformanceCheckLoggingEnabledMessage": "הפעלת רישום מעקב, אשר נועד לשמש מפתחים בלבד.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את רישום המעקב. האם ברצונך להשבית את רישום המעקב כעת?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "הפעלת השלכת הצללה, שנועדה לשמש מפתחים בלבד.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את השלכת הצללה. האם ברצונך להשבית את השלכת הצללה עכשיו?", + "DialogLoadAppGameAlreadyLoadedMessage": "ישנו משחק שכבר רץ כעת", + "DialogLoadAppGameAlreadyLoadedSubMessage": "אנא הפסק את האמולציה או סגור את האמולטור לפני הפעלת משחק אחר.", + "DialogUpdateAddUpdateErrorMessage": "הקובץ שצוין אינו מכיל עדכון עבור המשחק שנבחר!", + "DialogSettingsBackendThreadingWarningTitle": "אזהרה - ריבוי תהליכי רקע", + "DialogSettingsBackendThreadingWarningMessage": "יש להפעיל מחדש את ריוג'ינקס לאחר שינוי אפשרות זו כדי שהיא תחול במלואה. בהתאם לפלטפורמה שלך, ייתכן שיהיה עליך להשבית ידנית את ריבוי ההליכים של ההתקן שלך בעת השימוש ב-ריוג'ינקס.", + "SettingsTabGraphicsFeaturesOptions": "אפשרויות", + "SettingsTabGraphicsBackendMultithreading": "אחראי גרפיקה רב-תהליכי:", + "CommonAuto": "אוטומטי", + "CommonOff": "כבוי", + "CommonOn": "דלוק", + "InputDialogYes": "כן", + "InputDialogNo": "לא", + "DialogProfileInvalidProfileNameErrorMessage": "שם הקובץ מכיל תווים לא חוקיים. אנא נסה שוב.", + "MenuBarOptionsPauseEmulation": "הפסק", + "MenuBarOptionsResumeEmulation": "המשך", + "AboutUrlTooltipMessage": "לחץ כדי לפתוח את אתר ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutDisclaimerMessage": "ריוג'ינקס אינה מזוהת עם נינטנדו,\nאו שוטפייה בכל דרך שהיא.", + "AboutAmiiboDisclaimerMessage": "ממשק אמיבו (www.amiiboapi.com) משומש בהדמיית האמיבו שלנו.", + "AboutPatreonUrlTooltipMessage": "לחץ כדי לפתוח את דף הפטראון של ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutGithubUrlTooltipMessage": "לחץ כדי לפתוח את דף הגיטהב של ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutDiscordUrlTooltipMessage": "לחץ כדי לפתוח הזמנה לשרת הדיסקורד של ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutTwitterUrlTooltipMessage": "לחץ כדי לפתוח את דף הטוויטר של Ryujinx בדפדפן ברירת המחדל שלך.", + "AboutRyujinxAboutTitle": "אודות:", + "AboutRyujinxAboutContent": "ריוג'ינקס הוא אמולטור עבור הנינטנדו סוויץ' (כל הזכויות שמורות).\nבבקשה תתמכו בנו בפטראון.\nקבל את כל החדשות האחרונות בטוויטר או בדיסקורד שלנו.\nמפתחים המעוניינים לתרום יכולים לקבל מידע נוסף ב-גיטהאב או ב-דיסקורד שלנו.", + "AboutRyujinxMaintainersTitle": "מתוחזק על ידי:", + "AboutRyujinxMaintainersContentTooltipMessage": "לחץ כדי לפתוח את דף התורמים בדפדפן ברירת המחדל שלך.", + "AboutRyujinxSupprtersTitle": "תמוך באמצעות Patreon", + "AmiiboSeriesLabel": "סדרת אמיבו", + "AmiiboCharacterLabel": "דמות", + "AmiiboScanButtonLabel": "סרוק את זה", + "AmiiboOptionsShowAllLabel": "הצג את כל האמיבואים", + "AmiiboOptionsUsRandomTagLabel": "האצה: השתמש בתג Uuid אקראי", + "DlcManagerTableHeadingEnabledLabel": "מאופשר", + "DlcManagerTableHeadingTitleIdLabel": "מזהה משחק", + "DlcManagerTableHeadingContainerPathLabel": "נתיב מכיל", + "DlcManagerTableHeadingFullPathLabel": "נתיב מלא", + "DlcManagerRemoveAllButton": "מחק הכל", + "DlcManagerEnableAllButton": "אפשר הכל", + "DlcManagerDisableAllButton": "השבת הכל", + "MenuBarOptionsChangeLanguage": "החלף שפה", + "MenuBarShowFileTypes": "הצג מזהה סוג קובץ", + "CommonSort": "מיין", + "CommonShowNames": "הצג שמות", + "CommonFavorite": "מועדף", + "OrderAscending": "סדר עולה", + "OrderDescending": "סדר יורד", + "SettingsTabGraphicsFeatures": "תכונות ושיפורים", + "ErrorWindowTitle": "חלון שגיאה", + "ToggleDiscordTooltip": "בחרו להציג את ריוג'ינקס או לא בפעילות הדיסקורד שלכם \"משוחק כרגע\".", + "AddGameDirBoxTooltip": "הזן תקיית משחקים כדי להוסיף לרשימה", + "AddGameDirTooltip": "הוסף תקיית משחקים לרשימה", + "RemoveGameDirTooltip": "הסר את תקיית המשחקים שנבחרה", + "CustomThemeCheckTooltip": "השתמש בעיצוב מותאם אישית של אבלוניה עבור ה-ממשק הגראפי כדי לשנות את המראה של תפריטי האמולטור", + "CustomThemePathTooltip": "נתיב לערכת נושא לממשק גראפי מותאם אישית", + "CustomThemeBrowseTooltip": "חפש עיצוב ממשק גראפי מותאם אישית", + "DockModeToggleTooltip": "מצב עגינה גורם למערכת המדומה להתנהג כ-נינטנדו סוויץ' בתחנת עגינתו. זה משפר את הנאמנות הגרפית ברוב המשחקים.\n לעומת זאת, השבתה של תכונה זו תגרום למערכת המדומה להתנהג כ- נינטנדו סוויץ' נייד, ולהפחית את איכות הגרפיקה.\n\nהגדירו את שלט שחקן 1 אם אתם מתכננים להשתמש במצב עגינה; הגדירו את פקדי כף היד אם אתם מתכננים להשתמש במצב נייד.\n\nמוטב להשאיר דלוק אם אתם לא בטוחים.", + "DirectKeyboardTooltip": "תמיכה ישירה למקלדת (HID). מספק גישה בשביל משחקים למקלדת שלך כמכשיר להזנת טקסט.", + "DirectMouseTooltip": "תמיכה ישירה לעכבר (HID). מספק גישה בשביל משחקים לעכבר שלך כהתקן הצבעה.", + "RegionTooltip": "שנה אזור מערכת", + "LanguageTooltip": "שנה שפת מערכת", + "TimezoneTooltip": "שנה את אזור הזמן של המערכת", + "TimeTooltip": "שנה זמן מערכת", + "VSyncToggleTooltip": "מדמה סנכרון אנכי של קונסולה. כלומר חסם הפריימים לרוב המשחקים; השבתה שלו עלולה לגרום למשחקים לרוץ מהר יותר או לגרום למסכי טעינה לקחת יותר זמן או להתקע.\n\nניתן לשנות מצב של תפריט זה בזמן משחק עם המקש לבחירתך. אנו ממליצים לעשות זאת אם אתם מתכננים להשבית אותו.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "PptcToggleTooltip": "שומר את פונקציות ה-JIT המתורגמות כך שלא יצטרכו לעבור תרגום שוב כאשר משחק עולה.\n\nמפחית תקיעות ומשפר מהירות עלייה של המערכת אחרי הפתיחה הראשונה של המשחק.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "FsIntegrityToggleTooltip": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "AudioBackendTooltip": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.", + "MemoryManagerTooltip": "שנה איך שזיכרון מארח מיוחד ומונגד. משפיע מאוד על ביצועי המעבד המדומה.\n\nמוטב להשאיר על מארח לא מבוקר אם לא בטוחים.", + "MemoryManagerSoftwareTooltip": "השתמש בתוכנת ה-page table בכדי להתייחס לתרגומים. דיוק מרבי לקונסולה אך המימוש הכי איטי.", + "MemoryManagerHostTooltip": "ממפה זיכרון ישירות לכתובת המארח. מהיר בהרבה ביכולות קימפול ה-JIT והריצה.", + "MemoryManagerUnsafeTooltip": "ממפה זיכרון ישירות, אך לא ממסך את הכתובת בתוך כתובת המארח לפני הגישה. מהיר, אך במחיר של הגנה. יישום המארח בעל גישה לזיכרון מכל מקום בריוג'ינקס, לכן הריצו איתו רק קבצים שאתם סומכים עליהם.", + "UseHypervisorTooltip": "השתמש ב- Hypervisor במקום JIT. משפר מאוד ביצועים כשניתן, אבל יכול להיות לא יציב במצבו הנוכחי.", + "DRamTooltip": "מנצל תצורת מצב-זיכרון חלופית לחכות את מכשיר הפיתוח של הסוויץ'.\n\nזה שימושי להחלפת חבילות מרקמים באיכותיים יותר או כאלו ברזולוציית 4k. לא משפר ביצועים.\n\nמוטב להשאיר כבוי אם לא בטוחים.", + "IgnoreMissingServicesTooltip": "מתעלם מפעולות שלא קיבלו מימוש במערכת ההפעלה Horizon OS. זה עלול לעזור לעקוף קריסות של היישום במשחקים מסויימים.\n\nמוטב להשאיר כבוי אם לא בטוחים.", + "GraphicsBackendThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", + "GalThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", + "ShaderCacheToggleTooltip": "שומר זכרון מטמון של הצללות, דבר שמפחית תקיעות בריצות מסוימות.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "ResolutionScaleTooltip": "שיפור רזולוצייה המאופשרת לעיבוד מטרות.", + "ResolutionScaleEntryTooltip": "שיפור רזולוציית נקודה צפה, כגון 1.5. הוא שיפור לא אינטגרלי הנוטה לגרום יותר בעיות או להקריס.", + "AnisotropyTooltip": "רמת סינון אניסוטרופי (מוגדר לאוטומטי כדי להשתמש בערך המבוקש על ידי המשחק)", + "AspectRatioTooltip": "יחס גובה-רוחב הוחל על חלון המעבד.", + "ShaderDumpPathTooltip": "נתיב השלכת הצללות גראפיות", + "FileLogTooltip": "שומר את רישומי שורת הפקודות לזיכרון, לא משפיע על ביצועי היישום.", + "StubLogTooltip": "מדפיס רישומים כושלים לשורת הפקודות. לא משפיע על ביצועי היישום.", + "InfoLogTooltip": "מדפיק רישומי מידע לשורת הפקודות. לא משפיע על ביצועי היישום.", + "WarnLogTooltip": "מדפיק רישומי הערות לשורת הפקודות. לא משפיע על ביצועי היישום.", + "ErrorLogTooltip": "מדפיס רישומי שגיאות לשורת הפקודות. לא משפיע על ביצועי היישום.", + "TraceLogTooltip": "מדפיק רישומי זיכרון לשורת הפקודות. לא משפיע על ביצועי היישום.", + "GuestLogTooltip": "מדפיס רישומי אורח לשורת הפקודות. לא משפיע על ביצועי היישום.", + "FileAccessLogTooltip": "מדפיס גישות לקבצי רישום לשורת הפקודות.", + "FSAccessLogModeTooltip": "מאפשר גישה לרישומי FS ליציאת שורת הפקודות. האפשרויות הינן 0-3.", + "DeveloperOptionTooltip": "השתמש בזהירות", + "OpenGlLogLevel": "דורש הפעלת רמות רישום מתאימות", + "DebugLogTooltip": "מדפיס הודעות יומן ניפוי באגים בשורת הפקודות.", + "LoadApplicationFileTooltip": "פתח סייר קבצים כדי לבחור קובץ תואם סוויץ' לטעינה", + "LoadApplicationFolderTooltip": "פתח סייר קבצים כדי לבחור יישום תואם סוויץ', לא ארוז לטעינה.", + "OpenRyujinxFolderTooltip": "פתח את תיקיית מערכת הקבצים ריוג'ינקס", + "OpenRyujinxLogsTooltip": "פותח את התיקיה שאליה נכתבים רישומים", + "ExitTooltip": "צא מריוג'ינקס", + "OpenSettingsTooltip": "פתח את חלון ההגדרות", + "OpenProfileManagerTooltip": "פתח את חלון מנהל פרופילי המשתמש", + "StopEmulationTooltip": "הפסק את הדמייה של המשחק הנוכחי וחזור למסך בחירת המשחק", + "CheckUpdatesTooltip": "בדוק אם קיימים עדכונים לריוג'ינקס", + "OpenAboutTooltip": "פתח את חלון אודות היישום", + "GridSize": "גודל רשת", + "GridSizeTooltip": "שנה את גודל המוצרים על הרשת.", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "פורטוגלית ברזילאית", + "AboutRyujinxContributorsButtonHeader": "צפה בכל התורמים", + "SettingsTabSystemAudioVolume": "עוצמת קול: ", + "AudioVolumeTooltip": "שנה עוצמת קול", + "SettingsTabSystemEnableInternetAccess": "אפשר גישה לאינטרנט בתור אורח/חיבור לאן", + "EnableInternetAccessTooltip": "מאפשר ליישומים באמולצייה להתחבר לאינטרנט.\n\nמשחקים עם חיבור לאן יכולים להתחבר אחד לשני כשאופצייה זו מופעלת והמערכות מתחברות לאותה נקודת גישה. כמו כן זה כולל שורות פקודות אמיתיות גם.\n\nאופצייה זו לא מאפשרת חיבור לשרתי נינטנדו. כשהאופצייה דלוקה היא עלולה לגרום לקריסת היישום במשחקים מסויימים שמנסים להתחבר לאינטרנט.\n\nמוטב להשאיר כבוי אם לא בטוחים.", + "GameListContextMenuManageCheatToolTip": "נהל צ'יטים", + "GameListContextMenuManageCheat": "נהל צ'יטים", + "ControllerSettingsStickRange": "טווח:", + "DialogStopEmulationTitle": "ריוג'ינקס - עצור אמולציה", + "DialogStopEmulationMessage": "האם אתם בטוחים שאתם רוצים לסגור את האמולצייה?", + "SettingsTabCpu": "מעבד", + "SettingsTabAudio": "שמע", + "SettingsTabNetwork": "רשת", + "SettingsTabNetworkConnection": "חיבור רשת", + "SettingsTabCpuCache": "מטמון מעבד", + "SettingsTabCpuMemory": "מצב מעבד", + "DialogUpdaterFlatpakNotSupportedMessage": "בבקשה עדכן את ריוג'ינקס דרך פלאטהב.", + "UpdaterDisabledWarningTitle": "מעדכן מושבת!", + "GameListContextMenuOpenSdModsDirectory": "פתח את תקיית המודים של אטמוספרה", + "GameListContextMenuOpenSdModsDirectoryToolTip": "פותח את תקיית כרטיס ה-SD החלופית של אטמוספרה המכילה את המודים של האפליקציה. שימושי עבור מודים ארוזים עבור חומרה אמיתית.", + "ControllerSettingsRotate90": "סובב 90° עם בכיוון השעון", + "IconSize": "גודל הסמל", + "IconSizeTooltip": "שנה את גודל הסמלים של משחקים", + "MenuBarOptionsShowConsole": "הצג שורת פקודות", + "ShaderCachePurgeError": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}", + "UserErrorNoKeys": "המפתחות לא נמצאו", + "UserErrorNoFirmware": "קושחה לא נמצאה", + "UserErrorFirmwareParsingFailed": "שגיאת ניתוח קושחה", + "UserErrorApplicationNotFound": "יישום לא נמצא", + "UserErrorUnknown": "שגיאה לא ידועה", + "UserErrorUndefined": "שגיאה לא מוגדרת", + "UserErrorNoKeysDescription": "ריוג'ינקס לא הצליח למצוא את קובץ ה-'prod.keys' שלך", + "UserErrorNoFirmwareDescription": "ריוג'ינקס לא הצליחה למצוא קושחה מותקנת", + "UserErrorFirmwareParsingFailedDescription": "ריוג'ינקס לא הצליחה לנתח את הקושחה שסופקה. זה נגרם בדרך כלל על ידי מפתחות לא עדכניים.", + "UserErrorApplicationNotFoundDescription": "ריוג'ינקס לא מצאה יישום תקין בנתיב הנתון", + "UserErrorUnknownDescription": "קרתה שגיאה לא ידועה!", + "UserErrorUndefinedDescription": "קרתה שגיאה לא מוגדרת! זה לא אמור לקרות, אנא צרו קשר עם מפתח!", + "OpenSetupGuideMessage": "פתח מדריך התקנה", + "NoUpdate": "אין עדכון", + "TitleUpdateVersionLabel": "גרסה {0}", + "RyujinxInfo": "ריוג'ינקס - מידע", + "RyujinxConfirm": "ריוג'ינקס - אישור", + "FileDialogAllTypes": "כל הסוגים", + "Never": "אף פעם", + "SwkbdMinCharacters": "לפחות {0} תווים", + "SwkbdMinRangeCharacters": "באורך {0}-{1} תווים", + "SoftwareKeyboard": "מקלדת וירטואלית", + "SoftwareKeyboardModeNumbersOnly": "מחויב להיות מספרי בלבד", + "SoftwareKeyboardModeAlphabet": "מחויב להיות ללא אותיות CJK", + "SoftwareKeyboardModeASCII": "מחויב להיות טקסט אסקיי", + "DialogControllerAppletMessagePlayerRange": "האפליקציה מבקשת {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", + "DialogControllerAppletMessage": "האפליקציה מבקשת בדיוק {0} שחקנים עם:\n\nסוגים: {1}\n\nשחקנים: {2}\n\n{3}אנא פתח את ההגדרות והגדר מחדש את הקלט כעת או סגור.", + "DialogControllerAppletDockModeSet": "במצב עגינה. בנוסף מצב נייד לא אפשרי.\n\n", + "UpdaterRenaming": "משנה שמות של קבצים ישנים...", + "UpdaterRenameFailed": "המעדכן לא הצליח לשנות את שם הקובץ: {0}", + "UpdaterAddingFiles": "מוסיף קבצים חדשים...", + "UpdaterExtracting": "מחלץ עדכון...", + "UpdaterDownloading": "מוריד עדכון...", + "Game": "משחק", + "Docked": "בתחנת עגינה", + "Handheld": "נייד", + "ConnectionError": "שגיאת חיבור", + "AboutPageDeveloperListMore": "{0} ועוד...", + "ApiError": "שגיאת ממשק.", + "LoadingHeading": "טוען {0}", + "CompilingPPTC": "קימפול PTC", + "CompilingShaders": "קימפול הצללות", + "AllKeyboards": "כל המקלדות", + "OpenFileDialogTitle": "בחר קובץ נתמך לפתיחה", + "OpenFolderDialogTitle": "בחר תיקיה עם משחק לא ארוז", + "AllSupportedFormats": "כל הפורמטים הנתמכים", + "RyujinxUpdater": "מעדכן ריוג'ינקס", + "SettingsTabHotkeys": "מקשי קיצור במקלדת", + "SettingsTabHotkeysHotkeys": "מקשי קיצור במקלדת", + "SettingsTabHotkeysToggleVsyncHotkey": "שנה סינכרון אנכי:", + "SettingsTabHotkeysScreenshotHotkey": "צילום מסך:", + "SettingsTabHotkeysShowUiHotkey": "הצג ממשק משתמש:", + "SettingsTabHotkeysPauseHotkey": "הפסק:", + "SettingsTabHotkeysToggleMuteHotkey": "השתק:", + "ControllerMotionTitle": "הגדרות שליטת תנועות ג'ירוסקופ", + "ControllerRumbleTitle": "הגדרות רטט", + "SettingsSelectThemeFileDialogTitle": "בחרו קובץ ערכת נושא", + "SettingsXamlThemeFile": "קובץ ערכת נושא Xaml", + "AvatarWindowTitle": "ניהול חשבונות - אוואטר", + "Amiibo": "אמיבו", + "Unknown": "לא ידוע", + "Usage": "שימוש", + "Writable": "ניתן לכתיבה", + "SelectDlcDialogTitle": "בחרו קבצי הרחבות משחק", + "SelectUpdateDialogTitle": "בחרו קבצי עדכון", + "UserProfileWindowTitle": "ניהול פרופילי משתמש", + "CheatWindowTitle": "נהל צ'יטים למשחק", + "DlcWindowTitle": "נהל הרחבות משחק עבור {0} ({1})", + "UpdateWindowTitle": "נהל עדכוני משחקים", + "CheatWindowHeading": "צ'יטים זמינים עבור {0} [{1}]", + "BuildId": "מזהה בניה:", + "DlcWindowHeading": "{0} הרחבות משחק", + "UserProfilesEditProfile": "ערוך נבחר/ים", + "Cancel": "בטל", + "Save": "שמור", + "Discard": "השלך", + "UserProfilesSetProfileImage": "הגדר תמונת פרופיל", + "UserProfileEmptyNameError": "נדרש שם", + "UserProfileNoImageError": "נדרשת תמונת פרופיל", + "GameUpdateWindowHeading": "נהל עדכונים עבור {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "שפר רזולוציה:", + "SettingsTabHotkeysResScaleDownHotkey": "הפחת רזולוציה:", + "UserProfilesName": "שם:", + "UserProfilesUserId": "מזהה משתמש:", + "SettingsTabGraphicsBackend": "אחראי גראפיקה", + "SettingsTabGraphicsBackendTooltip": "אחראי גראפיקה לשימוש", + "SettingsEnableTextureRecompression": "אפשר דחיסה מחדש של המרקם", + "SettingsEnableTextureRecompressionTooltip": " דוחס מרקמים מסויימים להפחתת השימוש בראם הוירטואלי.\n\nמומלץ לשימוש עם כרטיס גראפי בעל פחות מ-4GiB בראם הוירטואלי.\n\nמוטב להשאיר כבוי אם אינכם בטוחים.", + "SettingsTabGraphicsPreferredGpu": "כרטיס גראפי מועדף", + "SettingsTabGraphicsPreferredGpuTooltip": "בחר את הכרטיס הגראפי שישומש עם הגראפיקה של וולקאן.\n\nדבר זה לא משפיע על הכרטיס הגראפי שישומש עם OpenGL.\n\nמוטב לבחור את ה-GPU המסומן כ-\"dGPU\" אם אינכם בטוחים, אם זו לא אופצייה, אל תשנו דבר.", + "SettingsAppRequiredRestartMessage": "ריוג'ינקס דורש אתחול מחדש", + "SettingsGpuBackendRestartMessage": "הגדרות אחראי גרפיקה או כרטיס גראפי שונו. זה ידרוש הפעלה מחדש כדי להחיל שינויים", + "SettingsGpuBackendRestartSubMessage": "האם ברצונך להפעיל מחדש כעט?", + "RyujinxUpdaterMessage": "האם ברצונך לעדכן את ריוג'ינקס לגרסא האחרונה?", + "SettingsTabHotkeysVolumeUpHotkey": "הגבר את עוצמת הקול:", + "SettingsTabHotkeysVolumeDownHotkey": "הנמך את עוצמת הקול:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "אמולצייה ברמה גבוהה של כרטיס גראפי עם קוד מקרו.\n\nמשפר את ביצועי היישום אך עלול לגרום לגליצ'ים חזותיים במשחקים מסויימים.\n\nמוטב להשאיר דלוק אם אינך בטוח.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "שמע", + "UserProfilesManageSaves": "נהל שמורים", + "DeleteUserSave": "האם ברצונך למחוק את תקיית השמור למשחק זה?", + "IrreversibleActionNote": "הפעולה הזו בלתי הפיכה.", + "SaveManagerHeading": "נהל שמורי משחק עבור {0} ({1})", + "SaveManagerTitle": "מנהל שמירות", + "Name": "שם", + "Size": "גודל", + "Search": "חפש", + "UserProfilesRecoverLostAccounts": "שחזר חשבון שאבד", + "Recover": "שחזר", + "UserProfilesRecoverHeading": "שמורים נמצאו לחשבונות הבאים", + "UserProfilesRecoverEmptyList": "אין פרופילים לשחזור", + "GraphicsAATooltip": "מחיל החלקת-עקומות על עיבוד המשחק", + "GraphicsAALabel": "החלקת-עקומות:", + "GraphicsScalingFilterLabel": "מסנן מידת איכות:", + "GraphicsScalingFilterTooltip": "אפשר מידת איכות מסוג איגור-תמונה", + "GraphicsScalingFilterLevelLabel": "רמה", + "GraphicsScalingFilterLevelTooltip": "קבע מידת איכות תמונה לפי רמת סינון", + "SmaaLow": "SMAA נמוך", + "SmaaMedium": "SMAA בינוני", + "SmaaHigh": "SMAA גבוהה", + "SmaaUltra": "SMAA אולטרה", + "UserEditorTitle": "ערוך משתמש", + "UserEditorTitleCreate": "צור משתמש", + "SettingsTabNetworkInterface": "ממשק רשת", + "NetworkInterfaceTooltip": "ממשק הרשת המשומש עבור יכולות לאן", + "NetworkInterfaceDefault": "ברירת המחדל", + "PackagingShaders": "אורז הצללות", + "AboutChangelogButton": "צפה במידע אודות שינויים בגיטהב", + "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/it_IT.json b/src/Ryujinx.Ava/Assets/Locales/it_IT.json new file mode 100644 index 000000000..5aff7a7ec --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/it_IT.json @@ -0,0 +1,656 @@ +{ + "Language": "Italiano", + "MenuBarFileOpenApplet": "Apri Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'applet Mii Editor in modalità Standalone", + "SettingsTabInputDirectMouseAccess": "Accesso diretto mouse", + "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (veloce)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (più veloce, non sicura)", + "SettingsTabSystemUseHypervisor": "Usa Hypervisor", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Carica applicazione da un file", + "MenuBarFileOpenUnpacked": "Carica _gioco estratto", + "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", + "MenuBarFileOpenLogsFolder": "Apri cartella dei Log", + "MenuBarFileExit": "_Esci", + "MenuBarOptions": "Opzioni", + "MenuBarOptionsToggleFullscreen": "Schermo intero", + "MenuBarOptionsStartGamesInFullscreen": "Avvia i giochi a schermo intero", + "MenuBarOptionsStopEmulation": "Ferma emulazione", + "MenuBarOptionsSettings": "_Impostazioni", + "MenuBarOptionsManageUserProfiles": "_Gestisci i profili utente", + "MenuBarActions": "_Azioni", + "MenuBarOptionsSimulateWakeUpMessage": "Simula messaggio Wake-up", + "MenuBarActionsScanAmiibo": "Scansiona un Amiibo", + "MenuBarTools": "_Strumenti", + "MenuBarToolsInstallFirmware": "Installa firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installa un firmware da file XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installa un firmare da una cartella", + "MenuBarToolsManageFileTypes": "Gestisci i tipi di file", + "MenuBarToolsInstallFileTypes": "Installa i tipi di file", + "MenuBarToolsUninstallFileTypes": "Disinstalla i tipi di file", + "MenuBarHelp": "Aiuto", + "MenuBarHelpCheckForUpdates": "Controlla aggiornamenti", + "MenuBarHelpAbout": "Informazioni", + "MenuSearch": "Cerca...", + "GameListHeaderFavorite": "Preferito", + "GameListHeaderIcon": "Icona", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Sviluppatore", + "GameListHeaderVersion": "Versione", + "GameListHeaderTimePlayed": "Tempo di gioco", + "GameListHeaderLastPlayed": "Giocato l'ultima volta", + "GameListHeaderFileExtension": "Estensione", + "GameListHeaderFileSize": "Dimensione file", + "GameListHeaderPath": "Percorso", + "GameListContextMenuOpenUserSaveDirectory": "Apri la cartella salvataggi dell'utente", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco dell'utente", + "GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dispositivo dell'utente", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo", + "GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella BCAT dell'utente", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione", + "GameListContextMenuManageTitleUpdates": "Gestisci aggiornamenti del gioco", + "GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco", + "GameListContextMenuManageDlc": "Gestici DLC", + "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC", + "GameListContextMenuOpenModsDirectory": "Apri cartella delle mod", + "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione", + "GameListContextMenuCacheManagement": "Gestione della cache", + "GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione", + "GameListContextMenuCacheManagementPurgeShaderCache": "Pulisci shader cache", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la shader cache dell'applicazione", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Apri cartella PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la PPTC cache dell'applicazione", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri cartella shader cache", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la shader cache dell'applicazione", + "GameListContextMenuExtractData": "Estrai dati", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "StatusBarGamesLoaded": "{0}/{1} Giochi caricati", + "StatusBarSystemVersion": "Versione di sistema: {0}", + "LinuxVmMaxMapCountDialogTitle": "Limite basso per le mappature di memoria rilevate", + "LinuxVmMaxMapCountDialogTextPrimary": "Vuoi aumentare il valore di vm.max_map_count a {0}?", + "LinuxVmMaxMapCountDialogTextSecondary": "Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sì, fino al prossimo riavvio", + "LinuxVmMaxMapCountDialogButtonPersistent": "Sì, in modo permanente", + "LinuxVmMaxMapCountWarningTextPrimary": "La quantità massima di mappature di memoria è inferiore a quella consigliata.", + "LinuxVmMaxMapCountWarningTextSecondary": "Il valore corrente di vm.max_map_count ({0}) è inferiore a {1}. Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.\n\nPotresti voler aumentare manualmente il limite o installare pkexec, il che permette a Ryujinx di assisterlo.", + "Settings": "Impostazioni", + "SettingsTabGeneral": "Interfaccia utente", + "SettingsTabGeneralGeneral": "Generali", + "SettingsTabGeneralEnableDiscordRichPresence": "Attiva Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Controlla aggiornamenti all'avvio", + "SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"", + "SettingsTabGeneralHideCursor": "Nascondi il cursore:", + "SettingsTabGeneralHideCursorNever": "Mai", + "SettingsTabGeneralHideCursorOnIdle": "Nascondi cursore inattivo", + "SettingsTabGeneralHideCursorAlways": "Sempre", + "SettingsTabGeneralGameDirectories": "Cartelle dei giochi", + "SettingsTabGeneralAdd": "Aggiungi", + "SettingsTabGeneralRemove": "Rimuovi", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principale", + "SettingsTabSystemSystemRegion": "Regione del sistema:", + "SettingsTabSystemSystemRegionJapan": "Giappone", + "SettingsTabSystemSystemRegionUSA": "Stati Uniti d'America", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "Cina", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Lingua del sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Giapponese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglese americano", + "SettingsTabSystemSystemLanguageFrench": "Francese", + "SettingsTabSystemSystemLanguageGerman": "Tedesco", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Spagnolo", + "SettingsTabSystemSystemLanguageChinese": "Cinese", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Olandese", + "SettingsTabSystemSystemLanguagePortuguese": "Portoghese", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglese britannico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francese canadese", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Spagnolo latino americano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Cinese semplificato", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Cinese tradizionale", + "SettingsTabSystemSystemTimeZone": "Fuso orario del sistema:", + "SettingsTabSystemSystemTime": "Data e ora del sistema:", + "SettingsTabSystemEnableVsync": "Attiva VSync", + "SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS", + "SettingsTabSystemAudioBackend": "Backend audio:", + "SettingsTabSystemAudioBackendDummy": "Manichino", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "AudioIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Trucchi", + "SettingsTabSystemHacksNote": " (Possono causare instabilità)", + "SettingsTabSystemExpandDramSize": "Espandi dimensione DRAM a 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignora servizi mancanti", + "SettingsTabGraphics": "Grafica", + "SettingsTabGraphicsAPI": "API Grafiche", + "SettingsTabGraphicsEnableShaderCache": "Attiva Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotropico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Scala della risoluzione:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizzata (Non raccomandata)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Rapporto d'aspetto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Adatta alla finestra", + "SettingsTabGraphicsDeveloperOptions": "Opzioni da sviluppatore", + "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shaders:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salva i log su file", + "SettingsTabLoggingEnableStubLogs": "Attiva Stub Logs", + "SettingsTabLoggingEnableInfoLogs": "Attiva Info Logs", + "SettingsTabLoggingEnableWarningLogs": "Attiva Warning Logs", + "SettingsTabLoggingEnableErrorLogs": "Attiva Error Logs", + "SettingsTabLoggingEnableTraceLogs": "Attiva Trace Logs", + "SettingsTabLoggingEnableGuestLogs": "Attiva Guest Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log accesso globale Fs:", + "SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)", + "SettingsTabLoggingDeveloperOptionsNote": "ATTENZIONE: ridurrà le prestazioni", + "SettingsTabLoggingGraphicsBackendLogLevel": "Livello registro backend grafico:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Errore", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto", + "SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Attiva modalità TV", + "SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera", + "SettingsButtonSave": "Salva", + "SettingsButtonClose": "Chiudi", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancella", + "SettingsButtonApply": "Applica", + "ControllerSettingsPlayer": "Giocatore", + "ControllerSettingsPlayer1": "Giocatore 1", + "ControllerSettingsPlayer2": "Giocatore 2", + "ControllerSettingsPlayer3": "Giocatore 3", + "ControllerSettingsPlayer4": "Giocatore 4", + "ControllerSettingsPlayer5": "Giocatore 5", + "ControllerSettingsPlayer6": "Giocatore 6", + "ControllerSettingsPlayer7": "Giocatore 7", + "ControllerSettingsPlayer8": "Giocatore 8", + "ControllerSettingsHandheld": "Portatile", + "ControllerSettingsInputDevice": "Dispositivo di input", + "ControllerSettingsRefresh": "Ricarica", + "ControllerSettingsDeviceDisabled": "Disabilitato", + "ControllerSettingsControllerType": "Tipo di controller", + "ControllerSettingsControllerTypeHandheld": "Portatile", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Coppia di JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon sinistro", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon destro", + "ControllerSettingsProfile": "Profilo", + "ControllerSettingsProfileDefault": "Predefinito", + "ControllerSettingsLoad": "Carica", + "ControllerSettingsAdd": "Aggiungi", + "ControllerSettingsRemove": "Rimuovi", + "ControllerSettingsButtons": "Pulsanti", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croce direzionale", + "ControllerSettingsDPadUp": "Su", + "ControllerSettingsDPadDown": "Giù", + "ControllerSettingsDPadLeft": "Sinistra", + "ControllerSettingsDPadRight": "Destra", + "ControllerSettingsStickButton": "Pulsante", + "ControllerSettingsStickUp": "Su", + "ControllerSettingsStickDown": "Giù", + "ControllerSettingsStickLeft": "Sinistra", + "ControllerSettingsStickRight": "Destra", + "ControllerSettingsStickStick": "Levetta", + "ControllerSettingsStickInvertXAxis": "Inverti levetta X", + "ControllerSettingsStickInvertYAxis": "Inverti levetta Y", + "ControllerSettingsStickDeadzone": "Zona morta:", + "ControllerSettingsLStick": "Stick sinistro", + "ControllerSettingsRStick": "Stick destro", + "ControllerSettingsTriggersLeft": "Grilletto sinistro", + "ControllerSettingsTriggersRight": "Grilletto destro", + "ControllerSettingsTriggersButtonsLeft": "Pulsante dorsale sinistro", + "ControllerSettingsTriggersButtonsRight": "Pulsante dorsale destro", + "ControllerSettingsTriggers": "Grilletti", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Tasto sinitro", + "ControllerSettingsExtraButtonsRight": "Tasto destro", + "ControllerSettingsMisc": "Varie", + "ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:", + "ControllerSettingsMotion": "Movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usa sensore compatibile con CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot del controller:", + "ControllerSettingsMotionMirrorInput": "Input specchiato", + "ControllerSettingsMotionRightJoyConSlot": "Slot JoyCon destro:", + "ControllerSettingsMotionServerHost": "Server:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilità del giroscopio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta del giroscopio:", + "ControllerSettingsSave": "Salva", + "ControllerSettingsClose": "Chiudi", + "UserProfilesSelectedUserProfile": "Profilo utente selezionato:", + "UserProfilesSaveProfileName": "Salva nome del profilo", + "UserProfilesChangeProfileImage": "Cambia immagine profilo", + "UserProfilesAvailableUserProfiles": "Profili utente disponibili:", + "UserProfilesAddNewProfile": "Aggiungi nuovo profilo", + "UserProfilesDelete": "Elimina", + "UserProfilesClose": "Chiudi", + "ProfileNameSelectionWatermark": "Scegli un soprannome", + "ProfileImageSelectionTitle": "Selezione dell'immagine profilo", + "ProfileImageSelectionHeader": "Scegli un'immagine profilo", + "ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema", + "ProfileImageSelectionImportImage": "Importa file immagine", + "ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware", + "InputDialogTitle": "Finestra di dialogo ", + "InputDialogOk": "OK", + "InputDialogCancel": "Annulla", + "InputDialogAddNewProfileTitle": "Scegli il nome profilo", + "InputDialogAddNewProfileHeader": "Digita un nome profilo", + "InputDialogAddNewProfileSubtext": "(Lunghezza massima: {0})", + "AvatarChoose": "Scegli", + "AvatarSetBackgroundColor": "Imposta colore di sfondo", + "AvatarClose": "Chiudi", + "ControllerSettingsLoadProfileToolTip": "Carica profilo", + "ControllerSettingsAddProfileToolTip": "Aggiungi profilo", + "ControllerSettingsRemoveProfileToolTip": "Rimuovi profilo", + "ControllerSettingsSaveProfileToolTip": "Salva profilo", + "MenuBarFileToolsTakeScreenshot": "Fai uno screenshot", + "MenuBarFileToolsHideUi": "Nascondi UI", + "GameListContextMenuRunApplication": "Esegui applicazione", + "GameListContextMenuToggleFavorite": "Preferito", + "GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Percorso del tema personalizzato", + "SettingsTabGeneralThemeBaseStyle": "Modalità", + "SettingsTabGeneralThemeBaseStyleDark": "Scura", + "SettingsTabGeneralThemeBaseStyleLight": "Chiara", + "SettingsTabGeneralThemeEnableCustomTheme": "Attiva tema personalizzato", + "ButtonBrowse": "Sfoglia", + "ControllerSettingsConfigureGeneral": "Configura", + "ControllerSettingsRumble": "Vibrazione", + "ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte", + "ControllerSettingsRumbleWeakMultiplier": "Moltiplicatore vibrazione debole", + "DialogMessageSaveNotAvailableMessage": "Non ci sono dati di salvataggio per {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Vuoi creare dei dati di salvataggio per questo gioco?", + "DialogConfirmationTitle": "Ryujinx - Conferma", + "DialogUpdaterTitle": "Ryujinx - Aggiornamento", + "DialogErrorTitle": "Ryujinx - Errore", + "DialogWarningTitle": "Ryujinx - Avviso", + "DialogExitTitle": "Ryujinx - Esci", + "DialogErrorMessage": "Ryujinx ha riscontrato un problema", + "DialogExitMessage": "Sei sicuro di voler chiudere Ryujinx?", + "DialogExitSubMessage": "Tutti i dati non salvati andranno persi!", + "DialogMessageCreateSaveErrorMessage": "C'è stato un errore durante la creazione dei dati di salvataggio: {0}", + "DialogMessageFindSaveErrorMessage": "C'è stato un errore durante la ricerca dei dati di salvataggio: {0}", + "FolderDialogExtractTitle": "Scegli una cartella in cui estrarre", + "DialogNcaExtractionMessage": "Estrazione della sezione {0} da {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Estrattore sezione NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "L'estrazione è fallita. L'NCA principale non era presente nel file selezionato.", + "DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Leggi il log per più informazioni.", + "DialogNcaExtractionSuccessMessage": "Estrazione completata con successo.", + "DialogUpdaterConvertFailedMessage": "La conversione dell'attuale versione di Ryujinx è fallita.", + "DialogUpdaterCancelUpdateMessage": "Annullando l'aggiornamento!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Stai già usando la versione più recente di Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore durante il tentativo di ottenere informazioni sulla versione da GitHub Release. Ciò può essere causato se una nuova versione viene compilata da GitHub Actions. Riprova tra qualche minuto.", + "DialogUpdaterConvertFailedGithubMessage": "La conversione della versione di Ryujinx ricevuta da Github Release è fallita.", + "DialogUpdaterDownloadingMessage": "Download dell'aggiornamento...", + "DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...", + "DialogUpdaterRenamingMessage": "Rinominazione dell'aggiornamento...", + "DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...", + "DialogUpdaterCompleteMessage": "Aggiornamento completato!", + "DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?", + "DialogUpdaterArchNotSupportedMessage": "Non stai usando un'architettura di sistema supportata!", + "DialogUpdaterArchNotSupportedSubMessage": "(Solo sistemi x64 sono supportati!)", + "DialogUpdaterNoInternetMessage": "Non sei connesso ad Internet!", + "DialogUpdaterNoInternetSubMessage": "Verifica di avere una connessione ad Internet funzionante!", + "DialogUpdaterDirtyBuildMessage": "Non puoi aggiornare una Dirty build di Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Scarica Ryujinx da https://ryujinx.org/ se stai cercando una versione supportata.", + "DialogRestartRequiredMessage": "Riavvio richiesto", + "DialogThemeRestartMessage": "Il tema è stato salvato. E' richiesto un riavvio per applicare un tema.", + "DialogThemeRestartSubMessage": "Vuoi riavviare?", + "DialogFirmwareInstallEmbeddedMessage": "Vuoi installare il firmware incorporato in questo gioco? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.", + "DialogFirmwareNoFirmwareInstalledMessage": "Nessun firmware installato", + "DialogFirmwareInstalledMessage": "Il firmware {0} è stato installato", + "DialogInstallFileTypesSuccessMessage": "Tipi di file installati con successo!", + "DialogInstallFileTypesErrorMessage": "Impossibile installare i tipi di file.", + "DialogUninstallFileTypesSuccessMessage": "Tipi di file disinstallati con successo!", + "DialogUninstallFileTypesErrorMessage": "Disinstallazione dei tipi di file non riuscita.", + "DialogOpenSettingsWindowLabel": "Apri finestra delle impostazioni", + "DialogControllerAppletTitle": "Applet del controller", + "DialogMessageDialogErrorExceptionMessage": "Errore nella visualizzazione del Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Errore nella visualizzazione della tastiera software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Errore nella visualizzazione dell'ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPer più informazioni su come risolvere questo errore, segui la nostra guida all'installazione.", + "DialogUserErrorDialogTitle": "Errore di Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Si è verificato un errore durante il recupero delle informazioni dall'API.", + "DialogAmiiboApiConnectErrorMessage": "Impossibile connettersi al server Amiibo API. Il servizio potrebbe essere fuori uso o potresti dover verificare che la tua connessione internet sia online.", + "DialogProfileInvalidProfileErrorMessage": "Il profilo {0} è incompatibile con l'attuale sistema di configurazione input.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Il profilo predefinito non può essere sovrascritto", + "DialogProfileDeleteProfileTitle": "Eliminazione profilo", + "DialogProfileDeleteProfileMessage": "Quest'azione è irreversibile, sei sicuro di voler continuare?", + "DialogWarning": "Avviso", + "DialogPPTCDeletionMessage": "Stai per eliminare la PPTC cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della PPTC cache a {0}: {1}", + "DialogShaderDeletionMessage": "Stai per eliminare la Shader cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della Shader cache a {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore", + "DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un ID titolo valido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware del sistema valido non è stato trovato in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installa firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "La versione del sistema {0} sarà installata.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nQuesta sostituirà l'attuale versione di sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVuoi continuare?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installazione del firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "La versione del sistema {0} è stata installata.", + "DialogUserProfileDeletionWarningMessage": "Non ci sarebbero altri profili da aprire se il profilo selezionato viene cancellato", + "DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?", + "DialogUserProfileUnsavedChangesTitle": "Attenzione - Modifiche Non Salvate", + "DialogUserProfileUnsavedChangesMessage": "Hai apportato modifiche a questo profilo utente che non sono state salvate.", + "DialogUserProfileUnsavedChangesSubMessage": "Vuoi scartare le modifiche?", + "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", + "DialogLoadNcaErrorMessage": "{0}. File errato: {1}", + "DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!", + "DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato lo shader dumping, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare lo shader dumping. Vuoi disabilitare lo shader dumping adesso?", + "DialogLoadAppGameAlreadyLoadedMessage": "Un gioco è già stato caricato", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Ferma l'emulazione o chiudi l'emulatore prima di avviare un altro gioco.", + "DialogUpdateAddUpdateErrorMessage": "Il file specificato non contiene un aggiornamento per il titolo selezionato!", + "DialogSettingsBackendThreadingWarningTitle": "Avviso - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Funzionalità", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading", + "CommonAuto": "Auto", + "CommonOff": "Spento", + "CommonOn": "Acceso", + "InputDialogYes": "Si", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene caratteri non validi. Riprova.", + "MenuBarOptionsPauseEmulation": "Pausa", + "MenuBarOptionsResumeEmulation": "Riprendi", + "AboutUrlTooltipMessage": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.", + "AboutDisclaimerMessage": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) è usata\nnella nostra emulazione Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clicca per aprire la pagina Patreon di Ryujinx nel tuo browser predefinito.", + "AboutGithubUrlTooltipMessage": "Clicca per aprire la pagina GitHub di Ryujinx nel tuo browser predefinito.", + "AboutDiscordUrlTooltipMessage": "Clicca per aprire un invito al server Discord di Ryujinx nel tuo browser predefinito.", + "AboutTwitterUrlTooltipMessage": "Clicca per aprire la pagina Twitter di Ryujinx nel tuo browser predefinito.", + "AboutRyujinxAboutTitle": "Informazioni:", + "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la Nintendo Switch™.\nPer favore supportaci su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenuto da:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei Contributors nel tuo browser predefinito.", + "AboutRyujinxSupprtersTitle": "Supportato su Patreon da:", + "AmiiboSeriesLabel": "Serie Amiibo", + "AmiiboCharacterLabel": "Personaggio", + "AmiiboScanButtonLabel": "Scannerizza", + "AmiiboOptionsShowAllLabel": "Mostra tutti gli amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Usa un tag uuid casuale", + "DlcManagerTableHeadingEnabledLabel": "Abilitato", + "DlcManagerTableHeadingTitleIdLabel": "ID Titolo", + "DlcManagerTableHeadingContainerPathLabel": "Percorso del contenitore", + "DlcManagerTableHeadingFullPathLabel": "Percorso completo", + "DlcManagerRemoveAllButton": "Rimuovi tutti", + "DlcManagerEnableAllButton": "Abilita tutto", + "DlcManagerDisableAllButton": "Disabilita tutto", + "MenuBarOptionsChangeLanguage": "Cambia lingua", + "MenuBarShowFileTypes": "Mostra tipi di file", + "CommonSort": "Ordina", + "CommonShowNames": "Mostra nomi", + "CommonFavorite": "Preferito", + "OrderAscending": "Crescente", + "OrderDescending": "Decrescente", + "SettingsTabGraphicsFeatures": "Funzionalità & Miglioramenti", + "ErrorWindowTitle": "Finestra errore", + "ToggleDiscordTooltip": "Attiva o disattiva Discord Rich Presence", + "AddGameDirBoxTooltip": "Inserisci la directory di un gioco per aggiungerlo alla lista", + "AddGameDirTooltip": "Aggiungi la directory di un gioco alla lista", + "RemoveGameDirTooltip": "Rimuovi la directory di gioco selezionata", + "CustomThemeCheckTooltip": "Attiva o disattiva temi personalizzati nella GUI", + "CustomThemePathTooltip": "Percorso al tema GUI personalizzato", + "CustomThemeBrowseTooltip": "Sfoglia per cercare un tema GUI personalizzato", + "DockModeToggleTooltip": "Attiva o disabilta modalità TV", + "DirectKeyboardTooltip": "Attiva o disattiva \"il supporto all'accesso diretto alla tastiera (HID)\" (Fornisce l'accesso ai giochi alla tua tastiera come dispositivo di immissione del testo)", + "DirectMouseTooltip": "Attiva o disattiva \"il supporto all'accesso diretto al mouse (HID)\" (Fornisce l'accesso ai giochi al tuo mouse come dispositivo di puntamento)", + "RegionTooltip": "Cambia regione di sistema", + "LanguageTooltip": "Cambia lingua di sistema", + "TimezoneTooltip": "Cambia fuso orario di sistema", + "TimeTooltip": "Cambia data e ora di sistema", + "VSyncToggleTooltip": "Attiva o disattiva sincronizzazione verticale", + "PptcToggleTooltip": "Attiva o disattiva PPTC", + "FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco", + "AudioBackendTooltip": "Cambia backend audio", + "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.", + "MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.", + "MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.", + "MemoryManagerUnsafeTooltip": "Mappa direttamente la memoria, ma non maschera l'indirizzo all'interno dello spazio degli indirizzi guest prima dell'accesso. Più veloce, ma a costo della sicurezza. L'applicazione guest può accedere alla memoria da qualsiasi punto di Ryujinx, quindi esegui solo programmi di cui ti fidi con questa modalità.", + "UseHypervisorTooltip": "Usa Hypervisor invece di JIT. Migliora notevolmente le prestazioni quando disponibile, ma può essere instabile nel suo stato attuale.", + "DRamTooltip": "Espande l'ammontare di memoria sul sistema emulato da 4GiB A 6GiB", + "IgnoreMissingServicesTooltip": "Attiva o disattiva l'opzione di ignorare i servizi mancanti", + "GraphicsBackendThreadingTooltip": "Attiva il Graphics Backend Multithreading", + "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread. Permette il multithreading runtime della compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver senza supporto multithreading proprio. Varia leggermente le prestazioni di picco sui driver con multithreading. Ryujinx potrebbe aver bisogno di essere riavviato per disabilitare correttamente il multithreading integrato nel driver, o potrebbe essere necessario farlo manualmente per ottenere le migliori prestazioni.", + "ShaderCacheToggleTooltip": "Attiva o disattiva la Shader Cache", + "ResolutionScaleTooltip": "Scala della risoluzione applicata ai render targets applicabili", + "ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.", + "AnisotropyTooltip": "Livello del filtro anisotropico (imposta su Auto per usare il valore richiesto dal gioco)", + "AspectRatioTooltip": "Rapporto d'aspetto applicato alla finestra del renderer.", + "ShaderDumpPathTooltip": "Percorso di dump Graphics Shaders", + "FileLogTooltip": "Attiva o disattiva il logging su file", + "StubLogTooltip": "Attiva messaggi stub log", + "InfoLogTooltip": "Attiva messaggi info log", + "WarnLogTooltip": "Attiva messaggi warning log", + "ErrorLogTooltip": "Attiva messaggi error log", + "TraceLogTooltip": "Attiva messaggi trace log", + "GuestLogTooltip": "Attiva messaggi guest log", + "FileAccessLogTooltip": "Attiva messaggi file access log", + "FSAccessLogModeTooltip": "Attiva output FS access log alla console. Le modalità possibili sono 0-3", + "DeveloperOptionTooltip": "Usa con attenzione", + "OpenGlLogLevel": "Richiede livelli di log appropriati abilitati", + "DebugLogTooltip": "Attiva messaggi debug log", + "LoadApplicationFileTooltip": "Apri un file explorer per scegliere un file compatibile Switch da caricare", + "LoadApplicationFolderTooltip": "Apri un file explorer per scegliere un file compatibile Switch, applicazione sfusa da caricare", + "OpenRyujinxFolderTooltip": "Apri la cartella del filesystem di Ryujinx", + "OpenRyujinxLogsTooltip": "Apre la cartella dove vengono scritti i log", + "ExitTooltip": "Esci da Ryujinx", + "OpenSettingsTooltip": "Apri finestra delle impostazioni", + "OpenProfileManagerTooltip": "Apri la finestra di gestione dei profili utente", + "StopEmulationTooltip": "Ferma l'emulazione del gioco attuale e torna alla selezione dei giochi", + "CheckUpdatesTooltip": "Controlla la presenza di aggiornamenti di Ryujinx", + "OpenAboutTooltip": "Apri finestra delle informazioni", + "GridSize": "Dimensione griglia", + "GridSizeTooltip": "Cambia la dimensione dei riquardi della griglia", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese Brasiliano", + "AboutRyujinxContributorsButtonHeader": "Vedi tutti i Contributors", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Cambia volume audio", + "SettingsTabSystemEnableInternetAccess": "Attiva Guest Internet Access", + "EnableInternetAccessTooltip": "Attiva il guest Internet access. Se abilitato, l'applicazione si comporterà come se la console Switch emulata fosse collegata a Internet. Si noti che in alcuni casi, le applicazioni possono comunque accedere a Internet anche con questa opzione disabilitata", + "GameListContextMenuManageCheatToolTip": "Gestisci Cheats", + "GameListContextMenuManageCheat": "Gestisci Cheats", + "ControllerSettingsStickRange": "Raggio:", + "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", + "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Rete", + "SettingsTabNetworkConnection": "Connessione di rete", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Memoria CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Updater disabilitato!", + "GameListContextMenuOpenSdModsDirectory": "Apri cartella delle mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella Atmosphere della scheda SD alternativa che contiene le Mod dell'applicazione. Utile per mod confezionate per hardware reale", + "ControllerSettingsRotate90": "Ruota in senso orario di 90°", + "IconSize": "Dimensioni icona", + "IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco", + "MenuBarOptionsShowConsole": "Mostra console", + "ShaderCachePurgeError": "Errore nella pulizia della shader cache a {0}: {1}", + "UserErrorNoKeys": "Chiavi non trovate", + "UserErrorNoFirmware": "Firmware non trovato", + "UserErrorFirmwareParsingFailed": "Errori di analisi del firmware", + "UserErrorApplicationNotFound": "Applicazione non trovata", + "UserErrorUnknown": "Errore sconosciuto", + "UserErrorUndefined": "Errore non definito", + "UserErrorNoKeysDescription": "Ryujinx non è riuscito a trovare il file 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx non è riuscito a trovare alcun firmware installato", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.", + "UserErrorApplicationNotFoundDescription": "Ryujinx non è riuscito a trovare un'applicazione valida nel percorso specificato.", + "UserErrorUnknownDescription": "Si è verificato un errore sconosciuto!", + "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Non dovrebbe succedere, per favore contatta uno sviluppatore!", + "OpenSetupGuideMessage": "Apri la guida all'installazione", + "NoUpdate": "Nessun aggiornamento", + "TitleUpdateVersionLabel": "Versione {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Conferma", + "FileDialogAllTypes": "Tutti i tipi", + "Never": "Mai", + "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", + "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", + "SoftwareKeyboard": "Tastiera software", + "SoftwareKeyboardModeNumbersOnly": "Deve essere solo numeri", + "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK", + "SoftwareKeyboardModeASCII": "Deve essere solo testo ASCII", + "DialogControllerAppletMessagePlayerRange": "L'applicazione richiede {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", + "DialogControllerAppletMessage": "L'applicazione richiede esattamente {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", + "DialogControllerAppletDockModeSet": "Modalità TV attivata. Neanche portatile è valida.\n\n", + "UpdaterRenaming": "Rinominazione dei vecchi files...", + "UpdaterRenameFailed": "L'updater non è riuscito a rinominare il file: {0}", + "UpdaterAddingFiles": "Aggiunta nuovi files...", + "UpdaterExtracting": "Estrazione aggiornamento...", + "UpdaterDownloading": "Download aggiornamento...", + "Game": "Gioco", + "Docked": "TV", + "Handheld": "Portatile", + "ConnectionError": "Errore di connessione.", + "AboutPageDeveloperListMore": "{0} e altri ancora...", + "ApiError": "Errore dell'API.", + "LoadingHeading": "Caricamento di {0}", + "CompilingPPTC": "Compilazione PTC", + "CompilingShaders": "Compilazione Shaders", + "AllKeyboards": "Tutte le tastiere", + "OpenFileDialogTitle": "Seleziona un file supportato da aprire", + "OpenFolderDialogTitle": "Seleziona una cartella con un gioco estratto", + "AllSupportedFormats": "Tutti i formati supportati", + "RyujinxUpdater": "Aggiornamento Ryujinx", + "SettingsTabHotkeys": "Tasti di scelta rapida", + "SettingsTabHotkeysHotkeys": "Tasti di scelta rapida", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Cattura Schermo:", + "SettingsTabHotkeysShowUiHotkey": "Mostra UI:", + "SettingsTabHotkeysPauseHotkey": "Metti in pausa:", + "SettingsTabHotkeysToggleMuteHotkey": "Muta:", + "ControllerMotionTitle": "Impostazioni dei sensori di movimento", + "ControllerRumbleTitle": "Impostazioni di vibrazione", + "SettingsSelectThemeFileDialogTitle": "Seleziona file del tema", + "SettingsXamlThemeFile": "File del tema xaml", + "AvatarWindowTitle": "Gestisci account - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Sconosciuto", + "Usage": "Utilizzo", + "Writable": "Scrivibile", + "SelectDlcDialogTitle": "Seleziona file dei DLC", + "SelectUpdateDialogTitle": "Seleziona file di aggiornamento", + "UserProfileWindowTitle": "Gestisci profili degli utenti", + "CheatWindowTitle": "Gestisci cheat dei giochi", + "DlcWindowTitle": "Gestisci DLC dei giochi", + "UpdateWindowTitle": "Gestisci aggiornamenti dei giochi", + "CheatWindowHeading": "Cheat disponibiili per {0} [{1}]", + "BuildId": "ID Build", + "DlcWindowHeading": "DLC disponibili per {0} [{1}]", + "UserProfilesEditProfile": "Modifica selezionati", + "Cancel": "Annulla", + "Save": "Salva", + "Discard": "Scarta", + "UserProfilesSetProfileImage": "Imposta immagine profilo", + "UserProfileEmptyNameError": "È richiesto un nome", + "UserProfileNoImageError": "Dev'essere impostata un'immagine profilo", + "GameUpdateWindowHeading": "Aggiornamenti disponibili per {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:", + "UserProfilesName": "Nome:", + "UserProfilesUserId": "ID utente:", + "SettingsTabGraphicsBackend": "Backend grafica", + "SettingsTabGraphicsBackendTooltip": "Backend grafica da usare", + "SettingsEnableTextureRecompression": "Abilita Ricompressione Texture", + "SettingsEnableTextureRecompressionTooltip": "Comprime alcune texture per ridurre l'utilizzo della VRAM.\n\nL'utilizzo è consigliato con GPU con meno di 4GB di VRAM.\n\nLascia su OFF se non sei sicuro.", + "SettingsTabGraphicsPreferredGpu": "GPU preferita", + "SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.", + "SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx", + "SettingsGpuBackendRestartMessage": "Le impostazioni della backend grafica o della GPU sono state modificate. Questo richiederà un riavvio perché le modifiche siano applicate", + "SettingsGpuBackendRestartSubMessage": "Vuoi riavviare ora?", + "RyujinxUpdaterMessage": "Vuoi aggiornare Ryujinx all'ultima versione?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentare il volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuire il volume:", + "SettingsEnableMacroHLE": "Abilita Macro HLE", + "SettingsEnableMacroHLETooltip": "Emulazione di alto livello del codice Macro GPU.\n\nMigliora le prestazioni, ma può causare anomalie grafiche in alcuni giochi.\n\nLasciare ON se non sei sicuro.", + "SettingsEnableColorSpacePassthrough": "Spazio colore passante", + "SettingsEnableColorSpacePassthroughTooltip": "Indica al backend Vulkan di passare le informazioni sul colore senza specificare uno spazio colore. Per gli utenti con schermi ad ampia gamma, questo può risultare in colori più vivaci, al costo della correttezza del colore.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gestisci i salvataggi", + "DeleteUserSave": "Vuoi eliminare il salvataggio utente per questo gioco?", + "IrreversibleActionNote": "Questa azione non è reversibile.", + "SaveManagerHeading": "Gestisci i salvataggi per {0}", + "SaveManagerTitle": "Gestione Salvataggi", + "Name": "Nome", + "Size": "Dimensione", + "Search": "Cerca", + "UserProfilesRecoverLostAccounts": "Recupera il tuo account", + "Recover": "Recupera", + "UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account", + "UserProfilesRecoverEmptyList": "Nessun profilo da recuperare", + "GraphicsAATooltip": "Applica anti-aliasing al rendering del gioco", + "GraphicsAALabel": "Anti-Aliasing:", + "GraphicsScalingFilterLabel": "Filtro di scala:", + "GraphicsScalingFilterTooltip": "Abilita scalatura Framebuffer", + "GraphicsScalingFilterLevelLabel": "Livello", + "GraphicsScalingFilterLevelTooltip": "Imposta livello del filtro di scala", + "SmaaLow": "SMAA Basso", + "SmaaMedium": "SMAA Medio", + "SmaaHigh": "SMAA Alto", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Modificare L'Utente", + "UserEditorTitleCreate": "Crea Un Utente", + "SettingsTabNetworkInterface": "Interfaccia di rete:", + "NetworkInterfaceTooltip": "L'interfaccia di rete utilizzata per le funzionalità LAN", + "NetworkInterfaceDefault": "Predefinito", + "PackagingShaders": "Comprimendo shader", + "AboutChangelogButton": "Visualizza changelog su GitHub", + "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/ja_JP.json b/src/Ryujinx.Ava/Assets/Locales/ja_JP.json new file mode 100644 index 000000000..5b31c5f20 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/ja_JP.json @@ -0,0 +1,656 @@ +{ + "Language": "英語 (アメリカ)", + "MenuBarFileOpenApplet": "アプレットを開く", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "スタンドアロンモードで Mii エディタアプレットを開きます", + "SettingsTabInputDirectMouseAccess": "マウス直接アクセス", + "SettingsTabSystemMemoryManagerMode": "メモリ管理モード:", + "SettingsTabSystemMemoryManagerModeSoftware": "ソフトウェア", + "SettingsTabSystemMemoryManagerModeHost": "ホスト (高速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "ホスト, チェックなし (最高速, 安全でない)", + "SettingsTabSystemUseHypervisor": "ハイパーバイザーを使用", + "MenuBarFile": "ファイル(_F)", + "MenuBarFileOpenFromFile": "ファイルからアプリケーションをロード(_L)", + "MenuBarFileOpenUnpacked": "展開されたゲームをロード", + "MenuBarFileOpenEmuFolder": "Ryujinx フォルダを開く", + "MenuBarFileOpenLogsFolder": "ログフォルダを開く", + "MenuBarFileExit": "終了(_E)", + "MenuBarOptions": "オプション", + "MenuBarOptionsToggleFullscreen": "全画面切り替え", + "MenuBarOptionsStartGamesInFullscreen": "全画面モードでゲームを開始", + "MenuBarOptionsStopEmulation": "エミュレーションを停止", + "MenuBarOptionsSettings": "設定(_S)", + "MenuBarOptionsManageUserProfiles": "ユーザプロファイルを管理(_M)", + "MenuBarActions": "アクション(_A)", + "MenuBarOptionsSimulateWakeUpMessage": "スリープ復帰メッセージをシミュレート", + "MenuBarActionsScanAmiibo": "Amiibo をスキャン", + "MenuBarTools": "ツール(_T)", + "MenuBarToolsInstallFirmware": "ファームウェアをインストール", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI または ZIP からファームウェアをインストール", + "MenuBarFileToolsInstallFirmwareFromDirectory": "ディレクトリからファームウェアをインストール", + "MenuBarToolsManageFileTypes": "ファイル形式を管理", + "MenuBarToolsInstallFileTypes": "ファイル形式をインストール", + "MenuBarToolsUninstallFileTypes": "ファイル形式をアンインストール", + "MenuBarHelp": "ヘルプ", + "MenuBarHelpCheckForUpdates": "アップデートを確認", + "MenuBarHelpAbout": "Ryujinx について", + "MenuSearch": "検索...", + "GameListHeaderFavorite": "お気に入り", + "GameListHeaderIcon": "アイコン", + "GameListHeaderApplication": "名称", + "GameListHeaderDeveloper": "開発元", + "GameListHeaderVersion": "バージョン", + "GameListHeaderTimePlayed": "プレイ時間", + "GameListHeaderLastPlayed": "最終プレイ日時", + "GameListHeaderFileExtension": "ファイル拡張子", + "GameListHeaderFileSize": "ファイルサイズ", + "GameListHeaderPath": "パス", + "GameListContextMenuOpenUserSaveDirectory": "セーブディレクトリを開く", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "アプリケーションのユーザセーブデータを格納するディレクトリを開きます", + "GameListContextMenuOpenDeviceSaveDirectory": "デバイスディレクトリを開く", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "アプリケーションのデバイスセーブデータを格納するディレクトリを開きます", + "GameListContextMenuOpenBcatSaveDirectory": "BCATディレクトリを開く", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "アプリケーションの BCAT セーブデータを格納するディレクトリを開きます", + "GameListContextMenuManageTitleUpdates": "アップデートを管理", + "GameListContextMenuManageTitleUpdatesToolTip": "タイトルのアップデート管理ウインドウを開きます", + "GameListContextMenuManageDlc": "DLCを管理", + "GameListContextMenuManageDlcToolTip": "DLC管理ウインドウを開きます", + "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", + "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", + "GameListContextMenuCacheManagement": "キャッシュ管理", + "GameListContextMenuCacheManagementPurgePptc": "PPTC を再構築", + "GameListContextMenuCacheManagementPurgePptcToolTip": "次回のゲーム起動時に PPTC を再構築します", + "GameListContextMenuCacheManagementPurgeShaderCache": "シェーダーキャッシュを破棄", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "アプリケーションのシェーダーキャッシュを破棄します", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC ディレクトリを開く", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "アプリケーションの PPTC キャッシュを格納するディレクトリを開きます", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "シェーダーキャッシュディレクトリを開く", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "アプリケーションのシェーダーキャッシュを格納するディレクトリを開きます", + "GameListContextMenuExtractData": "データを展開", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "現在のアプリケーション設定(アップデート含む)から ExeFS セクションを展開します", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "現在のアプリケーション設定(アップデート含む)から RomFS セクションを展開します", + "GameListContextMenuExtractDataLogo": "ロゴ", + "GameListContextMenuExtractDataLogoToolTip": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します", + "StatusBarGamesLoaded": "{0}/{1} ゲーム", + "StatusBarSystemVersion": "システムバージョン: {0}", + "LinuxVmMaxMapCountDialogTitle": "メモリマッピング上限値が小さすぎます", + "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count の値を {0}に増やしますか?", + "LinuxVmMaxMapCountDialogTextSecondary": "ゲームによっては, 現在許可されているサイズより大きなメモリマッピングを作成しようとすることがあります. この制限を超えると, Ryjinx はすぐにクラッシュします.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "はい, 次回再起動まで", + "LinuxVmMaxMapCountDialogButtonPersistent": "はい, 恒久的に", + "LinuxVmMaxMapCountWarningTextPrimary": "メモリマッピングの最大量が推奨値よりも小さいです.", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count の現在値 {0} は {1} よりも小さいです. ゲームによっては現在許可されている値よりも大きなメモリマッピングを作成しようとする場合があります. 上限を越えた場合, Ryujinx はクラッシュします.", + "Settings": "設定", + "SettingsTabGeneral": "ユーザインタフェース", + "SettingsTabGeneralGeneral": "一般", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord リッチプレゼンスを有効にする", + "SettingsTabGeneralCheckUpdatesOnLaunch": "起動時にアップデートを確認する", + "SettingsTabGeneralShowConfirmExitDialog": "\"終了を確認\" ダイアログを表示する", + "SettingsTabGeneralHideCursor": "マウスカーソルを非表示", + "SettingsTabGeneralHideCursorNever": "決して", + "SettingsTabGeneralHideCursorOnIdle": "アイドル時", + "SettingsTabGeneralHideCursorAlways": "常時", + "SettingsTabGeneralGameDirectories": "ゲームディレクトリ", + "SettingsTabGeneralAdd": "追加", + "SettingsTabGeneralRemove": "削除", + "SettingsTabSystem": "システム", + "SettingsTabSystemCore": "コア", + "SettingsTabSystemSystemRegion": "地域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "アメリカ", + "SettingsTabSystemSystemRegionEurope": "ヨーロッパ", + "SettingsTabSystemSystemRegionAustralia": "オーストラリア", + "SettingsTabSystemSystemRegionChina": "中国", + "SettingsTabSystemSystemRegionKorea": "韓国", + "SettingsTabSystemSystemRegionTaiwan": "台湾", + "SettingsTabSystemSystemLanguage": "言語:", + "SettingsTabSystemSystemLanguageJapanese": "日本語", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英語(アメリカ)", + "SettingsTabSystemSystemLanguageFrench": "フランス語", + "SettingsTabSystemSystemLanguageGerman": "ドイツ語", + "SettingsTabSystemSystemLanguageItalian": "イタリア語", + "SettingsTabSystemSystemLanguageSpanish": "スペイン語", + "SettingsTabSystemSystemLanguageChinese": "中国語", + "SettingsTabSystemSystemLanguageKorean": "韓国語", + "SettingsTabSystemSystemLanguageDutch": "オランダ語", + "SettingsTabSystemSystemLanguagePortuguese": "ポルトガル語", + "SettingsTabSystemSystemLanguageRussian": "ロシア語", + "SettingsTabSystemSystemLanguageTaiwanese": "台湾語", + "SettingsTabSystemSystemLanguageBritishEnglish": "英語(イギリス)", + "SettingsTabSystemSystemLanguageCanadianFrench": "フランス語(カナダ)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "スペイン語(ラテンアメリカ)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "中国語", + "SettingsTabSystemSystemLanguageTraditionalChinese": "台湾語", + "SettingsTabSystemSystemTimeZone": "タイムゾーン:", + "SettingsTabSystemSystemTime": "時刻:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "ファイルシステム整合性チェック", + "SettingsTabSystemAudioBackend": "音声バックエンド:", + "SettingsTabSystemAudioBackendDummy": "ダミー", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "ハック", + "SettingsTabSystemHacksNote": " (挙動が不安定になる可能性があります)", + "SettingsTabSystemExpandDramSize": "DRAMサイズを6GiBに拡大する", + "SettingsTabSystemIgnoreMissingServices": "未実装サービスを無視する", + "SettingsTabGraphics": "グラフィックス", + "SettingsTabGraphicsAPI": "グラフィックスAPI", + "SettingsTabGraphicsEnableShaderCache": "シェーダーキャッシュを有効にする", + "SettingsTabGraphicsAnisotropicFiltering": "異方性フィルタリング:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "解像度:", + "SettingsTabGraphicsResolutionScaleCustom": "カスタム (非推奨)", + "SettingsTabGraphicsResolutionScaleNative": "ネイティブ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "アスペクト比:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "ウインドウサイズに合わせる", + "SettingsTabGraphicsDeveloperOptions": "開発者向けオプション", + "SettingsTabGraphicsShaderDumpPath": "グラフィックス シェーダー ダンプパス:", + "SettingsTabLogging": "ロギング", + "SettingsTabLoggingLogging": "ロギング", + "SettingsTabLoggingEnableLoggingToFile": "ファイルへのロギングを有効にする", + "SettingsTabLoggingEnableStubLogs": "Stub ログを有効にする", + "SettingsTabLoggingEnableInfoLogs": "Info ログを有効にする", + "SettingsTabLoggingEnableWarningLogs": "Warning ログを有効にする", + "SettingsTabLoggingEnableErrorLogs": "Error ログを有効にする", + "SettingsTabLoggingEnableTraceLogs": "Trace ログを有効にする", + "SettingsTabLoggingEnableGuestLogs": "Guest ログを有効にする", + "SettingsTabLoggingEnableFsAccessLogs": "Fs アクセスログを有効にする", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs グローバルアクセスログモード:", + "SettingsTabLoggingDeveloperOptions": "開発者オプション", + "SettingsTabLoggingDeveloperOptionsNote": "警告: パフォーマンスを低下させます", + "SettingsTabLoggingGraphicsBackendLogLevel": "グラフィックスバックエンド ログレベル:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "なし", + "SettingsTabLoggingGraphicsBackendLogLevelError": "エラー", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "パフォーマンス低下", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "すべて", + "SettingsTabLoggingEnableDebugLogs": "デバッグログを有効にする", + "SettingsTabInput": "入力", + "SettingsTabInputEnableDockedMode": "ドッキングモード", + "SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス", + "SettingsButtonSave": "セーブ", + "SettingsButtonClose": "閉じる", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "キャンセル", + "SettingsButtonApply": "適用", + "ControllerSettingsPlayer": "プレイヤー", + "ControllerSettingsPlayer1": "プレイヤー 1", + "ControllerSettingsPlayer2": "プレイヤー 2", + "ControllerSettingsPlayer3": "プレイヤー 3", + "ControllerSettingsPlayer4": "プレイヤー 4", + "ControllerSettingsPlayer5": "プレイヤー 5", + "ControllerSettingsPlayer6": "プレイヤー 6", + "ControllerSettingsPlayer7": "プレイヤー 7", + "ControllerSettingsPlayer8": "プレイヤー 8", + "ControllerSettingsHandheld": "携帯", + "ControllerSettingsInputDevice": "入力デバイス", + "ControllerSettingsRefresh": "更新", + "ControllerSettingsDeviceDisabled": "無効", + "ControllerSettingsControllerType": "コントローラ種別", + "ControllerSettingsControllerTypeHandheld": "携帯", + "ControllerSettingsControllerTypeProController": "Pro コントローラ", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon ペア", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon 左", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon 右", + "ControllerSettingsProfile": "プロファイル", + "ControllerSettingsProfileDefault": "デフォルト", + "ControllerSettingsLoad": "ロード", + "ControllerSettingsAdd": "追加", + "ControllerSettingsRemove": "削除", + "ControllerSettingsButtons": "ボタン", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "十字キー", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsStickButton": "ボタン", + "ControllerSettingsStickUp": "上", + "ControllerSettingsStickDown": "下", + "ControllerSettingsStickLeft": "左", + "ControllerSettingsStickRight": "右", + "ControllerSettingsStickStick": "スティック", + "ControllerSettingsStickInvertXAxis": "X軸を反転", + "ControllerSettingsStickInvertYAxis": "Y軸を反転", + "ControllerSettingsStickDeadzone": "遊び:", + "ControllerSettingsLStick": "左スティック", + "ControllerSettingsRStick": "右スティック", + "ControllerSettingsTriggersLeft": "左トリガー", + "ControllerSettingsTriggersRight": "右トリガー", + "ControllerSettingsTriggersButtonsLeft": "左トリガーボタン", + "ControllerSettingsTriggersButtonsRight": "右トリガーボタン", + "ControllerSettingsTriggers": "トリガー", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左ボタン", + "ControllerSettingsExtraButtonsRight": "右ボタン", + "ControllerSettingsMisc": "その他", + "ControllerSettingsTriggerThreshold": "トリガーしきい値:", + "ControllerSettingsMotion": "モーション", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 互換モーションを使用", + "ControllerSettingsMotionControllerSlot": "コントローラ スロット:", + "ControllerSettingsMotionMirrorInput": "入力反転", + "ControllerSettingsMotionRightJoyConSlot": "JoyCon 右 スロット:", + "ControllerSettingsMotionServerHost": "サーバ:", + "ControllerSettingsMotionGyroSensitivity": "ジャイロ感度:", + "ControllerSettingsMotionGyroDeadzone": "ジャイロ遊び:", + "ControllerSettingsSave": "セーブ", + "ControllerSettingsClose": "閉じる", + "UserProfilesSelectedUserProfile": "選択されたユーザプロファイル:", + "UserProfilesSaveProfileName": "プロファイル名をセーブ", + "UserProfilesChangeProfileImage": "プロファイル画像を変更", + "UserProfilesAvailableUserProfiles": "利用可能なユーザプロファイル:", + "UserProfilesAddNewProfile": "プロファイルを作成", + "UserProfilesDelete": "削除", + "UserProfilesClose": "閉じる", + "ProfileNameSelectionWatermark": "ニックネームを選択", + "ProfileImageSelectionTitle": "プロファイル画像選択", + "ProfileImageSelectionHeader": "プロファイル画像を選択", + "ProfileImageSelectionNote": "カスタム画像をインポート, またはファームウェア内のアバターを選択できます", + "ProfileImageSelectionImportImage": "画像ファイルをインポート", + "ProfileImageSelectionSelectAvatar": "ファームウェア内のアバターを選択", + "InputDialogTitle": "入力ダイアログ", + "InputDialogOk": "OK", + "InputDialogCancel": "キャンセル", + "InputDialogAddNewProfileTitle": "プロファイル名を選択", + "InputDialogAddNewProfileHeader": "プロファイル名を入力してください", + "InputDialogAddNewProfileSubtext": "(最大長: {0})", + "AvatarChoose": "選択", + "AvatarSetBackgroundColor": "背景色を指定", + "AvatarClose": "閉じる", + "ControllerSettingsLoadProfileToolTip": "プロファイルをロード", + "ControllerSettingsAddProfileToolTip": "プロファイルを追加", + "ControllerSettingsRemoveProfileToolTip": "プロファイルを削除", + "ControllerSettingsSaveProfileToolTip": "プロファイルをセーブ", + "MenuBarFileToolsTakeScreenshot": "スクリーンショットを撮影", + "MenuBarFileToolsHideUi": "UIを隠す", + "GameListContextMenuRunApplication": "アプリケーションを実行", + "GameListContextMenuToggleFavorite": "お気に入りを切り替え", + "GameListContextMenuToggleFavoriteToolTip": "ゲームをお気に入りに含めるかどうかを切り替えます", + "SettingsTabGeneralTheme": "テーマ", + "SettingsTabGeneralThemeCustomTheme": "カスタムテーマパス", + "SettingsTabGeneralThemeBaseStyle": "基本スタイル", + "SettingsTabGeneralThemeBaseStyleDark": "ダーク", + "SettingsTabGeneralThemeBaseStyleLight": "ライト", + "SettingsTabGeneralThemeEnableCustomTheme": "カスタムテーマを有効にする", + "ButtonBrowse": "参照", + "ControllerSettingsConfigureGeneral": "設定", + "ControllerSettingsRumble": "振動", + "ControllerSettingsRumbleStrongMultiplier": "強振動の補正値", + "ControllerSettingsRumbleWeakMultiplier": "弱振動の補正値", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] のセーブデータはありません", + "DialogMessageSaveNotAvailableCreateSaveMessage": "このゲームのセーブデータを作成してよろしいですか?", + "DialogConfirmationTitle": "Ryujinx - 確認", + "DialogUpdaterTitle": "Ryujinx - アップデータ", + "DialogErrorTitle": "Ryujinx - エラー", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 終了", + "DialogErrorMessage": "エラーが発生しました", + "DialogExitMessage": "Ryujinx を閉じてよろしいですか?", + "DialogExitSubMessage": "セーブされていないデータはすべて失われます!", + "DialogMessageCreateSaveErrorMessage": "セーブデータ: {0} の作成中にエラーが発生しました", + "DialogMessageFindSaveErrorMessage": "セーブデータ: {0} の検索中にエラーが発生しました", + "FolderDialogExtractTitle": "展開フォルダを選択", + "DialogNcaExtractionMessage": "{1} から {0} セクションを展開中...", + "DialogNcaExtractionTitle": "Ryujinx - NCA セクション展開", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "展開に失敗しました. 選択されたファイルにはメイン NCA が存在しません.", + "DialogNcaExtractionCheckLogErrorMessage": "展開に失敗しました. 詳細はログを確認してください.", + "DialogNcaExtractionSuccessMessage": "展開が正常終了しました", + "DialogUpdaterConvertFailedMessage": "現在の Ryujinx バージョンの変換に失敗しました.", + "DialogUpdaterCancelUpdateMessage": "アップデータをキャンセル中!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "最新バージョンの Ryujinx を使用中です!", + "DialogUpdaterFailedToGetVersionMessage": "Github からのリリース情報取得時にエラーが発生しました. Github Actions でリリースファイルを作成中かもしれません. 後ほどもう一度試してみてください.", + "DialogUpdaterConvertFailedGithubMessage": "Github から取得した Ryujinx バージョンの変換に失敗しました.", + "DialogUpdaterDownloadingMessage": "アップデートをダウンロード中...", + "DialogUpdaterExtractionMessage": "アップデートを展開中...", + "DialogUpdaterRenamingMessage": "アップデートをリネーム中...", + "DialogUpdaterAddingFilesMessage": "新規アップデートを追加中...", + "DialogUpdaterCompleteMessage": "アップデート完了!", + "DialogUpdaterRestartMessage": "すぐに Ryujinx を再起動しますか?", + "DialogUpdaterArchNotSupportedMessage": "サポート外のアーキテクチャです!", + "DialogUpdaterArchNotSupportedSubMessage": "(x64 システムのみサポートしています!)", + "DialogUpdaterNoInternetMessage": "インターネットに接続されていません!", + "DialogUpdaterNoInternetSubMessage": "インターネット接続が正常動作しているか確認してください!", + "DialogUpdaterDirtyBuildMessage": "Dirty ビルドの Ryujinx はアップデートできません!", + "DialogUpdaterDirtyBuildSubMessage": "サポートされているバージョンをお探しなら, https://ryujinx.org/ で Ryujinx をダウンロードしてください.", + "DialogRestartRequiredMessage": "再起動が必要", + "DialogThemeRestartMessage": "テーマがセーブされました. テーマを適用するには再起動が必要です.", + "DialogThemeRestartSubMessage": "再起動しますか", + "DialogFirmwareInstallEmbeddedMessage": "このゲームに含まれるファームウェアをインストールしてよろしいですか? (ファームウェア {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\\nエミュレータが開始します.", + "DialogFirmwareNoFirmwareInstalledMessage": "ファームウェアがインストールされていません", + "DialogFirmwareInstalledMessage": "ファームウェア {0} がインストールされました", + "DialogInstallFileTypesSuccessMessage": "ファイル形式のインストールに成功しました!", + "DialogInstallFileTypesErrorMessage": "ファイル形式のインストールに失敗しました.", + "DialogUninstallFileTypesSuccessMessage": "ファイル形式のアンインストールに成功しました!", + "DialogUninstallFileTypesErrorMessage": "ファイル形式のアンインストールに失敗しました.", + "DialogOpenSettingsWindowLabel": "設定ウインドウを開く", + "DialogControllerAppletTitle": "コントローラアプレット", + "DialogMessageDialogErrorExceptionMessage": "メッセージダイアログ表示エラー: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "ソフトウェアキーボード表示エラー: {0}", + "DialogErrorAppletErrorExceptionMessage": "エラーアプレットダイアログ表示エラー: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nこのエラーへの対処方法については, セットアップガイドを参照してください.", + "DialogUserErrorDialogTitle": "Ryujinx エラー ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API からの情報取得中にエラーが発生しました.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API サーバに接続できませんでした. サーバがダウンしているか, インターネット接続に問題があるかもしれません.", + "DialogProfileInvalidProfileErrorMessage": "プロファイル {0} は現在の入力設定システムと互換性がありません.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "デフォルトのプロファイルは上書きできません", + "DialogProfileDeleteProfileTitle": "プロファイルを削除中", + "DialogProfileDeleteProfileMessage": "このアクションは元に戻せません. 本当に続けてよろしいですか?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "次回起動時に PPTC を再構築します:\n\n{0}\n\n実行してよろしいですか?", + "DialogPPTCDeletionErrorMessage": "PPTC キャッシュ破棄エラー {0}: {1}", + "DialogShaderDeletionMessage": "シェーダーキャッシュを破棄しようとしています:\n\n{0}\n\n実行してよろしいですか?", + "DialogShaderDeletionErrorMessage": "シェーダーキャッシュ破棄エラー {0}: {1}", + "DialogRyujinxErrorMessage": "エラーが発生しました", + "DialogInvalidTitleIdErrorMessage": "UI エラー: 選択されたゲームは有効なタイトル ID を保持していません", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} には有効なシステムファームウェアがありません.", + "DialogFirmwareInstallerFirmwareInstallTitle": "ファームウェア {0} をインストール", + "DialogFirmwareInstallerFirmwareInstallMessage": "システムバージョン {0} がインストールされます.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n現在のシステムバージョン {0} を置き換えます.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n続けてよろしいですか?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "ファームウェアをインストール中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "システムバージョン {0} が正常にインストールされました.", + "DialogUserProfileDeletionWarningMessage": "選択されたプロファイルを削除すると,プロファイルがひとつも存在しなくなります", + "DialogUserProfileDeletionConfirmMessage": "選択されたプロファイルを削除しますか", + "DialogUserProfileUnsavedChangesTitle": "警告 - 保存されていない変更", + "DialogUserProfileUnsavedChangesMessage": "保存されていないユーザプロファイルを変更しました.", + "DialogUserProfileUnsavedChangesSubMessage": "変更を破棄しますか?", + "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", + "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", + "DialogLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}", + "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", + "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "シェーダーダンプを有効にします. これは開発者のみに有用な機能です.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "パフォーマンス最適化のためには, シェーダーダンプを無効にすることを推奨します. シェーダーダンプを無効にしてよろしいですか?", + "DialogLoadAppGameAlreadyLoadedMessage": "ゲームはすでにロード済みです", + "DialogLoadAppGameAlreadyLoadedSubMessage": "別のゲームを起動する前に, エミュレーションを停止またはエミュレータを閉じてください.", + "DialogUpdateAddUpdateErrorMessage": "選択されたファイルはこのタイトル用のアップデートではありません!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - バックエンドスレッディング", + "DialogSettingsBackendThreadingWarningMessage": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.", + "SettingsTabGraphicsFeaturesOptions": "機能", + "SettingsTabGraphicsBackendMultithreading": "グラフィックスバックエンドのマルチスレッド実行:", + "CommonAuto": "自動", + "CommonOff": "オフ", + "CommonOn": "オン", + "InputDialogYes": "はい", + "InputDialogNo": "いいえ", + "DialogProfileInvalidProfileNameErrorMessage": "プロファイル名に無効な文字が含まれています. 再度試してみてください.", + "MenuBarOptionsPauseEmulation": "中断", + "MenuBarOptionsResumeEmulation": "再開", + "AboutUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx のウェブサイトを開きます.", + "AboutDisclaimerMessage": "Ryujinx は Nintendo™ および\nそのパートナー企業とは一切関係ありません.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) は\nAmiibo エミュレーションに使用されています.", + "AboutPatreonUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Patreon ページを開きます.", + "AboutGithubUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Github ページを開きます.", + "AboutDiscordUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Discord サーバを開きます.", + "AboutTwitterUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Twitter ページを開きます.", + "AboutRyujinxAboutTitle": "Ryujinx について:", + "AboutRyujinxAboutContent": "Ryujinx は Nintendo Switch™ のエミュレータです.\nPatreon で私達の活動を支援してください.\n最新の情報は Twitter または Discord から取得できます.\n貢献したい開発者の方は GitHub または Discord で詳細をご確認ください.", + "AboutRyujinxMaintainersTitle": "開発者:", + "AboutRyujinxMaintainersContentTooltipMessage": "クリックするとデフォルトのブラウザで 貢献者のページを開きます.", + "AboutRyujinxSupprtersTitle": "Patreon での支援者:", + "AmiiboSeriesLabel": "Amiibo シリーズ", + "AmiiboCharacterLabel": "キャラクタ", + "AmiiboScanButtonLabel": "スキャン", + "AmiiboOptionsShowAllLabel": "すべての Amiibo を表示", + "AmiiboOptionsUsRandomTagLabel": "ハック: ランダムな Uuid を使用", + "DlcManagerTableHeadingEnabledLabel": "有効", + "DlcManagerTableHeadingTitleIdLabel": "タイトルID", + "DlcManagerTableHeadingContainerPathLabel": "コンテナパス", + "DlcManagerTableHeadingFullPathLabel": "フルパス", + "DlcManagerRemoveAllButton": "すべて削除", + "DlcManagerEnableAllButton": "すべて有効", + "DlcManagerDisableAllButton": "すべて無効", + "MenuBarOptionsChangeLanguage": "言語を変更", + "MenuBarShowFileTypes": "ファイル形式を表示", + "CommonSort": "並べ替え", + "CommonShowNames": "名称を表示", + "CommonFavorite": "お気に入り", + "OrderAscending": "昇順", + "OrderDescending": "降順", + "SettingsTabGraphicsFeatures": "機能", + "ErrorWindowTitle": "エラーウインドウ", + "ToggleDiscordTooltip": "Discord の \"現在プレイ中\" アクティビティに Ryujinx を表示するかどうかを選択します", + "AddGameDirBoxTooltip": "リストに追加するゲームディレクトリを入力します", + "AddGameDirTooltip": "リストにゲームディレクトリを追加します", + "RemoveGameDirTooltip": "選択したゲームディレクトリを削除します", + "CustomThemeCheckTooltip": "エミュレータのメニュー外観を変更するためカスタム Avalonia テーマを使用します", + "CustomThemePathTooltip": "カスタム GUI テーマのパスです", + "CustomThemeBrowseTooltip": "カスタム GUI テーマを参照します", + "DockModeToggleTooltip": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.", + "DirectKeyboardTooltip": "キーボード直接アクセス (HID) に対応します. キーボードをテキスト入力デバイスとして使用できます.", + "DirectMouseTooltip": "マウス直接アクセス (HID) に対応します. マウスをポインティングデバイスとして使用できます.", + "RegionTooltip": "システムの地域を変更します", + "LanguageTooltip": "システムの言語を変更します", + "TimezoneTooltip": "システムのタイムゾーンを変更します", + "TimeTooltip": "システムの時刻を変更します", + "VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキーで, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.", + "PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.", + "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", + "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", + "MemoryManagerTooltip": "ゲストメモリのマップ/アクセス方式を変更します. エミュレートされるCPUのパフォーマンスに大きな影響を与えます.\n\nよくわからない場合は「ホスト,チェックなし」を設定してください.", + "MemoryManagerSoftwareTooltip": "アドレス変換にソフトウェアページテーブルを使用します. 非常に正確ですがパフォーマンスが大きく低下します.", + "MemoryManagerHostTooltip": "ホストのアドレス空間にメモリを直接マップします.JITのコンパイルと実行速度が大きく向上します.", + "MemoryManagerUnsafeTooltip": "メモリを直接マップしますが, アクセス前にゲストのアドレス空間内のアドレスをマスクしません. より高速になりますが, 安全性が犠牲になります. ゲストアプリケーションは Ryujinx のどこからでもメモリにアクセスできるので,このモードでは信頼できるプログラムだけを実行するようにしてください.", + "UseHypervisorTooltip": "JIT の代わりにハイパーバイザーを使用します. 利用可能な場合, パフォーマンスが大幅に向上しますが, 現在の状態では不安定になる可能性があります.", + "DRamTooltip": "エミュレートされたシステムのメモリ容量を 4GiB から 6GiB に増加します.\n\n高解像度のテクスチャパックや 4K解像度の mod を使用する場合に有用です. パフォーマンスを改善するものではありません.\n\nよくわからない場合はオフのままにしてください.", + "IgnoreMissingServicesTooltip": "未実装の Horizon OS サービスを無視します. 特定のゲームにおいて起動時のクラッシュを回避できる場合があります.\n\nよくわからない場合はオフのままにしてください.", + "GraphicsBackendThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", + "GalThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", + "ShaderCacheToggleTooltip": "ディスクシェーダーキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.", + "ResolutionScaleTooltip": "レンダリングに適用される解像度の倍率です", + "ResolutionScaleEntryTooltip": "1.5 のような整数でない倍率を指定すると,問題が発生したりクラッシュしたりする場合があります.", + "AnisotropyTooltip": "異方性フィルタリングのレベルです (ゲームが要求する値を使用する場合は「自動」を設定してください)", + "AspectRatioTooltip": "レンダリングに適用されるアスペクト比です.", + "ShaderDumpPathTooltip": "グラフィックス シェーダー ダンプのパスです", + "FileLogTooltip": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.", + "StubLogTooltip": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "InfoLogTooltip": "info ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "WarnLogTooltip": "warning ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "ErrorLogTooltip": "error ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "TraceLogTooltip": "trace ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "GuestLogTooltip": "guest ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "FileAccessLogTooltip": "ファイルアクセスログメッセージをコンソールに出力します.", + "FSAccessLogModeTooltip": "コンソールへのファイルシステムアクセスログ出力を有効にします.0-3 のモードが有効です", + "DeveloperOptionTooltip": "使用上の注意", + "OpenGlLogLevel": "適切なログレベルを有効にする必要があります", + "DebugLogTooltip": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.", + "LoadApplicationFileTooltip": "ロードする Switch 互換のファイルを選択するためファイルエクスプローラを開きます", + "LoadApplicationFolderTooltip": "ロードする Switch 互換の展開済みアプリケーションを選択するためファイルエクスプローラを開きます", + "OpenRyujinxFolderTooltip": "Ryujinx ファイルシステムフォルダを開きます", + "OpenRyujinxLogsTooltip": "ログが格納されるフォルダを開きます", + "ExitTooltip": "Ryujinx を終了します", + "OpenSettingsTooltip": "設定ウインドウを開きます", + "OpenProfileManagerTooltip": "ユーザプロファイル管理ウインドウを開きます", + "StopEmulationTooltip": "ゲームのエミュレーションを停止してゲーム選択画面に戻ります", + "CheckUpdatesTooltip": "Ryujinx のアップデートを確認します", + "OpenAboutTooltip": "Ryujinx についてのウインドウを開きます", + "GridSize": "グリッドサイズ", + "GridSizeTooltip": "グリッドサイズを変更します", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "ポルトガル語(ブラジル)", + "AboutRyujinxContributorsButtonHeader": "すべての貢献者を確認", + "SettingsTabSystemAudioVolume": "音量: ", + "AudioVolumeTooltip": "音量を変更します", + "SettingsTabSystemEnableInternetAccess": "ゲストインターネットアクセス / LAN モード", + "EnableInternetAccessTooltip": "エミュレートしたアプリケーションをインターネットに接続できるようにします.\n\nLAN モードを持つゲーム同士は,この機能を有効にして同じアクセスポイントに接続すると接続できます. 実機も含まれます.\n\n任天堂のサーバーには接続できません. インターネットに接続しようとすると,特定のゲームでクラッシュすることがあります.\n\nよくわからない場合はオフのままにしてください.", + "GameListContextMenuManageCheatToolTip": "チートを管理します", + "GameListContextMenuManageCheat": "チートを管理", + "ControllerSettingsStickRange": "範囲:", + "DialogStopEmulationTitle": "Ryujinx - エミュレーションを停止", + "DialogStopEmulationMessage": "エミュレーションを停止してよろしいですか?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音声", + "SettingsTabNetwork": "ネットワーク", + "SettingsTabNetworkConnection": "ネットワーク接続", + "SettingsTabCpuCache": "CPU キャッシュ", + "SettingsTabCpuMemory": "CPU メモリ", + "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub を使用して Ryujinx をアップデートしてください.", + "UpdaterDisabledWarningTitle": "アップデータは無効です!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", + "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用にパッケージされた Mod データに有用です.", + "ControllerSettingsRotate90": "時計回りに 90° 回転", + "IconSize": "アイコンサイズ", + "IconSizeTooltip": "ゲームアイコンのサイズを変更します", + "MenuBarOptionsShowConsole": "コンソールを表示", + "ShaderCachePurgeError": "シェーダーキャッシュの破棄エラー {0}: {1}", + "UserErrorNoKeys": "Keys がありません", + "UserErrorNoFirmware": "ファームウェアがありません", + "UserErrorFirmwareParsingFailed": "ファームウェアのパーズエラー", + "UserErrorApplicationNotFound": "アプリケーションがありません", + "UserErrorUnknown": "不明なエラー", + "UserErrorUndefined": "未定義エラー", + "UserErrorNoKeysDescription": "'prod.keys' が見つかりませんでした", + "UserErrorNoFirmwareDescription": "インストールされたファームウェアが見つかりませんでした", + "UserErrorFirmwareParsingFailedDescription": "ファームウェアをパーズできませんでした.通常,古いキーが原因です.", + "UserErrorApplicationNotFoundDescription": "指定されたパスに有効なアプリケーションがありませんでした.", + "UserErrorUnknownDescription": "不明なエラーが発生しました!", + "UserErrorUndefinedDescription": "未定義のエラーが発生しました! 発生すべきものではないので,開発者にご連絡ください!", + "OpenSetupGuideMessage": "セットアップガイドを開く", + "NoUpdate": "アップデートなし", + "TitleUpdateVersionLabel": "バージョン {0} - {1}", + "RyujinxInfo": "Ryujinx - 情報", + "RyujinxConfirm": "Ryujinx - 確認", + "FileDialogAllTypes": "すべての種別", + "Never": "決して", + "SwkbdMinCharacters": "最低 {0} 文字必要です", + "SwkbdMinRangeCharacters": "{0}-{1} 文字にしてください", + "SoftwareKeyboard": "ソフトウェアキーボード", + "SoftwareKeyboardModeNumbersOnly": "数字のみ", + "SoftwareKeyboardModeAlphabet": "CJK文字以外のみ", + "SoftwareKeyboardModeASCII": "ASCII文字列のみ", + "DialogControllerAppletMessagePlayerRange": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", + "DialogControllerAppletMessage": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", + "DialogControllerAppletDockModeSet": "ドッキングモードに設定されました. 携帯モードは無効になります.\n\n", + "UpdaterRenaming": "古いファイルをリネーム中...", + "UpdaterRenameFailed": "ファイルをリネームできませんでした: {0}", + "UpdaterAddingFiles": "新規ファイルを追加中...", + "UpdaterExtracting": "アップデートを展開中...", + "UpdaterDownloading": "アップデートをダウンロード中...", + "Game": "ゲーム", + "Docked": "ドッキング", + "Handheld": "携帯", + "ConnectionError": "接続エラー.", + "AboutPageDeveloperListMore": "{0}, その他大勢...", + "ApiError": "API エラー.", + "LoadingHeading": "ロード中: {0}", + "CompilingPPTC": "PTC をコンパイル中", + "CompilingShaders": "シェーダーをコンパイル中", + "AllKeyboards": "すべてのキーボード", + "OpenFileDialogTitle": "開くファイルを選択", + "OpenFolderDialogTitle": "展開されたゲームフォルダを選択", + "AllSupportedFormats": "すべての対応フォーマット", + "RyujinxUpdater": "Ryujinx アップデータ", + "SettingsTabHotkeys": "キーボード ホットキー", + "SettingsTabHotkeysHotkeys": "キーボード ホットキー", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync 切り替え:", + "SettingsTabHotkeysScreenshotHotkey": "スクリーンショット:", + "SettingsTabHotkeysShowUiHotkey": "UI表示:", + "SettingsTabHotkeysPauseHotkey": "中断:", + "SettingsTabHotkeysToggleMuteHotkey": "ミュート:", + "ControllerMotionTitle": "モーションコントロール設定", + "ControllerRumbleTitle": "振動設定", + "SettingsSelectThemeFileDialogTitle": "テーマファイルを選択", + "SettingsXamlThemeFile": "Xaml テーマファイル", + "AvatarWindowTitle": "アカウント - アバター管理", + "Amiibo": "Amiibo", + "Unknown": "不明", + "Usage": "使用法", + "Writable": "書き込み可能", + "SelectDlcDialogTitle": "DLC ファイルを選択", + "SelectUpdateDialogTitle": "アップデートファイルを選択", + "UserProfileWindowTitle": "ユーザプロファイルを管理", + "CheatWindowTitle": "チート管理", + "DlcWindowTitle": "DLC 管理", + "UpdateWindowTitle": "アップデート管理", + "CheatWindowHeading": "利用可能なチート {0} [{1}]", + "BuildId": "ビルドID:", + "DlcWindowHeading": "利用可能な DLC {0} [{1}]", + "UserProfilesEditProfile": "編集", + "Cancel": "キャンセル", + "Save": "セーブ", + "Discard": "破棄", + "UserProfilesSetProfileImage": "プロファイル画像を設定", + "UserProfileEmptyNameError": "名称が必要です", + "UserProfileNoImageError": "プロファイル画像が必要です", + "GameUpdateWindowHeading": "利用可能なアップデート {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "解像度を上げる:", + "SettingsTabHotkeysResScaleDownHotkey": "解像度を下げる:", + "UserProfilesName": "名称:", + "UserProfilesUserId": "ユーザID:", + "SettingsTabGraphicsBackend": "グラフィックスバックエンド", + "SettingsTabGraphicsBackendTooltip": "使用するグラフィックスバックエンドです", + "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効にする", + "SettingsEnableTextureRecompressionTooltip": "VRAMの使用量を削減するためテクスチャを圧縮します.\n\nGPUのVRAMが4GiB未満の場合は使用を推奨します.\n\nよくわからない場合はオフのままにしてください.", + "SettingsTabGraphicsPreferredGpu": "優先使用するGPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.", + "SettingsAppRequiredRestartMessage": "Ryujinx の再起動が必要です", + "SettingsGpuBackendRestartMessage": "グラフィックスバックエンドまたはGPUの設定が変更されました. 変更を適用するには再起動する必要があります", + "SettingsGpuBackendRestartSubMessage": "今すぐ再起動しますか?", + "RyujinxUpdaterMessage": "Ryujinx を最新版にアップデートしますか?", + "SettingsTabHotkeysVolumeUpHotkey": "音量を上げる:", + "SettingsTabHotkeysVolumeDownHotkey": "音量を下げる:", + "SettingsEnableMacroHLE": "マクロの高レベルエミュレーション (HLE) を有効にする", + "SettingsEnableMacroHLETooltip": "GPU マクロコードの高レベルエミュレーションです.\n\nパフォーマンスを向上させますが, 一部のゲームでグラフィックに不具合が発生する可能性があります.\n\nよくわからない場合はオンのままにしてください.", + "SettingsEnableColorSpacePassthrough": "色空間をパススルー", + "SettingsEnableColorSpacePassthroughTooltip": "Vulkan バックエンドに対して, 色空間を指定せずに色情報を渡します. 高色域ディスプレイを使用する場合, 正確ではないですがより鮮やかな色になる可能性があります.", + "VolumeShort": "音量", + "UserProfilesManageSaves": "セーブデータの管理", + "DeleteUserSave": "このゲームのユーザセーブデータを削除しますか?", + "IrreversibleActionNote": "この操作は元に戻せません.", + "SaveManagerHeading": "{0} のセーブデータを管理", + "SaveManagerTitle": "セーブデータマネージャ", + "Name": "名称", + "Size": "サイズ", + "Search": "検索", + "UserProfilesRecoverLostAccounts": "アカウントの復旧", + "Recover": "復旧", + "UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました", + "UserProfilesRecoverEmptyList": "復元するプロファイルはありません", + "GraphicsAATooltip": "ゲームのレンダリングにアンチエイリアスを適用します", + "GraphicsAALabel": "アンチエイリアス:", + "GraphicsScalingFilterLabel": "スケーリングフィルタ:", + "GraphicsScalingFilterTooltip": "フレームバッファスケーリングを有効にします", + "GraphicsScalingFilterLevelLabel": "レベル", + "GraphicsScalingFilterLevelTooltip": "スケーリングフィルタのレベルを設定", + "SmaaLow": "SMAA Low", + "SmaaMedium": "SMAA Medium", + "SmaaHigh": "SMAA High", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "ユーザを編集", + "UserEditorTitleCreate": "ユーザを作成", + "SettingsTabNetworkInterface": "ネットワークインタフェース:", + "NetworkInterfaceTooltip": "LAN機能に使用されるネットワークインタフェース", + "NetworkInterfaceDefault": "デフォルト", + "PackagingShaders": "シェーダーを構築中", + "AboutChangelogButton": "GitHub で更新履歴を表示", + "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/ko_KR.json b/src/Ryujinx.Ava/Assets/Locales/ko_KR.json new file mode 100644 index 000000000..cdc617222 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/ko_KR.json @@ -0,0 +1,656 @@ +{ + "Language": "한국어", + "MenuBarFileOpenApplet": "애플릿 열기", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드에서 Mii 편집기 애플릿 열기", + "SettingsTabInputDirectMouseAccess": "직접 마우스 접속", + "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드:", + "SettingsTabSystemMemoryManagerModeSoftware": "소프트웨어", + "SettingsTabSystemMemoryManagerModeHost": "호스트 (빠름)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "호스트 확인 안함 (가장 빠르나 안전하지 않음)", + "SettingsTabSystemUseHypervisor": "하이퍼바이저 사용하기", + "MenuBarFile": "_파일", + "MenuBarFileOpenFromFile": "_파일에서 응용 프로그램 불러오기", + "MenuBarFileOpenUnpacked": "_압축을 푼 게임 불러오기", + "MenuBarFileOpenEmuFolder": "Ryujinx 폴더 열기", + "MenuBarFileOpenLogsFolder": "로그 폴더 열기", + "MenuBarFileExit": "_종료", + "MenuBarOptions": "옵션", + "MenuBarOptionsToggleFullscreen": "전체화면 전환", + "MenuBarOptionsStartGamesInFullscreen": "전체 화면 모드에서 게임 시작", + "MenuBarOptionsStopEmulation": "에뮬레이션 중지", + "MenuBarOptionsSettings": "_설정", + "MenuBarOptionsManageUserProfiles": "_사용자 프로파일 관리", + "MenuBarActions": "_동작", + "MenuBarOptionsSimulateWakeUpMessage": "깨우기 메시지 시뮬레이션", + "MenuBarActionsScanAmiibo": "Amiibo 스캔", + "MenuBarTools": "_도구", + "MenuBarToolsInstallFirmware": "펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI 또는 ZIP에서 펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromDirectory": "디렉터리에서 펌웨어 설치", + "MenuBarToolsManageFileTypes": "파일 형식 관리", + "MenuBarToolsInstallFileTypes": "파일 형식 설치", + "MenuBarToolsUninstallFileTypes": "파일 형식 설치 제거", + "MenuBarHelp": "도움말", + "MenuBarHelpCheckForUpdates": "업데이트 확인", + "MenuBarHelpAbout": "정보", + "MenuSearch": "검색...", + "GameListHeaderFavorite": "즐겨찾기", + "GameListHeaderIcon": "아이콘", + "GameListHeaderApplication": "이름", + "GameListHeaderDeveloper": "개발자", + "GameListHeaderVersion": "버전", + "GameListHeaderTimePlayed": "플레이 시간", + "GameListHeaderLastPlayed": "마지막 플레이", + "GameListHeaderFileExtension": "파일 확장자", + "GameListHeaderFileSize": "파일 크기", + "GameListHeaderPath": "경로", + "GameListContextMenuOpenUserSaveDirectory": "사용자 저장 디렉터리 열기", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "응용프로그램의 사용자 저장이 포함된 디렉터리 열기", + "GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉터리 열기", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "응용프로그램의 장치 저장이 포함된 디렉터리 열기", + "GameListContextMenuOpenBcatSaveDirectory": "BCAT 저장 디렉터리 열기", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "응용프로그램의 BCAT 저장이 포함된 디렉터리 열기", + "GameListContextMenuManageTitleUpdates": "타이틀 업데이트 관리", + "GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기", + "GameListContextMenuManageDlc": "DLC 관리", + "GameListContextMenuManageDlcToolTip": "DLC 관리 창 열기", + "GameListContextMenuOpenModsDirectory": "Mod 디렉터리 열기", + "GameListContextMenuOpenModsDirectoryToolTip": "응용프로그램의 Mod가 포함된 디렉터리 열기", + "GameListContextMenuCacheManagement": "캐시 관리", + "GameListContextMenuCacheManagementPurgePptc": "대기열 PPTC 재구성", + "GameListContextMenuCacheManagementPurgePptcToolTip": "다음 게임 시작에서 부팅 시 PPTC가 다시 빌드하도록 트리거", + "GameListContextMenuCacheManagementPurgeShaderCache": "셰이더 캐시 제거", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "응용프로그램 셰이더 캐시 삭제\n", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC 디렉터리 열기", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "응용프로그램 PPTC 캐시가 포함된 디렉터리 열기", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "셰이더 캐시 디렉터리 열기", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "응용프로그램 셰이더 캐시가 포함된 디렉터리 열기", + "GameListContextMenuExtractData": "데이터 추출", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "응용프로그램의 현재 구성에서 ExeFS 추출 (업데이트 포함)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "응용 프로그램의 현재 구성에서 RomFS 추출 (업데이트 포함)", + "GameListContextMenuExtractDataLogo": "로고", + "GameListContextMenuExtractDataLogoToolTip": "응용프로그램의 현재 구성에서 로고 섹션 추출 (업데이트 포함)", + "StatusBarGamesLoaded": "{0}/{1}개의 게임 불러옴", + "StatusBarSystemVersion": "시스템 버전 : {0}", + "LinuxVmMaxMapCountDialogTitle": "감지된 메모리 매핑의 하한선", + "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count의 값을 {0}으로 늘리시겠습니까?", + "LinuxVmMaxMapCountDialogTextSecondary": "일부 게임은 현재 허용된 것보다 더 많은 메모리 매핑을 생성하려고 시도할 수 있습니다. 이 제한을 초과하는 즉시 Ryujinx에 문제가 발생합니다.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "예, 다음에 다시 시작할 때까지", + "LinuxVmMaxMapCountDialogButtonPersistent": "예, 영구적으로", + "LinuxVmMaxMapCountWarningTextPrimary": "메모리 매핑의 최대 용량이 권장 용량보다 적습니다.", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count({0})의 현재 값이 {1}보다 낮습니다. 일부 게임은 현재 허용된 것보다 더 많은 메모리 매핑을 생성하려고 시도할 수 있습니다. 이 제한을 초과하는 즉시 Ryujinx에 문제가 발생합니다.\n\n수동으로 제한을 늘리거나 Ryujinx의 도움을 받을 수 있는 pkexec을 설치하는 것이 좋습니다.", + "Settings": "설정", + "SettingsTabGeneral": "사용자 인터페이스", + "SettingsTabGeneralGeneral": "일반", + "SettingsTabGeneralEnableDiscordRichPresence": "디스코드 활동 상태 활성화", + "SettingsTabGeneralCheckUpdatesOnLaunch": "시작 시, 업데이트 확인", + "SettingsTabGeneralShowConfirmExitDialog": "\"종료 확인\" 대화 상자 표시", + "SettingsTabGeneralHideCursor": "마우스 커서 숨기기", + "SettingsTabGeneralHideCursorNever": "절대 안 함", + "SettingsTabGeneralHideCursorOnIdle": "유휴 상태", + "SettingsTabGeneralHideCursorAlways": "언제나", + "SettingsTabGeneralGameDirectories": "게임 디렉터리", + "SettingsTabGeneralAdd": "추가", + "SettingsTabGeneralRemove": "제거", + "SettingsTabSystem": "시스템", + "SettingsTabSystemCore": "코어", + "SettingsTabSystemSystemRegion": "시스템 지역:", + "SettingsTabSystemSystemRegionJapan": "일본", + "SettingsTabSystemSystemRegionUSA": "미국", + "SettingsTabSystemSystemRegionEurope": "유럽", + "SettingsTabSystemSystemRegionAustralia": "호주", + "SettingsTabSystemSystemRegionChina": "중국", + "SettingsTabSystemSystemRegionKorea": "한국", + "SettingsTabSystemSystemRegionTaiwan": "대만", + "SettingsTabSystemSystemLanguage": "시스템 언어 :", + "SettingsTabSystemSystemLanguageJapanese": "일본어", + "SettingsTabSystemSystemLanguageAmericanEnglish": "영어(미국)", + "SettingsTabSystemSystemLanguageFrench": "프랑스어", + "SettingsTabSystemSystemLanguageGerman": "독일어", + "SettingsTabSystemSystemLanguageItalian": "이탈리아어", + "SettingsTabSystemSystemLanguageSpanish": "스페인어", + "SettingsTabSystemSystemLanguageChinese": "중국어", + "SettingsTabSystemSystemLanguageKorean": "한국어", + "SettingsTabSystemSystemLanguageDutch": "네덜란드어", + "SettingsTabSystemSystemLanguagePortuguese": "포르투갈어", + "SettingsTabSystemSystemLanguageRussian": "러시아어", + "SettingsTabSystemSystemLanguageTaiwanese": "대만어", + "SettingsTabSystemSystemLanguageBritishEnglish": "영어(영국)", + "SettingsTabSystemSystemLanguageCanadianFrench": "프랑스어(캐나다)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "스페인어(라틴 아메리카)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "중국어 간체", + "SettingsTabSystemSystemLanguageTraditionalChinese": "중국어 번체", + "SettingsTabSystemSystemTimeZone": "시스템 시간대:", + "SettingsTabSystemSystemTime": "시스템 시간:", + "SettingsTabSystemEnableVsync": "수직 동기화", + "SettingsTabSystemEnablePptc": "PPTC(프로파일된 영구 번역 캐시)", + "SettingsTabSystemEnableFsIntegrityChecks": "파일 시스템 무결성 검사", + "SettingsTabSystemAudioBackend": "음향 후단부 :", + "SettingsTabSystemAudioBackendDummy": "더미", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "사운드IO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "해킹", + "SettingsTabSystemHacksNote": "불안정성을 유발할 수 있음", + "SettingsTabSystemExpandDramSize": "대체 메모리 레이아웃 사용(개발자)", + "SettingsTabSystemIgnoreMissingServices": "누락된 서비스 무시", + "SettingsTabGraphics": "그래픽", + "SettingsTabGraphicsAPI": "그래픽 API", + "SettingsTabGraphicsEnableShaderCache": "셰이더 캐시 활성화", + "SettingsTabGraphicsAnisotropicFiltering": "이방성 필터링 :", + "SettingsTabGraphicsAnisotropicFilteringAuto": "자동", + "SettingsTabGraphicsAnisotropicFiltering2x": "2배", + "SettingsTabGraphicsAnisotropicFiltering4x": "4배", + "SettingsTabGraphicsAnisotropicFiltering8x": "8배", + "SettingsTabGraphicsAnisotropicFiltering16x": "16배", + "SettingsTabGraphicsResolutionScale": "해상도 배율 :", + "SettingsTabGraphicsResolutionScaleCustom": "사용자 정의(권장하지 않음)", + "SettingsTabGraphicsResolutionScaleNative": "원본(720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2배(1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3배(2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4배(2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "종횡비 :", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "창에 맞게 늘리기", + "SettingsTabGraphicsDeveloperOptions": "개발자 옵션", + "SettingsTabGraphicsShaderDumpPath": "그래픽 셰이더 덤프 경로 :", + "SettingsTabLogging": "로그 기록", + "SettingsTabLoggingLogging": "로그 기록", + "SettingsTabLoggingEnableLoggingToFile": "파일에 로그 기록 활성화", + "SettingsTabLoggingEnableStubLogs": "스텁 로그 활성화", + "SettingsTabLoggingEnableInfoLogs": "정보 로그 활성화", + "SettingsTabLoggingEnableWarningLogs": "경고 로그 활성화", + "SettingsTabLoggingEnableErrorLogs": "오류 로그 활성화", + "SettingsTabLoggingEnableTraceLogs": "추적 로그 활성화", + "SettingsTabLoggingEnableGuestLogs": "게스트 로그 활성화", + "SettingsTabLoggingEnableFsAccessLogs": "Fs 접속 로그 활성화", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs 전역 접속 로그 모드 :", + "SettingsTabLoggingDeveloperOptions": "개발자 옵션", + "SettingsTabLoggingDeveloperOptionsNote": "경고: 성능이 저하됨", + "SettingsTabLoggingGraphicsBackendLogLevel": "그래픽 후단부 로그 수준 :", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "없음", + "SettingsTabLoggingGraphicsBackendLogLevelError": "오류", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "느려짐", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "모두", + "SettingsTabLoggingEnableDebugLogs": "디버그 로그 활성화", + "SettingsTabInput": "입력", + "SettingsTabInputEnableDockedMode": "도킹 모드", + "SettingsTabInputDirectKeyboardAccess": "직접 키보드 접속", + "SettingsButtonSave": "저장", + "SettingsButtonClose": "닫기", + "SettingsButtonOk": "확인", + "SettingsButtonCancel": "취소", + "SettingsButtonApply": "적용", + "ControllerSettingsPlayer": "플레이어", + "ControllerSettingsPlayer1": "플레이어 1", + "ControllerSettingsPlayer2": "플레이어 2", + "ControllerSettingsPlayer3": "플레이어 3", + "ControllerSettingsPlayer4": "플레이어 4", + "ControllerSettingsPlayer5": "플레이어 5", + "ControllerSettingsPlayer6": "플레이어 6", + "ControllerSettingsPlayer7": "플레이어 7", + "ControllerSettingsPlayer8": "플레이어 8", + "ControllerSettingsHandheld": "휴대 모드", + "ControllerSettingsInputDevice": "입력 장치", + "ControllerSettingsRefresh": "새로 고침", + "ControllerSettingsDeviceDisabled": "비활성화됨", + "ControllerSettingsControllerType": "컨트롤러 유형", + "ControllerSettingsControllerTypeHandheld": "휴대 모드", + "ControllerSettingsControllerTypeProController": "프로 컨트롤러", + "ControllerSettingsControllerTypeJoyConPair": "조이콘 페어링", + "ControllerSettingsControllerTypeJoyConLeft": "좌측 조이콘", + "ControllerSettingsControllerTypeJoyConRight": "우측 조이콘", + "ControllerSettingsProfile": "프로필", + "ControllerSettingsProfileDefault": "기본", + "ControllerSettingsLoad": "불러오기", + "ControllerSettingsAdd": "추가", + "ControllerSettingsRemove": "제거", + "ControllerSettingsButtons": "버튼", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "방향 패드", + "ControllerSettingsDPadUp": "↑", + "ControllerSettingsDPadDown": "↓", + "ControllerSettingsDPadLeft": "←", + "ControllerSettingsDPadRight": "→", + "ControllerSettingsStickButton": "버튼", + "ControllerSettingsStickUp": "↑", + "ControllerSettingsStickDown": "↓", + "ControllerSettingsStickLeft": "←", + "ControllerSettingsStickRight": "→", + "ControllerSettingsStickStick": "스틱", + "ControllerSettingsStickInvertXAxis": "스틱 X 축 반전", + "ControllerSettingsStickInvertYAxis": "스틱 Y 축 반전", + "ControllerSettingsStickDeadzone": "사각지대 :", + "ControllerSettingsLStick": "좌측 스틱", + "ControllerSettingsRStick": "우측 스틱", + "ControllerSettingsTriggersLeft": "좌측 트리거", + "ControllerSettingsTriggersRight": "우측 트리거", + "ControllerSettingsTriggersButtonsLeft": "좌측 트리거 버튼", + "ControllerSettingsTriggersButtonsRight": "우측 트리거 버튼", + "ControllerSettingsTriggers": "트리거 버튼", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "좌측 버튼", + "ControllerSettingsExtraButtonsRight": "우측 버튼", + "ControllerSettingsMisc": "기타", + "ControllerSettingsTriggerThreshold": "트리거 임계값 :", + "ControllerSettingsMotion": "동작", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 호환 모션 사용", + "ControllerSettingsMotionControllerSlot": "컨트롤러 슬롯 :", + "ControllerSettingsMotionMirrorInput": "미러 입력", + "ControllerSettingsMotionRightJoyConSlot": "우측 조이콘 슬롯 :", + "ControllerSettingsMotionServerHost": "서버 호스트 :", + "ControllerSettingsMotionGyroSensitivity": "자이로 감도 :", + "ControllerSettingsMotionGyroDeadzone": "자이로 사각지대 :", + "ControllerSettingsSave": "저장", + "ControllerSettingsClose": "닫기", + "UserProfilesSelectedUserProfile": "선택한 사용자 프로필 :", + "UserProfilesSaveProfileName": "프로필 이름 저장", + "UserProfilesChangeProfileImage": "프로필 이미지 변경", + "UserProfilesAvailableUserProfiles": "사용 가능한 사용자 프로필 :", + "UserProfilesAddNewProfile": "프로필 생성", + "UserProfilesDelete": "삭제", + "UserProfilesClose": "닫기", + "ProfileNameSelectionWatermark": "닉네임을 입력하세요", + "ProfileImageSelectionTitle": "프로필 이미지 선택", + "ProfileImageSelectionHeader": "프로필 이미지 선택", + "ProfileImageSelectionNote": "사용자 지정 프로필 이미지를 가져오거나 시스템 펌웨어에서 아바타 선택 가능", + "ProfileImageSelectionImportImage": "이미지 파일 가져오기", + "ProfileImageSelectionSelectAvatar": "펌웨어 아바타 선택", + "InputDialogTitle": "입력 대화상자", + "InputDialogOk": "확인", + "InputDialogCancel": "취소", + "InputDialogAddNewProfileTitle": "프로필 이름 선택", + "InputDialogAddNewProfileHeader": "프로필 이름 입력", + "InputDialogAddNewProfileSubtext": "(최대 길이 : {0})", + "AvatarChoose": "선택", + "AvatarSetBackgroundColor": "배경색 설정", + "AvatarClose": "닫기", + "ControllerSettingsLoadProfileToolTip": "프로필 불러오기", + "ControllerSettingsAddProfileToolTip": "프로필 추가", + "ControllerSettingsRemoveProfileToolTip": "프로필 제거", + "ControllerSettingsSaveProfileToolTip": "프로필 저장", + "MenuBarFileToolsTakeScreenshot": "스크린 샷 찍기", + "MenuBarFileToolsHideUi": "UI 숨기기", + "GameListContextMenuRunApplication": "응용프로그램 실행", + "GameListContextMenuToggleFavorite": "즐겨찾기 전환", + "GameListContextMenuToggleFavoriteToolTip": "게임 즐겨찾기 상태 전환", + "SettingsTabGeneralTheme": "테마", + "SettingsTabGeneralThemeCustomTheme": "커스텀 테마 경로", + "SettingsTabGeneralThemeBaseStyle": "기본 스타일", + "SettingsTabGeneralThemeBaseStyleDark": "어두움", + "SettingsTabGeneralThemeBaseStyleLight": "밝음", + "SettingsTabGeneralThemeEnableCustomTheme": "사용자 정의 테마 활성화", + "ButtonBrowse": "찾아보기", + "ControllerSettingsConfigureGeneral": "구성", + "ControllerSettingsRumble": "진동", + "ControllerSettingsRumbleStrongMultiplier": "강력한 진동 증폭기", + "ControllerSettingsRumbleWeakMultiplier": "약한 진동 증폭기", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}]에 대한 저장 데이터가 없음", + "DialogMessageSaveNotAvailableCreateSaveMessage": "이 게임에 대한 저장 데이터를 생성하겠습니까?", + "DialogConfirmationTitle": "Ryujinx - 확인", + "DialogUpdaterTitle": "Ryujinx - 업데이터", + "DialogErrorTitle": "Ryujinx - 오류", + "DialogWarningTitle": "Ryujinx - 경고", + "DialogExitTitle": "Ryujinx - 종료", + "DialogErrorMessage": "Ryujinx 오류 발생", + "DialogExitMessage": "Ryujinx를 종료하겠습니까?", + "DialogExitSubMessage": "저장하지 않은 모든 데이터는 손실됩니다!", + "DialogMessageCreateSaveErrorMessage": "지정된 저장 데이터를 작성하는 중에 오류 발생: {0}", + "DialogMessageFindSaveErrorMessage": "지정된 저장 데이터를 찾는 중에 오류 발생: {0}", + "FolderDialogExtractTitle": "추출할 폴더 선택", + "DialogNcaExtractionMessage": "{1}에서 {0} 섹션을 추출하는 중...", + "DialogNcaExtractionTitle": "Ryujinx - NCA 섹션 추출기", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출 실패하였습니다. 선택한 파일에 기본 NCA가 없습니다.", + "DialogNcaExtractionCheckLogErrorMessage": "추출 실패하였습니다. 자세한 내용은 로그 파일을 읽으세요.", + "DialogNcaExtractionSuccessMessage": "추출이 성공적으로 완료되었습니다.", + "DialogUpdaterConvertFailedMessage": "현재 Ryujinx 버전을 변환하지 못했습니다.", + "DialogUpdaterCancelUpdateMessage": "업데이트 취소 중 입니다!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "이미 최신 버전의 Ryujinx를 사용하고 있습니다!", + "DialogUpdaterFailedToGetVersionMessage": "GitHub 릴리스에서 릴리스 정보를 가져오는 중에 오류가 발생했습니다. 이는 GitHub Actions에서 새 릴리스를 컴파일하는 경우 발생할 수 있습니다. 몇 분 후에 다시 시도하세요.", + "DialogUpdaterConvertFailedGithubMessage": "Github 개정에서 받은 Ryujinx 버전을 변환하지 못했습니다.", + "DialogUpdaterDownloadingMessage": "업데이트 다운로드 중...", + "DialogUpdaterExtractionMessage": "업데이트 추출 중...", + "DialogUpdaterRenamingMessage": "업데이트 이름 바꾸는 중...", + "DialogUpdaterAddingFilesMessage": "새 업데이트 추가 중...", + "DialogUpdaterCompleteMessage": "업데이트를 완료했습니다!", + "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하겠습니까?", + "DialogUpdaterArchNotSupportedMessage": "지원되는 시스템 아키텍처를 실행하고 있지 않습니다!", + "DialogUpdaterArchNotSupportedSubMessage": "(64비트 시스템만 지원됩니다!)", + "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", + "DialogUpdaterNoInternetSubMessage": "인터넷 연결이 작동하는지 확인하세요!", + "DialogUpdaterDirtyBuildMessage": "Ryujinx의 나쁜 빌드는 업데이트할 수 없습니다!\n", + "DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾고 있다면 https://ryujinx.org/에서 Ryujinx를 다운로드하세요.", + "DialogRestartRequiredMessage": "재시작 필요", + "DialogThemeRestartMessage": "테마가 저장되었습니다. 테마를 적용하려면 다시 시작해야 합니다.", + "DialogThemeRestartSubMessage": "다시 시작하겠습니까?", + "DialogFirmwareInstallEmbeddedMessage": "이 게임에 내장된 펌웨어를 설치하겠습니까? (펌웨어 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어가 없지만 Ryujinx가 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있었습니다.\\n이제 에뮬레이터가 시작됩니다.", + "DialogFirmwareNoFirmwareInstalledMessage": "설치된 펌웨어 없음", + "DialogFirmwareInstalledMessage": "펌웨어 {0}이(가) 설치됨", + "DialogInstallFileTypesSuccessMessage": "파일 형식을 성공적으로 설치했습니다!", + "DialogInstallFileTypesErrorMessage": "파일 형식을 설치하지 못했습니다.", + "DialogUninstallFileTypesSuccessMessage": "파일 형식을 성공적으로 제거했습니다!", + "DialogUninstallFileTypesErrorMessage": "파일 형식을 제거하지 못했습니다.", + "DialogOpenSettingsWindowLabel": "설정 창 열기", + "DialogControllerAppletTitle": "컨트롤러 애플릿", + "DialogMessageDialogErrorExceptionMessage": "메시지 대화상자를 표시하는 동안 오류 발생 : {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "소프트웨어 키보드를 표시하는 동안 오류 발생 : {0}", + "DialogErrorAppletErrorExceptionMessage": "오류에플릿 대화상자를 표시하는 동안 오류 발생 : {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n이 오류를 수정하는 방법에 대한 자세한 내용은 설정 가이드를 따르세요.", + "DialogUserErrorDialogTitle": "Ryuijnx 오류 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API에서 정보를 가져오는 동안 오류가 발생했습니다.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API 서버에 연결할 수 없습니다. 서비스가 다운되었거나 인터넷 연결이 온라인 상태인지 확인해야 할 수 있습니다.", + "DialogProfileInvalidProfileErrorMessage": "{0} 프로필은 현재 입력 구성 시스템과 호환되지 않습니다.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "기본 프로필을 덮어쓸 수 없음", + "DialogProfileDeleteProfileTitle": "프로필 삭제", + "DialogProfileDeleteProfileMessage": "이 작업은 되돌릴 수 없습니다. 계속하겠습니까?", + "DialogWarning": "경고", + "DialogPPTCDeletionMessage": "다음 부팅 시, PPTC 재구축을 대기열에 추가 :\n\n{0}\n\n계속하겠습니까?", + "DialogPPTCDeletionErrorMessage": "{0}에서 PPTC 캐시 삭제 오류 : {1}", + "DialogShaderDeletionMessage": "다음에 대한 셰이더 캐시 삭제 :\n\n{0}\n\n계속하겠습니까?", + "DialogShaderDeletionErrorMessage": "{0}에서 셰이더 캐시 제거 오류 : {1}", + "DialogRyujinxErrorMessage": "Ryujinx에 오류 발생", + "DialogInvalidTitleIdErrorMessage": "UI 오류 : 선택한 게임에 유효한 타이틀 ID가 없음", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0}에서 유효한 시스템 펌웨어를 찾을 수 없습니다.", + "DialogFirmwareInstallerFirmwareInstallTitle": "펌웨어 {0} 설치", + "DialogFirmwareInstallerFirmwareInstallMessage": "시스템 버전 {0}이(가) 설치됩니다.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n이것은 현재 시스템 버전 {0}을(를) 대체합니다.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n계속하겠습니까?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "펌웨어 설치 중...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "시스템 버전 {0}이(가) 성공적으로 설치되었습니다.", + "DialogUserProfileDeletionWarningMessage": "선택한 프로파일이 삭제되면 사용 가능한 다른 프로파일이 없음", + "DialogUserProfileDeletionConfirmMessage": "선택한 프로파일을 삭제하겠습니까?", + "DialogUserProfileUnsavedChangesTitle": "경고 - 변경사항 저장되지 않음", + "DialogUserProfileUnsavedChangesMessage": "저장되지 않은 사용자 프로파일을 수정했습니다.", + "DialogUserProfileUnsavedChangesSubMessage": "변경사항을 저장하지 않으시겠습니까?", + "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", + "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", + "DialogLoadNcaErrorMessage": "{0}. 오류 발생 파일 : {1}", + "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", + "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로그 기록이 활성화되어 있습니다.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로그 생성을 비활성화하는 것이 좋습니다. 지금 추적 로그 기록을 비활성화하겠습니까?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "개발자만 사용하도록 설계된 셰이더 덤프를 활성화했습니다.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "최적의 성능을 위해 세이더 덤핑을 비활성화하는 것이 좋습니다. 지금 세이더 덤핑을 비활성화하겠습니까?", + "DialogLoadAppGameAlreadyLoadedMessage": "이미 게임 불러옴", + "DialogLoadAppGameAlreadyLoadedSubMessage": "다른 게임을 시작하기 전에 에뮬레이션을 중지하거나 에뮬레이터를 닫으세요.", + "DialogUpdateAddUpdateErrorMessage": "지정된 파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", + "DialogSettingsBackendThreadingWarningTitle": "경고 - 후단부 스레딩", + "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후, Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", + "SettingsTabGraphicsFeaturesOptions": "기능", + "SettingsTabGraphicsBackendMultithreading": "그래픽 후단부 멀티스레딩 :", + "CommonAuto": "자동", + "CommonOff": "끔", + "CommonOn": "켬", + "InputDialogYes": "예", + "InputDialogNo": "아니오", + "DialogProfileInvalidProfileNameErrorMessage": "파일 이름에 잘못된 문자가 포함되어 있습니다. 다시 시도하세요.", + "MenuBarOptionsPauseEmulation": "일시 정지", + "MenuBarOptionsResumeEmulation": "다시 시작", + "AboutUrlTooltipMessage": "기본 브라우저에서 Ryujinx 웹사이트를 열려면 클릭하세요.", + "AboutDisclaimerMessage": "Ryujinx는 닌텐도™,\n또는 그 파트너와 제휴한 바가 없습니다.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com)는\nAmiibo 에뮬레이션에 사용됩니다.", + "AboutPatreonUrlTooltipMessage": "기본 브라우저에서 Ryujinx Patreon 페이지를 열려면 클릭하세요.", + "AboutGithubUrlTooltipMessage": "기본 브라우저에서 Ryujinx GitHub 페이지를 열려면 클릭하세요.", + "AboutDiscordUrlTooltipMessage": "기본 브라우저에서 Ryujinx 디스코드 서버에 대한 초대를 열려면 클릭하세요.", + "AboutTwitterUrlTooltipMessage": "기본 브라우저에서 Ryujinx 트위터 페이지를 열려면 클릭하세요.", + "AboutRyujinxAboutTitle": "정보 :", + "AboutRyujinxAboutContent": "Ryujinx는 닌텐도 스위치™용 에뮬레이터입니다.\nPatreon에서 지원해 주세요.\n트위터나 디스코드에서 최신 소식을 받아보세요.\n기여에 참여하고자 하는 개발자는 GitHub 또는 디스코드에서 자세한 내용을 확인할 수 있습니다.", + "AboutRyujinxMaintainersTitle": "유지 관리 :", + "AboutRyujinxMaintainersContentTooltipMessage": "기본 브라우저에서 기여자 페이지를 열려면 클릭하세요.", + "AboutRyujinxSupprtersTitle": "Patreon에서 후원:", + "AmiiboSeriesLabel": "Amiibo 시리즈", + "AmiiboCharacterLabel": "캐릭터", + "AmiiboScanButtonLabel": "스캔", + "AmiiboOptionsShowAllLabel": "모든 Amiibo 표시", + "AmiiboOptionsUsRandomTagLabel": "해킹: 임의의 태그 UUID 사용", + "DlcManagerTableHeadingEnabledLabel": "활성화됨", + "DlcManagerTableHeadingTitleIdLabel": "타이틀 ID", + "DlcManagerTableHeadingContainerPathLabel": "컨테이너 경로", + "DlcManagerTableHeadingFullPathLabel": "전체 경로", + "DlcManagerRemoveAllButton": "모두 제거", + "DlcManagerEnableAllButton": "모두 활성화", + "DlcManagerDisableAllButton": "모두 비활성화", + "MenuBarOptionsChangeLanguage": "언어 변경", + "MenuBarShowFileTypes": "파일 유형 표시", + "CommonSort": "정렬", + "CommonShowNames": "이름 표시", + "CommonFavorite": "즐겨찾기", + "OrderAscending": "오름차순", + "OrderDescending": "내림차순", + "SettingsTabGraphicsFeatures": "기능ㆍ개선 사항", + "ErrorWindowTitle": "오류 창", + "ToggleDiscordTooltip": "\"현재 재생 중인\" 디스코드 활동에 Ryujinx를 표시할지 여부 선택", + "AddGameDirBoxTooltip": "목록에 추가할 게임 디렉터리 입력", + "AddGameDirTooltip": "목록에 게임 디렉터리 추가", + "RemoveGameDirTooltip": "선택한 게임 디렉터리 제거", + "CustomThemeCheckTooltip": "GUI에 사용자 지정 Avalonia 테마를 사용하여 에뮬레이터 메뉴의 모양 변경", + "CustomThemePathTooltip": "사용자 정의 GUI 테마 경로", + "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾아보기", + "DockModeToggleTooltip": "독 모드에서는 에뮬레이트된 시스템이 도킹된 닌텐도 스위치처럼 작동합니다. 이것은 대부분의 게임에서 그래픽 품질을 향상시킵니다. 반대로 이 기능을 비활성화하면 에뮬레이트된 시스템이 휴대용 닌텐도 스위치처럼 작동하여 그래픽 품질이 저하됩니다.\n\n독 모드를 사용하려는 경우 플레이어 1의 컨트롤을 구성하세요. 휴대 모드를 사용하려는 경우 휴대용 컨트롤을 구성하세요.\n\n확실하지 않으면 켜 두세요.", + "DirectKeyboardTooltip": "직접 키보드 접속 (HID) 지원합니다. 텍스트 입력 장치로 키보드에 대한 게임 접속을 제공합니다.", + "DirectMouseTooltip": "직접 마우스 접속 (HID) 지원합니다. 포인팅 장치로 마우스에 대한 게임 접속을 제공합니다.", + "RegionTooltip": "시스템 지역 변경", + "LanguageTooltip": "시스템 언어 변경", + "TimezoneTooltip": "시스템 시간대 변경", + "TimeTooltip": "시스템 시간 변경", + "VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화입니다. 기본적으로 대부분의 게임에 대한 프레임 제한 장치입니다. 비활성화하면 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다. 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n확실하지 않으면 켜 두세요.", + "PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.", + "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", + "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", + "MemoryManagerTooltip": "게스트 메모리가 매핑되고 접속되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 크게 영향을 미칩니다.\n\n확실하지 않은 경우 호스트 확인 안함으로 설정하세요.", + "MemoryManagerSoftwareTooltip": "주소 변환을 위해 소프트웨어 페이지 테이블을 사용하세요. 정확도는 가장 높지만 성능은 가장 느립니다.", + "MemoryManagerHostTooltip": "호스트 주소 공간의 메모리를 직접 매핑합니다. 훨씬 빠른 JIT 컴파일 및 실행합니다.", + "MemoryManagerUnsafeTooltip": "메모리를 직접 매핑하지만 접속하기 전에 게스트 주소 공간 내의 주소를 마스킹하지 마십시오. 더 빠르지만 안전을 희생해야 합니다. 게스트 응용 프로그램은 Ryujinx의 어디에서나 메모리에 접속할 수 있으므로 이 모드에서는 신뢰할 수 있는 프로그램만 실행하세요.", + "UseHypervisorTooltip": "JIT 대신 하이퍼바이저를 사용합니다. 하이퍼바이저를 사용할 수 있을 때 성능을 향상시키지만, 현재 상태에서는 불안정할 수 있습니다.", + "DRamTooltip": "대체 메모리모드 레이아웃을 활용하여 스위치 개발 모델을 모방합니다.\n\n고해상도 텍스처 팩 또는 4k 해상도 모드에만 유용합니다. 성능을 향상시키지 않습니다.\n\n확실하지 않으면 꺼 두세요.", + "IgnoreMissingServicesTooltip": "구현되지 않은 호라이즌 OS 서비스를 무시합니다. 이것은 특정 게임을 부팅할 때 충돌을 우회하는 데 도움이 될 수 있습니다.\n\n확실하지 않으면 꺼 두세요.", + "GraphicsBackendThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", + "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", + "ShaderCacheToggleTooltip": "후속 실행에서 끊김 현상을 줄이는 디스크 세이더 캐시를 저장합니다.\n\n확실하지 않으면 켜 두세요.", + "ResolutionScaleTooltip": "적용 가능한 렌더 타겟에 적용된 해상도 스케일", + "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 분해능 스케일입니다. 비통합 척도는 문제나 충돌을 일으킬 가능성이 더 큽니다.", + "AnisotropyTooltip": "비등방성 필터링 수준 (게임에서 요청한 값을 사용하려면 자동으로 설정)", + "AspectRatioTooltip": "렌더러 창에 적용된 화면비입니다.", + "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", + "FileLogTooltip": "디스크의 로그 파일에 콘솔 로깅을 저장합니다. 성능에 영향을 미치지 않습니다.", + "StubLogTooltip": "콘솔에 스텁 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "InfoLogTooltip": "콘솔에 정보 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "WarnLogTooltip": "콘솔에 경고 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "ErrorLogTooltip": "콘솔에 오류 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "TraceLogTooltip": "콘솔에 추적 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "GuestLogTooltip": "콘솔에 게스트 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "FileAccessLogTooltip": "콘솔에 파일 액세스 로그 메시지를 인쇄합니다.", + "FSAccessLogModeTooltip": "콘솔에 대한 FS 접속 로그 출력을 활성화합니다. 가능한 모드는 0-3\t\t\t\t", + "DeveloperOptionTooltip": "주의해서 사용", + "OpenGlLogLevel": "적절한 로그 수준을 활성화해야 함", + "DebugLogTooltip": "콘솔에 디버그 로그 메시지를 인쇄합니다.\n\n로그를 읽기 어렵게 만들고 에뮬레이터 성능을 악화시키므로 직원이 구체적으로 지시한 경우에만 사용하세요.", + "LoadApplicationFileTooltip": "파일 탐색기를 열어 불러올 스위치 호환 파일 선택", + "LoadApplicationFolderTooltip": "파일 탐색기를 열어 불러올 스위치 호환 압축 해제 응용 프로그램 선택", + "OpenRyujinxFolderTooltip": "Ryujinx 파일 시스템 폴더 열기", + "OpenRyujinxLogsTooltip": "로그가 기록된 폴더 열기", + "ExitTooltip": "Ryujinx 종료", + "OpenSettingsTooltip": "설정 창 열기", + "OpenProfileManagerTooltip": "사용자 프로파일 관리자 창 열기", + "StopEmulationTooltip": "현재 게임의 에뮬레이션을 중지하고 게임 선택으로 돌아감", + "CheckUpdatesTooltip": "Ryujinx 업데이트 확인", + "OpenAboutTooltip": "정보 창 열기", + "GridSize": "격자 크기", + "GridSizeTooltip": "격자 항목의 크기 변경", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "포르투갈어(브라질)", + "AboutRyujinxContributorsButtonHeader": "모든 기여자 보기", + "SettingsTabSystemAudioVolume": "음량 : ", + "AudioVolumeTooltip": "음향 음량 변경", + "SettingsTabSystemEnableInternetAccess": "게스트 인터넷 접속/LAN 모드", + "EnableInternetAccessTooltip": "에뮬레이션된 응용프로그램이 인터넷에 연결되도록 허용합니다.\n\nLAN 모드가 있는 게임은 이 모드가 활성화되고 시스템이 동일한 접속 포인트에 연결된 경우 서로 연결할 수 있습니다. 여기에는 실제 콘솔도 포함됩니다.\n\n닌텐도 서버에 연결할 수 없습니다. 인터넷에 연결을 시도하는 특정 게임에서 충돌이 발생할 수 있습니다.\n\n확실하지 않으면 꺼두세요.", + "GameListContextMenuManageCheatToolTip": "치트 관리", + "GameListContextMenuManageCheat": "치트 관리", + "ControllerSettingsStickRange": "범위 :", + "DialogStopEmulationTitle": "Ryujinx - 에뮬레이션 중지", + "DialogStopEmulationMessage": "에뮬레이션을 중지하겠습니까?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "오디오", + "SettingsTabNetwork": "네트워크", + "SettingsTabNetworkConnection": "네트워크 연결", + "SettingsTabCpuCache": "CPU 캐시", + "SettingsTabCpuMemory": "CPU 모드", + "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub를 통해 Ryujinx를 업데이트하세요.", + "UpdaterDisabledWarningTitle": "업데이터 비활성화입니다!", + "GameListContextMenuOpenSdModsDirectory": "분위기 모드 디렉터리 열기", + "GameListContextMenuOpenSdModsDirectoryToolTip": "응용프로그램의 모드가 포함된 대체 SD 카드 분위기 디렉터리를 엽니다. 실제 하드웨어용으로 패키징된 모드에 유용합니다.", + "ControllerSettingsRotate90": "시계 방향으로 90° 회전", + "IconSize": "아이콘 크기", + "IconSizeTooltip": "게임 아이콘 크기 변경", + "MenuBarOptionsShowConsole": "콘솔 표시", + "ShaderCachePurgeError": "{0}에서 셰이더 캐시를 제거하는 중 오류 발생: {1}", + "UserErrorNoKeys": "키를 찾을 수 없음", + "UserErrorNoFirmware": "펌웨어를 찾을 수 없음", + "UserErrorFirmwareParsingFailed": "펌웨어 구문 분석 오류", + "UserErrorApplicationNotFound": "응용 프로그램을 찾을 수 없음", + "UserErrorUnknown": "알 수 없는 오류", + "UserErrorUndefined": "정의되지 않은 오류", + "UserErrorNoKeysDescription": "Ryujinx가 'prod.keys' 파일을 찾을 수 없음", + "UserErrorNoFirmwareDescription": "Ryujinx가 설치된 펌웨어를 찾을 수 없음", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx가 제공된 펌웨어를 구문 분석할 수 없습니다. 일반적으로 오래된 키가 원인입니다.", + "UserErrorApplicationNotFoundDescription": "Ryujinx가 지정된 경로에서 유효한 응용 프로그램을 찾을 수 없습니다.", + "UserErrorUnknownDescription": "알 수 없는 오류가 발생했습니다!", + "UserErrorUndefinedDescription": "정의되지 않은 오류가 발생했습니다! 이런 일이 발생하면 안 되므로, 개발자에게 문의하세요!", + "OpenSetupGuideMessage": "설정 가이드 열기", + "NoUpdate": "업데이트 없음", + "TitleUpdateVersionLabel": "버전 {0}", + "RyujinxInfo": "Ryujinx - 정보", + "RyujinxConfirm": "Ryujinx - 확인", + "FileDialogAllTypes": "모든 유형", + "Never": "절대 안 함", + "SwkbdMinCharacters": "{0}자 이상이어야 함", + "SwkbdMinRangeCharacters": "{0}-{1}자여야 함", + "SoftwareKeyboard": "소프트웨어 키보드", + "SoftwareKeyboardModeNumbersOnly": "숫자만 가능", + "SoftwareKeyboardModeAlphabet": "한중일 문자가 아닌 문자만 가능", + "SoftwareKeyboardModeASCII": "ASCII 텍스트만 가능", + "DialogControllerAppletMessagePlayerRange": "응용 프로그램은 다음을 사용하는 {0} 명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", + "DialogControllerAppletMessage": "응용 프로그램은 다음을 사용하는 정확히 {0}명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", + "DialogControllerAppletDockModeSet": "독 모드가 설정되었습니다. 휴대 모드도 유효하지 않습니다.", + "UpdaterRenaming": "이전 파일 이름 바꾸는 중...", + "UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음: {0}", + "UpdaterAddingFiles": "새로운 파일을 추가하는 중...", + "UpdaterExtracting": "업데이트를 추출하는 중...", + "UpdaterDownloading": "업데이트 다운로드 중...", + "Game": "게임", + "Docked": "도킹됨", + "Handheld": "휴대용", + "ConnectionError": "연결 오류입니다.", + "AboutPageDeveloperListMore": "{0} 등...", + "ApiError": "API 오류입니다.", + "LoadingHeading": "{0} 로딩 중", + "CompilingPPTC": "PTC 컴파일 중", + "CompilingShaders": "셰이더 컴파일 중", + "AllKeyboards": "모든 키보드", + "OpenFileDialogTitle": "지원되는 파일을 선택", + "OpenFolderDialogTitle": "압축을 푼 게임이 있는 폴더 선택", + "AllSupportedFormats": "지원되는 모든 형식", + "RyujinxUpdater": "Ryujinx 업데이터", + "SettingsTabHotkeys": "키보드 단축키", + "SettingsTabHotkeysHotkeys": "키보드 단축키", + "SettingsTabHotkeysToggleVsyncHotkey": "수직 동기화 전환 :", + "SettingsTabHotkeysScreenshotHotkey": "스크린샷 :", + "SettingsTabHotkeysShowUiHotkey": "UI 표시 :", + "SettingsTabHotkeysPauseHotkey": "일시 중지 :", + "SettingsTabHotkeysToggleMuteHotkey": "음 소거 :", + "ControllerMotionTitle": "동작 제어 설정", + "ControllerRumbleTitle": "진동 설정", + "SettingsSelectThemeFileDialogTitle": "테마 파일 선택", + "SettingsXamlThemeFile": "Xaml 테마 파일", + "AvatarWindowTitle": "계정 관리 - 아바타", + "Amiibo": "Amiibo", + "Unknown": "알 수 없음", + "Usage": "사용법", + "Writable": "쓰기 가능", + "SelectDlcDialogTitle": "DLC 파일 선택", + "SelectUpdateDialogTitle": "업데이트 파일 선택", + "UserProfileWindowTitle": "사용자 프로파일 관리자", + "CheatWindowTitle": "치트 관리자", + "DlcWindowTitle": "{0} ({1})의 다운로드 가능한 콘텐츠 관리", + "UpdateWindowTitle": "타이틀 업데이트 관리자", + "CheatWindowHeading": "{0} [{1}]에 사용할 수 있는 치트", + "BuildId": "빌드ID :", + "DlcWindowHeading": "{0} 내려받기 가능한 콘텐츠", + "UserProfilesEditProfile": "선택된 항목 편집", + "Cancel": "취소", + "Save": "저장", + "Discard": "삭제", + "UserProfilesSetProfileImage": "프로파일 이미지 설정", + "UserProfileEmptyNameError": "이름 필요", + "UserProfileNoImageError": "프로파일 이미지를 설정해야 함", + "GameUpdateWindowHeading": "{0} ({1})에 대한 업데이트 관리", + "SettingsTabHotkeysResScaleUpHotkey": "해상도 증가 :", + "SettingsTabHotkeysResScaleDownHotkey": "해상도 감소 :", + "UserProfilesName": "이름 :", + "UserProfilesUserId": "사용자 ID :", + "SettingsTabGraphicsBackend": "그래픽 후단부", + "SettingsTabGraphicsBackendTooltip": "사용할 그래픽 후단부", + "SettingsEnableTextureRecompression": "텍스처 재압축 활성화", + "SettingsEnableTextureRecompressionTooltip": "VRAM 사용량을 줄이기 위해 특정 텍스처를 압축합니다.\n\n4GiB VRAM 미만의 GPU와 함께 사용하는 것이 좋습니다.\n\n확실하지 않으면 꺼 두세요.", + "SettingsTabGraphicsPreferredGpu": "선호하는 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan 그래픽 후단부와 함께 사용할 그래픽 카드를 선택하세요.\n\nOpenGL이 사용할 GPU에는 영향을 미치지 않습니다.\n\n확실하지 않은 경우 \"dGPU\" 플래그가 지정된 GPU로 설정하세요. 없는 경우, 그대로 두세요.", + "SettingsAppRequiredRestartMessage": "Ryujinx 다시 시작 필요", + "SettingsGpuBackendRestartMessage": "그래픽 후단부 또는 GPU 설정이 수정되었습니다. 적용하려면 다시 시작해야 합니다.", + "SettingsGpuBackendRestartSubMessage": "지금 다시 시작하겠습니까?", + "RyujinxUpdaterMessage": "Ryujinx를 최신 버전으로 업데이트하겠습니까?", + "SettingsTabHotkeysVolumeUpHotkey": "음량 증가 :", + "SettingsTabHotkeysVolumeDownHotkey": "음량 감소 :", + "SettingsEnableMacroHLE": "매크로 HLE 활성화", + "SettingsEnableMacroHLETooltip": "GPU 매크로 코드의 높은 수준 에뮬레이션입니다.\n\n성능이 향상되지만 일부 게임에서 그래픽 결함이 발생할 수 있습니다.\n\n확실하지 않으면 켜 두세요.", + "SettingsEnableColorSpacePassthrough": "색 공간 통과", + "SettingsEnableColorSpacePassthroughTooltip": "색 공간을 지정하지 않고 색상 정보를 전달하도록 Vulkan 후단에 지시합니다. 와이드 가멋 디스플레이를 사용하는 사용자의 경우 색 정확도가 저하되지만 더 생생한 색상을 얻을 수 있습니다.", + "VolumeShort": "음량", + "UserProfilesManageSaves": "저장 관리", + "DeleteUserSave": "이 게임에 대한 사용자 저장을 삭제하겠습니까?", + "IrreversibleActionNote": "이 작업은 되돌릴 수 없습니다.", + "SaveManagerHeading": "{0} ({1})의 저장 관리", + "SaveManagerTitle": "저장 관리자", + "Name": "이름", + "Size": "크기", + "Search": "검색", + "UserProfilesRecoverLostAccounts": "잃어버린 계정 복구", + "Recover": "복구", + "UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견", + "UserProfilesRecoverEmptyList": "복구할 프로파일이 없습니다", + "GraphicsAATooltip": "게임 렌더링에 안티 앨리어싱을 적용", + "GraphicsAALabel": "안티 앨리어싱:", + "GraphicsScalingFilterLabel": "스케일링 필터:", + "GraphicsScalingFilterTooltip": "프레임버퍼 스케일링 활성화", + "GraphicsScalingFilterLevelLabel": "수준", + "GraphicsScalingFilterLevelTooltip": "스케일링 필터 수준 설정", + "SmaaLow": "SMAA 낮음", + "SmaaMedium": "SMAA 중간", + "SmaaHigh": "SMAA 높음", + "SmaaUltra": "SMAA 울트라", + "UserEditorTitle": "사용자 수정", + "UserEditorTitleCreate": "사용자 생성", + "SettingsTabNetworkInterface": "네트워크 인터페이스:", + "NetworkInterfaceTooltip": "LAN 기능에 사용되는 네트워크 인터페이스", + "NetworkInterfaceDefault": "기본", + "PackagingShaders": "셰이더 패키징 중", + "AboutChangelogButton": "GitHub에서 변경 로그 보기", + "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/pl_PL.json b/src/Ryujinx.Ava/Assets/Locales/pl_PL.json new file mode 100644 index 000000000..7edf41e84 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/pl_PL.json @@ -0,0 +1,656 @@ +{ + "Language": "Angielski (USA)", + "MenuBarFileOpenApplet": "Otwórz Aplet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie Indywidualnym", + "SettingsTabInputDirectMouseAccess": "Bezpośredni Dostęp do Myszy", + "SettingsTabSystemMemoryManagerMode": "Tryb Menedżera Pamięci:", + "SettingsTabSystemMemoryManagerModeSoftware": "Oprogramowanie", + "SettingsTabSystemMemoryManagerModeHost": "Host (szybko)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Niesprawdzony (najszybciej, niebezpiecznie)", + "SettingsTabSystemUseHypervisor": "Użyj Hiperwizora", + "MenuBarFile": "_Plik", + "MenuBarFileOpenFromFile": "_Załaduj Aplikację z Pliku", + "MenuBarFileOpenUnpacked": "Załaduj _Rozpakowaną Grę", + "MenuBarFileOpenEmuFolder": "Otwórz Folder Ryujinx", + "MenuBarFileOpenLogsFolder": "Otwórz Folder Logów", + "MenuBarFileExit": "_Wyjdź", + "MenuBarOptions": "Opcje", + "MenuBarOptionsToggleFullscreen": "Przełącz Tryb Pełnoekranowy", + "MenuBarOptionsStartGamesInFullscreen": "Uruchamiaj Gry w Trybie Pełnoekranowym", + "MenuBarOptionsStopEmulation": "Zatrzymaj Emulację", + "MenuBarOptionsSettings": "_Ustawienia", + "MenuBarOptionsManageUserProfiles": "_Zarządzaj Profilami Użytkowników", + "MenuBarActions": "_Akcje", + "MenuBarOptionsSimulateWakeUpMessage": "Symuluj Wiadomość Budzenia", + "MenuBarActionsScanAmiibo": "Skanuj Amiibo", + "MenuBarTools": "_Narzędzia", + "MenuBarToolsInstallFirmware": "Zainstaluj Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj Firmware z XCI lub ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Zainstaluj Firmware z Katalogu", + "MenuBarToolsManageFileTypes": "Zarządzaj rodzajem plików", + "MenuBarToolsInstallFileTypes": "Instalacja typów plików", + "MenuBarToolsUninstallFileTypes": "Odinstaluj rodzaje plików", + "MenuBarHelp": "Pomoc", + "MenuBarHelpCheckForUpdates": "Sprawdź Aktualizacje", + "MenuBarHelpAbout": "O Aplikacji", + "MenuSearch": "Wyszukaj...", + "GameListHeaderFavorite": "Ulub", + "GameListHeaderIcon": "Ikona", + "GameListHeaderApplication": "Nazwa", + "GameListHeaderDeveloper": "Deweloper", + "GameListHeaderVersion": "Wersja", + "GameListHeaderTimePlayed": "Czas Gry", + "GameListHeaderLastPlayed": "Ostatnio Grane", + "GameListHeaderFileExtension": "Rozsz. Pliku", + "GameListHeaderFileSize": "Rozm. Pliku", + "GameListHeaderPath": "Ścieżka", + "GameListContextMenuOpenUserSaveDirectory": "Otwórz Katalog Zapisów Użytkownika", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Użytkownika Aplikacji", + "GameListContextMenuOpenDeviceSaveDirectory": "Otwórz Katalog Urządzeń Użytkownika", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Urządzenia Aplikacji", + "GameListContextMenuOpenBcatSaveDirectory": "Otwórz Katalog BCAT Użytkownika", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis BCAT Aplikacji", + "GameListContextMenuManageTitleUpdates": "Zarządzaj Aktualizacjami Tytułów", + "GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania Aktualizacjami Tytułu", + "GameListContextMenuManageDlc": "Zarządzaj DLC", + "GameListContextMenuManageDlcToolTip": "Otwiera okno zarządzania DLC", + "GameListContextMenuOpenModsDirectory": "Otwórz Katalog Modów", + "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający Mody Aplikacji", + "GameListContextMenuCacheManagement": "Zarządzanie Cache", + "GameListContextMenuCacheManagementPurgePptc": "Dodaj Rekompilację PPTC do Kolejki", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Zainicjuj Rekompilację PPTC przy następnym uruchomieniu gry", + "GameListContextMenuCacheManagementPurgeShaderCache": "Wyczyść Cache Shaderów", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Usuwa cache shaderów aplikacji", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Otwórz Katalog PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Otwiera katalog, który zawiera cache PPTC aplikacji", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Otwórz Katalog Cache Shaderów", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Otwiera katalog, który zawiera cache shaderów aplikacji", + "GameListContextMenuExtractData": "Wyodrębnij Dane", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Wyodrębnij sekcję ExeFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Wyodrębnij sekcję RomFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Wyodrębnij sekcję Logo z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "StatusBarGamesLoaded": "{0}/{1} Załadowane Gry", + "StatusBarSystemVersion": "Wersja Systemu: {0}", + "LinuxVmMaxMapCountDialogTitle": "Wykryto niski limit dla mapowania pamięci", + "LinuxVmMaxMapCountDialogTextPrimary": "Czy chciałbyś zwiększyć wartość vm.max_map_count do {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie, jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Tak, do następnego ponownego uruchomienia", + "LinuxVmMaxMapCountDialogButtonPersistent": "Tak, permanentnie ", + "LinuxVmMaxMapCountWarningTextPrimary": "Maksymalna ilość mapowania pamięci jest mniejsza niż zalecana.", + "LinuxVmMaxMapCountWarningTextSecondary": "Obecna wartość vm.max_map_count ({0}) jest mniejsza niż {1}. Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.\n\nMożesz chcieć ręcznie zwiększyć limit lub zainstalować pkexec, co pozwala Ryujinx na pomoc w tym zakresie.", + "Settings": "Ustawienia", + "SettingsTabGeneral": "Interfejs Użytkownika", + "SettingsTabGeneralGeneral": "Ogólne", + "SettingsTabGeneralEnableDiscordRichPresence": "Włącz Bogatą Obecność Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdź Aktualizacje przy Uruchomieniu", + "SettingsTabGeneralShowConfirmExitDialog": "Pokaż Okno Dialogowe \"Potwierdzenia Wyjścia\"", + "SettingsTabGeneralHideCursor": "Ukryj kursor:", + "SettingsTabGeneralHideCursorNever": "Nigdy", + "SettingsTabGeneralHideCursorOnIdle": "Ukryj Kursor Podczas Bezczynności", + "SettingsTabGeneralHideCursorAlways": "Zawsze", + "SettingsTabGeneralGameDirectories": "Katalogi Gier", + "SettingsTabGeneralAdd": "Dodaj", + "SettingsTabGeneralRemove": "Usuń", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Główne", + "SettingsTabSystemSystemRegion": "Region Systemu:", + "SettingsTabSystemSystemRegionJapan": "Japonia", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "Chiny", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Tajwan", + "SettingsTabSystemSystemLanguage": "Język Systemu:", + "SettingsTabSystemSystemLanguageJapanese": "Japoński", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerykański Angielski", + "SettingsTabSystemSystemLanguageFrench": "Francuski", + "SettingsTabSystemSystemLanguageGerman": "Niemiecki", + "SettingsTabSystemSystemLanguageItalian": "Włoski", + "SettingsTabSystemSystemLanguageSpanish": "Hiszpański", + "SettingsTabSystemSystemLanguageChinese": "Chiński", + "SettingsTabSystemSystemLanguageKorean": "Koreański", + "SettingsTabSystemSystemLanguageDutch": "Holenderski", + "SettingsTabSystemSystemLanguagePortuguese": "Portugalski", + "SettingsTabSystemSystemLanguageRussian": "Rosyjski", + "SettingsTabSystemSystemLanguageTaiwanese": "Tajwański", + "SettingsTabSystemSystemLanguageBritishEnglish": "Brytyjski Angielski", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadyjski Francuski", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Hiszpański Latynoamerykański", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chiński Uproszczony", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chiński Tradycyjny", + "SettingsTabSystemSystemTimeZone": "Strefa Czasowa Systemu:", + "SettingsTabSystemSystemTime": "Czas Systemu:", + "SettingsTabSystemEnableVsync": "Włącz synchronizację pionową", + "SettingsTabSystemEnablePptc": "PPTC (Profilowany Cache Trwałych Tłumaczeń)", + "SettingsTabSystemEnableFsIntegrityChecks": "Kontrole Integralności Systemu Plików", + "SettingsTabSystemAudioBackend": "Backend Dżwięku:", + "SettingsTabSystemAudioBackendDummy": "Atrapa", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacki", + "SettingsTabSystemHacksNote": " (mogą powodować niestabilność)", + "SettingsTabSystemExpandDramSize": "Użyj alternatywnego układu pamięci (Deweloperzy)", + "SettingsTabSystemIgnoreMissingServices": "Ignoruj Brakujące Usługi", + "SettingsTabGraphics": "Grafika", + "SettingsTabGraphicsAPI": "Graficzne API", + "SettingsTabGraphicsEnableShaderCache": "Włącz Cache Shaderów", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrowanie Anizotropowe:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automatyczny", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Skala Rozdzielczości:", + "SettingsTabGraphicsResolutionScaleCustom": "Niestandardowa (Niezalecane)", + "SettingsTabGraphicsResolutionScaleNative": "Natywna (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Współczynnik Proporcji:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Rozciągnij do Okna", + "SettingsTabGraphicsDeveloperOptions": "Opcje Programistyczne", + "SettingsTabGraphicsShaderDumpPath": "Ścieżka Zrzutu Shaderów Grafiki:", + "SettingsTabLogging": "Logowanie", + "SettingsTabLoggingLogging": "Logowanie", + "SettingsTabLoggingEnableLoggingToFile": "Włącz Logowanie do Pliku", + "SettingsTabLoggingEnableStubLogs": "Wlącz Skróty Logów", + "SettingsTabLoggingEnableInfoLogs": "Włącz Logi Informacyjne", + "SettingsTabLoggingEnableWarningLogs": "Włącz Logi Ostrzeżeń", + "SettingsTabLoggingEnableErrorLogs": "Włącz Logi Błędów", + "SettingsTabLoggingEnableTraceLogs": "Włącz Logi Śledzenia", + "SettingsTabLoggingEnableGuestLogs": "Włącz Logi Gości", + "SettingsTabLoggingEnableFsAccessLogs": "Włącz Logi Dostępu do Systemu Plików", + "SettingsTabLoggingFsGlobalAccessLogMode": "Tryb Globalnych Logów Systemu Plików:", + "SettingsTabLoggingDeveloperOptions": "Opcje programistyczne (OSTRZEŻENIE: Zmniejszą wydajność)", + "SettingsTabLoggingDeveloperOptionsNote": "UWAGA: Zredukuje wydajność", + "SettingsTabLoggingGraphicsBackendLogLevel": "Ilość Logów Backendu Graficznego:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Żadne", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Błędy", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Spowolnienia", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystkie", + "SettingsTabLoggingEnableDebugLogs": "Włącz Logi Debugowania", + "SettingsTabInput": "Sterowanie", + "SettingsTabInputEnableDockedMode": "Tryb Zadokowany", + "SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury", + "SettingsButtonSave": "Zapisz", + "SettingsButtonClose": "Zamknij", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Anuluj", + "SettingsButtonApply": "Zastosuj", + "ControllerSettingsPlayer": "Gracz", + "ControllerSettingsPlayer1": "Gracz 1", + "ControllerSettingsPlayer2": "Gracz 2", + "ControllerSettingsPlayer3": "Gracz 3", + "ControllerSettingsPlayer4": "Gracz 4", + "ControllerSettingsPlayer5": "Gracz 5", + "ControllerSettingsPlayer6": "Gracz 6", + "ControllerSettingsPlayer7": "Gracz 7", + "ControllerSettingsPlayer8": "Gracz 8", + "ControllerSettingsHandheld": "Przenośny", + "ControllerSettingsInputDevice": "Urządzenie Wejściowe", + "ControllerSettingsRefresh": "Odśwież", + "ControllerSettingsDeviceDisabled": "Wyłączone", + "ControllerSettingsControllerType": "Typ Kontrolera", + "ControllerSettingsControllerTypeHandheld": "Przenośny", + "ControllerSettingsControllerTypeProController": "Pro Kontroler", + "ControllerSettingsControllerTypeJoyConPair": "Para JoyCon-ów", + "ControllerSettingsControllerTypeJoyConLeft": "Lewy JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Prawy JoyCon", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Domyślny", + "ControllerSettingsLoad": "Wczytaj", + "ControllerSettingsAdd": "Dodaj", + "ControllerSettingsRemove": "Usuń", + "ControllerSettingsButtons": "Przyciski", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Pad Kierunkowy", + "ControllerSettingsDPadUp": "Góra", + "ControllerSettingsDPadDown": "Dół", + "ControllerSettingsDPadLeft": "Lewo", + "ControllerSettingsDPadRight": "Prawo", + "ControllerSettingsStickButton": "Przycisk", + "ControllerSettingsStickUp": "Góra ", + "ControllerSettingsStickDown": "Dół ", + "ControllerSettingsStickLeft": "Lewo", + "ControllerSettingsStickRight": "Prawo", + "ControllerSettingsStickStick": "Gałka", + "ControllerSettingsStickInvertXAxis": "Odwróć gałkę X", + "ControllerSettingsStickInvertYAxis": "Odwróć gałkę Y", + "ControllerSettingsStickDeadzone": "Martwa strefa:", + "ControllerSettingsLStick": "Lewa Gałka", + "ControllerSettingsRStick": "Prawa Gałka", + "ControllerSettingsTriggersLeft": "Lewe Triggery", + "ControllerSettingsTriggersRight": "Prawe Triggery", + "ControllerSettingsTriggersButtonsLeft": "Lewe Przyciski Triggerów", + "ControllerSettingsTriggersButtonsRight": "Prawe Przyciski Triggerów", + "ControllerSettingsTriggers": "Triggery", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Lewe Przyciski", + "ControllerSettingsExtraButtonsRight": "Prawe Przyciski", + "ControllerSettingsMisc": "Różne", + "ControllerSettingsTriggerThreshold": "Próg Triggerów:", + "ControllerSettingsMotion": "Ruch", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Użyj ruchu zgodnego z CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot Kontrolera:", + "ControllerSettingsMotionMirrorInput": "Odzwierciedlaj Sterowanie", + "ControllerSettingsMotionRightJoyConSlot": "Prawy Slot JoyCon:", + "ControllerSettingsMotionServerHost": "Host Serwera:", + "ControllerSettingsMotionGyroSensitivity": "Czułość Żyroskopu:", + "ControllerSettingsMotionGyroDeadzone": "Deadzone Żyroskopu:", + "ControllerSettingsSave": "Zapisz", + "ControllerSettingsClose": "Zamknij", + "UserProfilesSelectedUserProfile": "Wybrany Profil Użytkownika:", + "UserProfilesSaveProfileName": "Zapisz Nazwę Profilu", + "UserProfilesChangeProfileImage": "Zmień Obraz Profilu", + "UserProfilesAvailableUserProfiles": "Dostępne Profile Użytkowników:", + "UserProfilesAddNewProfile": "Utwórz Profil", + "UserProfilesDelete": "Usuwać", + "UserProfilesClose": "Zamknij", + "ProfileNameSelectionWatermark": "Wybierz pseudonim", + "ProfileImageSelectionTitle": "Wybór Obrazu Profilu", + "ProfileImageSelectionHeader": "Wybierz zdjęcie profilowe", + "ProfileImageSelectionNote": "Możesz zaimportować niestandardowy obraz profilu lub wybrać awatar z firmware'u systemowego", + "ProfileImageSelectionImportImage": "Importuj Plik Obrazu", + "ProfileImageSelectionSelectAvatar": "Wybierz Awatar z Firmware'u", + "InputDialogTitle": "Okno Dialogowe Wprowadzania", + "InputDialogOk": "OK", + "InputDialogCancel": "Anuluj", + "InputDialogAddNewProfileTitle": "Wybierz Nazwę Profilu", + "InputDialogAddNewProfileHeader": "Wprowadź Nazwę Profilu", + "InputDialogAddNewProfileSubtext": "(Maksymalna Długość: {0})", + "AvatarChoose": "Wybierz", + "AvatarSetBackgroundColor": "Ustaw Kolor Tła", + "AvatarClose": "Zamknij", + "ControllerSettingsLoadProfileToolTip": "Załaduj Profil", + "ControllerSettingsAddProfileToolTip": "Dodaj Profil", + "ControllerSettingsRemoveProfileToolTip": "Usuń Profil", + "ControllerSettingsSaveProfileToolTip": "Zapisz Profil", + "MenuBarFileToolsTakeScreenshot": "Zrób Zrzut Ekranu", + "MenuBarFileToolsHideUi": "Ukryj UI", + "GameListContextMenuRunApplication": "Uruchom aplikację ", + "GameListContextMenuToggleFavorite": "Przełącz Ulubione", + "GameListContextMenuToggleFavoriteToolTip": "Przełącz status Ulubionej Gry", + "SettingsTabGeneralTheme": "Motyw", + "SettingsTabGeneralThemeCustomTheme": "Ścieżka Niestandardowych Motywów", + "SettingsTabGeneralThemeBaseStyle": "Styl Podstawowy", + "SettingsTabGeneralThemeBaseStyleDark": "Ciemny", + "SettingsTabGeneralThemeBaseStyleLight": "Jasny", + "SettingsTabGeneralThemeEnableCustomTheme": "Włącz Niestandardowy Motyw", + "ButtonBrowse": "Przeglądaj", + "ControllerSettingsConfigureGeneral": "Konfiguruj", + "ControllerSettingsRumble": "Wibracje", + "ControllerSettingsRumbleStrongMultiplier": "Mocny Mnożnik Wibracji", + "ControllerSettingsRumbleWeakMultiplier": "Słaby Mnożnik Wibracji", + "DialogMessageSaveNotAvailableMessage": "Nie ma danych zapisu dla {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Czy chcesz utworzyć dane zapisu dla tej gry?", + "DialogConfirmationTitle": "Ryujinx - Potwierdzenie", + "DialogUpdaterTitle": "Ryujinx - Aktualizator", + "DialogErrorTitle": "Ryujinx - Błąd", + "DialogWarningTitle": "Ryujinx - Uwaga", + "DialogExitTitle": "Ryujinx - Wyjdź", + "DialogErrorMessage": "Ryujinx napotkał błąd", + "DialogExitMessage": "Czy na pewno chcesz zamknąć Ryujinx?", + "DialogExitSubMessage": "Wszystkie niezapisane dane zostaną utracone!", + "DialogMessageCreateSaveErrorMessage": "Wystąpił błąd podczas tworzenia określonych danych zapisu: {0}", + "DialogMessageFindSaveErrorMessage": "Wystąpił błąd podczas znajdowania określonych danych zapisu: {0}", + "FolderDialogExtractTitle": "Wybierz folder do rozpakowania", + "DialogNcaExtractionMessage": "Wyodrębnianie sekcji {0} z {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Ekstraktor Sekcji NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Niepowodzenie ekstrakcji. W wybranym pliku nie było głównego NCA.", + "DialogNcaExtractionCheckLogErrorMessage": "Niepowodzenie ekstrakcji. Przeczytaj plik dziennika, aby uzyskać więcej informacji.", + "DialogNcaExtractionSuccessMessage": "Ekstrakcja zakończona pomyślnie.", + "DialogUpdaterConvertFailedMessage": "Nie udało się przekonwertować obecnej wersji Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Anulowanie Aktualizacji!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Używasz już najnowszej wersji Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Wystąpił błąd podczas próby uzyskania informacji o wydaniu z GitHub Release. Może to być spowodowane nową wersją kompilowaną przez GitHub Actions. Spróbuj ponownie za kilka minut.", + "DialogUpdaterConvertFailedGithubMessage": "Nie udało się przekonwertować otrzymanej wersji Ryujinx z Github Release.", + "DialogUpdaterDownloadingMessage": "Pobieranie Aktualizacji...", + "DialogUpdaterExtractionMessage": "Wypakowywanie Aktualizacji...", + "DialogUpdaterRenamingMessage": "Zmiana Nazwy Aktualizacji...", + "DialogUpdaterAddingFilesMessage": "Dodawanie Nowej Aktualizacji...", + "DialogUpdaterCompleteMessage": "Aktualizacja Zakończona!", + "DialogUpdaterRestartMessage": "Czy chcesz teraz zrestartować Ryujinx?", + "DialogUpdaterArchNotSupportedMessage": "Nie używasz obsługiwanej architektury systemu!", + "DialogUpdaterArchNotSupportedSubMessage": "(Obsługiwane są tylko systemy x64!)", + "DialogUpdaterNoInternetMessage": "Nie masz połączenia z Internetem!", + "DialogUpdaterNoInternetSubMessage": "Sprawdź, czy masz działające połączenie internetowe!", + "DialogUpdaterDirtyBuildMessage": "Nie możesz zaktualizować Dirty wersji Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Pobierz Ryujinx ze strony https://ryujinx.org/, jeśli szukasz obsługiwanej wersji.", + "DialogRestartRequiredMessage": "Wymagane Ponowne Uruchomienie", + "DialogThemeRestartMessage": "Motyw został zapisany. Aby zastosować motyw, konieczne jest ponowne uruchomienie.", + "DialogThemeRestartSubMessage": "Czy chcesz uruchomić ponownie?", + "DialogFirmwareInstallEmbeddedMessage": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Nie znaleziono zainstalowanego firmware'u, ale Ryujinx był w stanie zainstalować firmware {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.", + "DialogFirmwareNoFirmwareInstalledMessage": "Brak Zainstalowanego Firmware'u", + "DialogFirmwareInstalledMessage": "Firmware {0} został zainstalowany", + "DialogInstallFileTypesSuccessMessage": "Pomyślnie zainstalowano typy plików!", + "DialogInstallFileTypesErrorMessage": "Nie udało się zainstalować typów plików.", + "DialogUninstallFileTypesSuccessMessage": "Pomyślnie odinstalowano typy plików!", + "DialogUninstallFileTypesErrorMessage": "Nie udało się odinstalować typów plików.", + "DialogOpenSettingsWindowLabel": "Otwórz Okno Ustawień", + "DialogControllerAppletTitle": "Aplet Kontrolera", + "DialogMessageDialogErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego Wiadomości: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Błąd wyświetlania Klawiatury Oprogramowania: {0}", + "DialogErrorAppletErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nAby uzyskać więcej informacji o tym, jak naprawić ten błąd, zapoznaj się z naszym Przewodnikiem instalacji.", + "DialogUserErrorDialogTitle": "Błąd Ryujinxa ({0})", + "DialogAmiiboApiTitle": "API Amiibo", + "DialogAmiiboApiFailFetchMessage": "Wystąpił błąd podczas pobierania informacji z API.", + "DialogAmiiboApiConnectErrorMessage": "Nie można połączyć się z serwerem API Amiibo. Usługa może nie działać lub może być konieczne sprawdzenie, czy połączenie internetowe jest online.", + "DialogProfileInvalidProfileErrorMessage": "Profil {0} jest niezgodny z bieżącym systemem konfiguracji sterowania.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Profil Domyślny nie może zostać nadpisany", + "DialogProfileDeleteProfileTitle": "Usuwanie Profilu", + "DialogProfileDeleteProfileMessage": "Ta czynność jest nieodwracalna, czy na pewno chcesz kontynuować?", + "DialogWarning": "Uwaga", + "DialogPPTCDeletionMessage": "Masz zamiar umieścić w kolejce rekompilację PPTC przy następnym uruchomieniu:\n\n{0}\n\nCzy na pewno chcesz kontynuować?", + "DialogPPTCDeletionErrorMessage": "Błąd czyszczenia cache PPTC w {0}: {1}", + "DialogShaderDeletionMessage": "Zamierzasz usunąć cache Shaderów dla :\n\n{0}\n\nNa pewno chcesz kontynuować?", + "DialogShaderDeletionErrorMessage": "Błąd czyszczenia cache Shaderów w {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx napotkał błąd", + "DialogInvalidTitleIdErrorMessage": "Błąd UI: Wybrana gra nie miała prawidłowego ID tytułu", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Nie znaleziono prawidłowego firmware'u systemowego w {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Zainstaluj Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Wersja systemu {0} zostanie zainstalowana.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nZastąpi to obecną wersję systemu {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nCzy chcesz kontynuować?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalowanie firmware'u...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Wersja systemu {0} została pomyślnie zainstalowana.", + "DialogUserProfileDeletionWarningMessage": "Nie będzie innych profili do otwarcia, jeśli wybrany profil zostanie usunięty", + "DialogUserProfileDeletionConfirmMessage": "Czy chcesz usunąć wybrany profil", + "DialogUserProfileUnsavedChangesTitle": "Uwaga - Niezapisane zmiany", + "DialogUserProfileUnsavedChangesMessage": "Wprowadziłeś zmiany dla tego profilu użytkownika, które nie zostały zapisane.", + "DialogUserProfileUnsavedChangesSubMessage": "Czy chcesz odrzucić zmiany?", + "DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Czy chcesz zapisać?", + "DialogLoadNcaErrorMessage": "{0}. Błędny Plik: {1}", + "DialogDlcNoDlcErrorMessage": "Określony plik nie zawiera DLC dla wybranego tytułu!", + "DialogPerformanceCheckLoggingEnabledMessage": "Masz włączone rejestrowanie śledzenia, które jest przeznaczone tylko dla programistów.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie rejestrowania śledzenia. Czy chcesz teraz wyłączyć rejestrowanie śledzenia?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Masz włączone zrzucanie shaderów, które jest przeznaczone tylko dla programistów.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie zrzucania shaderów. Czy chcesz teraz wyłączyć zrzucanie shaderów?", + "DialogLoadAppGameAlreadyLoadedMessage": "Gra została już załadowana", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Zatrzymaj emulację lub zamknij emulator przed uruchomieniem innej gry.", + "DialogUpdateAddUpdateErrorMessage": "Określony plik nie zawiera aktualizacji dla wybranego tytułu!", + "DialogSettingsBackendThreadingWarningTitle": "Ostrzeżenie — Wątki Backend", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx musi zostać ponownie uruchomiony po zmianie tej opcji, aby działał w pełni. W zależności od platformy może być konieczne ręczne wyłączenie sterownika wielowątkowości podczas korzystania z Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Funkcje", + "SettingsTabGraphicsBackendMultithreading": "Wielowątkowość Backendu Graficznego:", + "CommonAuto": "Auto", + "CommonOff": "Wyłączone", + "CommonOn": "Włączone", + "InputDialogYes": "Tak", + "InputDialogNo": "Nie", + "DialogProfileInvalidProfileNameErrorMessage": "Nazwa pliku zawiera nieprawidłowe znaki. Proszę spróbuj ponownie.", + "MenuBarOptionsPauseEmulation": "Pauza", + "MenuBarOptionsResumeEmulation": "Wznów", + "AboutUrlTooltipMessage": "Kliknij, aby otworzyć stronę Ryujinx w domyślnej przeglądarce.", + "AboutDisclaimerMessage": "Ryujinx nie jest w żaden sposób powiązany z Nintendo™,\nani z żadnym z jej partnerów.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) jest używane\nw naszej emulacji Amiibo.", + "AboutPatreonUrlTooltipMessage": "Kliknij, aby otworzyć stronę Patreon Ryujinx w domyślnej przeglądarce.", + "AboutGithubUrlTooltipMessage": "Kliknij, aby otworzyć stronę GitHub Ryujinx w domyślnej przeglądarce.", + "AboutDiscordUrlTooltipMessage": "Kliknij, aby otworzyć zaproszenie na serwer Discord Ryujinx w domyślnej przeglądarce.", + "AboutTwitterUrlTooltipMessage": "Kliknij, aby otworzyć stronę Twitter Ryujinx w domyślnej przeglądarce.", + "AboutRyujinxAboutTitle": "O Aplikacji:", + "AboutRyujinxAboutContent": "Ryujinx to emulator Nintendo Switch™.\nWspieraj nas na Patreonie.\nOtrzymuj najnowsze wiadomości na naszym Twitterze lub Discordzie.\nDeweloperzy zainteresowani współpracą mogą dowiedzieć się więcej na naszym GitHubie lub Discordzie.", + "AboutRyujinxMaintainersTitle": "Utrzymywany Przez:", + "AboutRyujinxMaintainersContentTooltipMessage": "Kliknij, aby otworzyć stronę Współtwórcy w domyślnej przeglądarce.", + "AboutRyujinxSupprtersTitle": "Wspierani na Patreonie Przez:", + "AmiiboSeriesLabel": "Seria Amiibo", + "AmiiboCharacterLabel": "Postać", + "AmiiboScanButtonLabel": "Zeskanuj", + "AmiiboOptionsShowAllLabel": "Pokaż Wszystkie Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Użyj losowego UUID tagu", + "DlcManagerTableHeadingEnabledLabel": "Włączone", + "DlcManagerTableHeadingTitleIdLabel": "ID Tytułu", + "DlcManagerTableHeadingContainerPathLabel": "Ścieżka Kontenera", + "DlcManagerTableHeadingFullPathLabel": "Pełna Ścieżka", + "DlcManagerRemoveAllButton": "Usuń Wszystkie", + "DlcManagerEnableAllButton": "Włącz Wszystkie", + "DlcManagerDisableAllButton": "Wyłącz Wszystkie", + "MenuBarOptionsChangeLanguage": "Zmień Język", + "MenuBarShowFileTypes": "Pokaż typy plików", + "CommonSort": "Sortuj", + "CommonShowNames": "Pokaż Nazwy", + "CommonFavorite": "Ulubione", + "OrderAscending": "Rosnąco", + "OrderDescending": "Malejąco", + "SettingsTabGraphicsFeatures": "Funkcje i Ulepszenia", + "ErrorWindowTitle": "Okno Błędu", + "ToggleDiscordTooltip": "Wybierz, czy chcesz wyświetlać Ryujinx w swojej \"aktualnie grane\" aktywności Discord", + "AddGameDirBoxTooltip": "Wprowadź katalog gier aby dodać go do listy", + "AddGameDirTooltip": "Dodaj katalog gier do listy", + "RemoveGameDirTooltip": "Usuń wybrany katalog gier", + "CustomThemeCheckTooltip": "Użyj niestandardowego motywu Avalonia dla GUI, aby zmienić wygląd menu emulatora", + "CustomThemePathTooltip": "Ścieżka do niestandardowego motywu GUI", + "CustomThemeBrowseTooltip": "Wyszukaj niestandardowy motyw GUI", + "DockModeToggleTooltip": "Tryb Zadokowany sprawia, że emulowany system zachowuje się jak zadokowany Nintendo Switch. Poprawia to jakość grafiki w większości gier. I odwrotnie, wyłączenie tej opcji sprawi, że emulowany system będzie zachowywał się jak przenośny Nintendo Switch, zmniejszając jakość grafiki.\n\nSkonfiguruj sterowanie gracza 1, jeśli planujesz używać trybu Zadokowanego; Skonfiguruj sterowanie przenośne, jeśli planujesz używać trybu przenośnego.\n\nPozostaw WŁĄCZONY, jeśli nie masz pewności.", + "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.", + "DirectMouseTooltip": "Obsługa bezpośredniego dostępu myszy (HID). Zapewnia grom dostęp do myszy jako urządzenia wskazującego.", + "RegionTooltip": "Zmień Region Systemu", + "LanguageTooltip": "Zmień Język Systemu", + "TimezoneTooltip": "Zmień Strefę Czasową Systemu", + "TimeTooltip": "Zmień Czas Systemu", + "VSyncToggleTooltip": "Pionowa synchronizacja emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ", + "PptcToggleTooltip": "Zapisuje przetłumaczone funkcje JIT, dzięki czemu nie muszą być tłumaczone za każdym razem, gdy gra się ładuje.\n\nZmniejsza zacinanie się i znacznie przyspiesza uruchamianie po pierwszym uruchomieniu gry.\n\nJeśli nie masz pewności, pozostaw WŁĄCZONE", + "FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", + "AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.", + "MemoryManagerTooltip": "Zmień sposób mapowania i uzyskiwania dostępu do pamięci gości. Znacznie wpływa na wydajność emulowanego procesora.\n\nUstaw na HOST UNCHECKED, jeśli nie masz pewności.", + "MemoryManagerSoftwareTooltip": "Użyj tabeli stron oprogramowania do translacji adresów. Najwyższa celność, ale najwolniejsza wydajność.", + "MemoryManagerHostTooltip": "Bezpośrednio mapuj pamięć w przestrzeni adresowej hosta. Znacznie szybsza kompilacja i wykonanie JIT.", + "MemoryManagerUnsafeTooltip": "Bezpośrednio mapuj pamięć, ale nie maskuj adresu w przestrzeni adresowej gościa przed uzyskaniem dostępu. Szybciej, ale kosztem bezpieczeństwa. Aplikacja gościa może uzyskać dostęp do pamięci z dowolnego miejsca w Ryujinx, więc w tym trybie uruchamiaj tylko programy, którym ufasz.", + "UseHypervisorTooltip": "Użyj Hiperwizora zamiast JIT. Znacznie poprawia wydajność, gdy jest dostępny, ale może być niestabilny w swoim obecnym stanie ", + "DRamTooltip": "Wykorzystuje alternatywny układ MemoryMode, aby naśladować model rozwojowy Switcha.\n\nJest to przydatne tylko w przypadku pakietów tekstur o wyższej rozdzielczości lub modów w rozdzielczości 4k. NIE poprawia wydajności.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "IgnoreMissingServicesTooltip": "Ignoruje niezaimplementowane usługi Horizon OS. Może to pomóc w ominięciu awarii podczas uruchamiania niektórych gier.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "GraphicsBackendThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", + "GalThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", + "ShaderCacheToggleTooltip": "Zapisuje pamięć podręczną shaderów na dysku, co zmniejsza zacinanie się w kolejnych uruchomieniach.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", + "ResolutionScaleTooltip": "Skala Rozdzielczości zastosowana do odpowiednich celów renderowania", + "ResolutionScaleEntryTooltip": "Skala rozdzielczości zmiennoprzecinkowej, np. 1,5. Skale niecałkowite częściej powodują problemy lub awarie.", + "AnisotropyTooltip": "Poziom filtrowania anizotropowego (ustaw na Auto, aby użyć wartości wymaganej przez grę)", + "AspectRatioTooltip": "Współczynnik proporcji zastosowany do okna renderowania.", + "ShaderDumpPathTooltip": "Ścieżka Zrzutu Shaderów Grafiki", + "FileLogTooltip": "Zapisuje logowanie konsoli w pliku dziennika na dysku. Nie wpływa na wydajność.", + "StubLogTooltip": "Wyświetla w konsoli skrótowe komunikaty dziennika. Nie wpływa na wydajność.", + "InfoLogTooltip": "Wyświetla komunikaty dziennika informacyjnego w konsoli. Nie wpływa na wydajność.", + "WarnLogTooltip": "Wyświetla komunikaty dziennika ostrzeżeń w konsoli. Nie wpływa na wydajność.", + "ErrorLogTooltip": "Wyświetla w konsoli komunikaty dziennika błędów. Nie wpływa na wydajność.", + "TraceLogTooltip": "Wyświetla komunikaty dziennika śledzenia w konsoli. Nie wpływa na wydajność.", + "GuestLogTooltip": "Wyświetla komunikaty dziennika gości w konsoli. Nie wpływa na wydajność.", + "FileAccessLogTooltip": "Wyświetla w konsoli komunikaty dziennika dostępu do plików.", + "FSAccessLogModeTooltip": "Włącza wyjście dziennika dostępu FS do konsoli. Możliwe tryby to 0-3", + "DeveloperOptionTooltip": "Używaj ostrożnie", + "OpenGlLogLevel": "Wymaga włączonych odpowiednich poziomów logów", + "DebugLogTooltip": "Wyświetla komunikaty dziennika debugowania w konsoli.\n\nUżywaj tego tylko na wyraźne polecenie członka załogi, ponieważ utrudni to odczytanie dzienników i pogorszy wydajność emulatora.", + "LoadApplicationFileTooltip": "Otwórz eksplorator plików, aby wybrać plik kompatybilny z Switch do wczytania", + "LoadApplicationFolderTooltip": "Otwórz eksplorator plików, aby wybrać zgodną z Switch, rozpakowaną aplikację do załadowania", + "OpenRyujinxFolderTooltip": "Otwórz folder systemu plików Ryujinx", + "OpenRyujinxLogsTooltip": "Otwiera folder, w którym zapisywane są logi", + "ExitTooltip": "Wyjdź z Ryujinx", + "OpenSettingsTooltip": "Otwórz okno ustawień", + "OpenProfileManagerTooltip": "Otwórz okno Menedżera Profili Użytkownika", + "StopEmulationTooltip": "Zatrzymaj emulację bieżącej gry i wróć do wyboru gier", + "CheckUpdatesTooltip": "Sprawdź aktualizacje Ryujinx", + "OpenAboutTooltip": "Otwórz Okno Informacje", + "GridSize": "Wielkość siatki", + "GridSizeTooltip": "Zmień rozmiar elementów siatki", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazylijski Portugalski", + "AboutRyujinxContributorsButtonHeader": "Zobacz Wszystkich Współtwórców", + "SettingsTabSystemAudioVolume": "Głośność: ", + "AudioVolumeTooltip": "Zmień Głośność Dźwięku", + "SettingsTabSystemEnableInternetAccess": "Dostęp do Internetu Gościa/Tryb LAN", + "EnableInternetAccessTooltip": "Pozwala emulowanej aplikacji na łączenie się z Internetem.\n\nGry w trybie LAN mogą łączyć się ze sobą, gdy ta opcja jest włączona, a systemy są połączone z tym samym punktem dostępu. Dotyczy to również prawdziwych konsol.\n\nNie pozwala na łączenie się z serwerami Nintendo. Może powodować awarie niektórych gier, które próbują połączyć się z Internetem.\n\nPozostaw WYŁĄCZONE, jeśli nie masz pewności.", + "GameListContextMenuManageCheatToolTip": "Zarządzaj Kodami", + "GameListContextMenuManageCheat": "Zarządzaj Kodami", + "ControllerSettingsStickRange": "Zasięg:", + "DialogStopEmulationTitle": "Ryujinx - Zatrzymaj Emulację", + "DialogStopEmulationMessage": "Czy na pewno chcesz zatrzymać emulację?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Dżwięk", + "SettingsTabNetwork": "Sieć", + "SettingsTabNetworkConnection": "Połączenie Sieciowe", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Pamięć CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Zaktualizuj Ryujinx przez FlatHub.", + "UpdaterDisabledWarningTitle": "Aktualizator Wyłączony!", + "GameListContextMenuOpenSdModsDirectory": "Otwórz Katalog Modów Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera modyfikacje aplikacji. Przydatne dla modów, które są pakowane dla prawdziwego sprzętu.", + "ControllerSettingsRotate90": "Obróć o 90° w Prawo", + "IconSize": "Rozmiar Ikon", + "IconSizeTooltip": "Zmień rozmiar ikon gry", + "MenuBarOptionsShowConsole": "Pokaż Konsolę", + "ShaderCachePurgeError": "Błąd podczas czyszczenia cache shaderów w {0}: {1}", + "UserErrorNoKeys": "Nie znaleziono kluczy", + "UserErrorNoFirmware": "Nie znaleziono firmware'u", + "UserErrorFirmwareParsingFailed": "Błąd parsowania firmware'u", + "UserErrorApplicationNotFound": "Aplikacja nie znaleziona", + "UserErrorUnknown": "Nieznany błąd", + "UserErrorUndefined": "Niezdefiniowany błąd", + "UserErrorNoKeysDescription": "Ryujinx nie mógł znaleźć twojego pliku 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx nie mógł znaleźć żadnego zainstalowanego firmware'u", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx nie był w stanie zparsować dostarczonego firmware'u. Jest to zwykle spowodowane nieaktualnymi kluczami.", + "UserErrorApplicationNotFoundDescription": "Ryujinx nie mógł znaleźć prawidłowej aplikacji na podanej ścieżce.", + "UserErrorUnknownDescription": "Wystąpił nieznany błąd!", + "UserErrorUndefinedDescription": "Wystąpił niezdefiniowany błąd! To nie powinno się zdarzyć, skontaktuj się z deweloperem!", + "OpenSetupGuideMessage": "Otwórz Podręcznik Konfiguracji", + "NoUpdate": "Brak Aktualizacji", + "TitleUpdateVersionLabel": "Wersja {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Potwierdzenie", + "FileDialogAllTypes": "Wszystkie typy", + "Never": "Nigdy", + "SwkbdMinCharacters": "Musi mieć co najmniej {0} znaków", + "SwkbdMinRangeCharacters": "Musi mieć długość od {0}-{1} znaków", + "SoftwareKeyboard": "Klawiatura Oprogramowania", + "SoftwareKeyboardModeNumbersOnly": "Mogą być tylko liczby ", + "SoftwareKeyboardModeAlphabet": "Nie może zawierać znaków CJK", + "SoftwareKeyboardModeASCII": "Musi zawierać tylko tekst ASCII", + "DialogControllerAppletMessagePlayerRange": "Aplikacja żąda {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", + "DialogControllerAppletMessage": "Aplikacja żąda dokładnie {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz teraz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", + "DialogControllerAppletDockModeSet": "Ustawiono tryb Zadokowane. Przenośny też jest nieprawidłowy.\n\n", + "UpdaterRenaming": "Zmienianie Nazw Starych Plików...", + "UpdaterRenameFailed": "Aktualizator nie mógł zmienić nazwy pliku: {0}", + "UpdaterAddingFiles": "Dodawanie Nowych Plików...", + "UpdaterExtracting": "Wypakowywanie Aktualizacji...", + "UpdaterDownloading": "Pobieranie Aktualizacji...", + "Game": "Gra", + "Docked": "Zadokowany", + "Handheld": "Przenośny", + "ConnectionError": "Błąd Połączenia.", + "AboutPageDeveloperListMore": "{0} i więcej...", + "ApiError": "Błąd API.", + "LoadingHeading": "Wczytywanie {0}", + "CompilingPPTC": "Kompilowanie PTC", + "CompilingShaders": "Kompilowanie Shaderów", + "AllKeyboards": "Wszystkie klawiatury", + "OpenFileDialogTitle": "Wybierz obsługiwany plik do otwarcia", + "OpenFolderDialogTitle": "Wybierz folder z rozpakowaną grą", + "AllSupportedFormats": "Wszystkie Obsługiwane Formaty", + "RyujinxUpdater": "Aktualizator Ryujinx", + "SettingsTabHotkeys": "Skróty Klawiszowe Klawiatury", + "SettingsTabHotkeysHotkeys": "Skróty Klawiszowe Klawiatury", + "SettingsTabHotkeysToggleVsyncHotkey": "Przełącz VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Zrzut Ekranu:", + "SettingsTabHotkeysShowUiHotkey": "Pokaż UI:", + "SettingsTabHotkeysPauseHotkey": "Pauza:", + "SettingsTabHotkeysToggleMuteHotkey": "Wycisz:", + "ControllerMotionTitle": "Ustawienia Sterowania Ruchowego", + "ControllerRumbleTitle": "Ustawienia Wibracji", + "SettingsSelectThemeFileDialogTitle": "Wybierz Plik Motywu", + "SettingsXamlThemeFile": "Plik Motywu Xaml", + "AvatarWindowTitle": "Zarządzaj Kontami — Avatar", + "Amiibo": "Amiibo", + "Unknown": "Nieznane", + "Usage": "Użycie", + "Writable": "Zapisywalne", + "SelectDlcDialogTitle": "Wybierz pliki DLC", + "SelectUpdateDialogTitle": "Wybierz pliki aktualizacji", + "UserProfileWindowTitle": "Menedżer Profili Użytkowników", + "CheatWindowTitle": "Menedżer Kodów", + "DlcWindowTitle": "Menedżer Zawartości do Pobrania", + "UpdateWindowTitle": "Menedżer Aktualizacji Tytułu", + "CheatWindowHeading": "Kody Dostępne dla {0} [{1}]", + "BuildId": "Identyfikator wersji:", + "DlcWindowHeading": "{0} Zawartości do Pobrania dostępna dla {1} ({2})", + "UserProfilesEditProfile": "Edytuj Zaznaczone", + "Cancel": "Anuluj", + "Save": "Zapisz", + "Discard": "Odrzuć", + "UserProfilesSetProfileImage": "Ustaw Obraz Profilu", + "UserProfileEmptyNameError": "Nazwa jest wymagana", + "UserProfileNoImageError": "Należy ustawić obraz profilowy", + "GameUpdateWindowHeading": "{0} Aktualizacje dostępne dla {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Zwiększ Rozdzielczość:", + "SettingsTabHotkeysResScaleDownHotkey": "Zmniejsz Rozdzielczość:", + "UserProfilesName": "Nazwa:", + "UserProfilesUserId": "ID Użytkownika:", + "SettingsTabGraphicsBackend": "Backend Graficzny", + "SettingsTabGraphicsBackendTooltip": "Używalne Backendy Graficzne", + "SettingsEnableTextureRecompression": "Włącz Rekompresję Tekstur", + "SettingsEnableTextureRecompressionTooltip": "Kompresuje niektóre tekstury w celu zmniejszenia zużycia pamięci VRAM.\n\nZalecane do użytku z GPU, które mają mniej niż 4 GiB pamięci VRAM.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "SettingsTabGraphicsPreferredGpu": "Preferowane GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Wybierz kartę graficzną, która będzie używana z backendem graficznym Vulkan.\n\nNie wpływa na GPU używane przez OpenGL.\n\nW razie wątpliwości ustaw flagę GPU jako \"dGPU\". Jeśli żadnej nie ma, pozostaw nietknięte.", + "SettingsAppRequiredRestartMessage": "Wymagane Zrestartowanie Ryujinx", + "SettingsGpuBackendRestartMessage": "Zmieniono ustawienia Backendu Graficznego lub GPU. Będzie to wymagało ponownego uruchomienia", + "SettingsGpuBackendRestartSubMessage": "Czy chcesz zrestartować teraz?", + "RyujinxUpdaterMessage": "Czy chcesz zaktualizować Ryujinx do najnowszej wersji?", + "SettingsTabHotkeysVolumeUpHotkey": "Zwiększ Głośność:", + "SettingsTabHotkeysVolumeDownHotkey": "Zmniejsz Głośność:", + "SettingsEnableMacroHLE": "Włącz Macro HLE", + "SettingsEnableMacroHLETooltip": "Wysokopoziomowa emulacja kodu GPU Macro.\n\nPoprawia wydajność, ale może powodować błędy graficzne w niektórych grach.\n\nW razie wątpliwości pozostaw WŁĄCZONE.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Głoś", + "UserProfilesManageSaves": "Zarządzaj Zapisami", + "DeleteUserSave": "Czy chcesz usunąć zapis użytkownika dla tej gry?", + "IrreversibleActionNote": "Ta czynność nie jest odwracalna.", + "SaveManagerHeading": "Zarządzaj Zapisami dla {0}", + "SaveManagerTitle": "Menedżer Zapisów", + "Name": "Nazwa", + "Size": "Rozmiar", + "Search": "Wyszukaj", + "UserProfilesRecoverLostAccounts": "Odzyskaj Utracone Konta", + "Recover": "Odzyskaj", + "UserProfilesRecoverHeading": "Znaleziono zapisy dla następujących kont", + "UserProfilesRecoverEmptyList": "Brak profili do odzyskania", + "GraphicsAATooltip": "Stosuje antyaliasing do renderowania gry", + "GraphicsAALabel": "Antyaliasing:", + "GraphicsScalingFilterLabel": "Filtr skalowania:", + "GraphicsScalingFilterTooltip": "Włącza skalowanie bufora ramki", + "GraphicsScalingFilterLevelLabel": "Poziom", + "GraphicsScalingFilterLevelTooltip": "Ustaw poziom filtra skalowania", + "SmaaLow": "SMAA Niskie", + "SmaaMedium": "SMAA Średnie", + "SmaaHigh": "SMAA Wysokie", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Edytuj użytkownika", + "UserEditorTitleCreate": "Utwórz użytkownika", + "SettingsTabNetworkInterface": "Interfejs sieci:", + "NetworkInterfaceTooltip": "Interfejs sieciowy używany do funkcji LAN", + "NetworkInterfaceDefault": "Domyślny", + "PackagingShaders": "Pakuje Shadery ", + "AboutChangelogButton": "Zobacz listę zmian na GitHubie", + "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/pt_BR.json b/src/Ryujinx.Ava/Assets/Locales/pt_BR.json new file mode 100644 index 000000000..8909a84fe --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/pt_BR.json @@ -0,0 +1,656 @@ +{ + "Language": "Português (BR)", + "MenuBarFileOpenApplet": "Abrir Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abrir editor Mii em modo avulso", + "SettingsTabInputDirectMouseAccess": "Acesso direto ao mouse", + "SettingsTabSystemMemoryManagerMode": "Modo de gerenciamento de memória:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Hóspede (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hóspede sem verificação (mais rápido, inseguro)", + "SettingsTabSystemUseHypervisor": "Usar Hipervisor", + "MenuBarFile": "_Arquivo", + "MenuBarFileOpenFromFile": "_Abrir ROM do jogo...", + "MenuBarFileOpenUnpacked": "Abrir jogo _extraído...", + "MenuBarFileOpenEmuFolder": "Abrir diretório do e_mulador...", + "MenuBarFileOpenLogsFolder": "Abrir diretório de _logs...", + "MenuBarFileExit": "_Sair", + "MenuBarOptions": "_Opções", + "MenuBarOptionsToggleFullscreen": "_Mudar para tela cheia", + "MenuBarOptionsStartGamesInFullscreen": "Iniciar jogos em tela cheia", + "MenuBarOptionsStopEmulation": "_Encerrar emulação", + "MenuBarOptionsSettings": "_Configurações", + "MenuBarOptionsManageUserProfiles": "_Gerenciar perfis de usuário", + "MenuBarActions": "_Ações", + "MenuBarOptionsSimulateWakeUpMessage": "_Simular mensagem de acordar console", + "MenuBarActionsScanAmiibo": "Escanear um Amiibo", + "MenuBarTools": "_Ferramentas", + "MenuBarToolsInstallFirmware": "_Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware a partir de um arquivo ZIP/XCI", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware a partir de um diretório", + "MenuBarToolsManageFileTypes": "Gerenciar tipos de arquivo", + "MenuBarToolsInstallFileTypes": "Instalar tipos de arquivo", + "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de arquivos", + "MenuBarHelp": "A_juda", + "MenuBarHelpCheckForUpdates": "_Verificar se há atualizações", + "MenuBarHelpAbout": "_Sobre", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Favorito", + "GameListHeaderIcon": "Ícone", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Desenvolvedor", + "GameListHeaderVersion": "Versão", + "GameListHeaderTimePlayed": "Tempo de jogo", + "GameListHeaderLastPlayed": "Último jogo", + "GameListHeaderFileExtension": "Extensão", + "GameListHeaderFileSize": "Tamanho", + "GameListHeaderPath": "Caminho", + "GameListContextMenuOpenUserSaveDirectory": "Abrir diretório de saves do usuário", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre o diretório que contém jogos salvos para o usuário atual", + "GameListContextMenuOpenDeviceSaveDirectory": "Abrir diretório de saves de dispositivo do usuário", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre o diretório que contém saves do dispositivo para o usuário atual", + "GameListContextMenuOpenBcatSaveDirectory": "Abrir diretório de saves BCAT do usuário", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abre o diretório que contém saves BCAT para o usuário atual", + "GameListContextMenuManageTitleUpdates": "Gerenciar atualizações do jogo", + "GameListContextMenuManageTitleUpdatesToolTip": "Abre a janela de gerenciamento de atualizações", + "GameListContextMenuManageDlc": "Gerenciar DLCs", + "GameListContextMenuManageDlcToolTip": "Abre a janela de gerenciamento de DLCs", + "GameListContextMenuOpenModsDirectory": "Abrir diretório de mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre o diretório que contém modificações (mods) do jogo", + "GameListContextMenuCacheManagement": "Gerenciamento de cache", + "GameListContextMenuCacheManagementPurgePptc": "Limpar cache PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Deleta o cache PPTC armazenado em disco do jogo", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpar cache de Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deleta o cache de Shader armazenado em disco do jogo", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir diretório do cache PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abre o diretório contendo os arquivos do cache PPTC", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir diretório do cache de Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abre o diretório contendo os arquivos do cache de Shader", + "GameListContextMenuExtractData": "Extrair dados", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrai a seção ExeFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrai a seção RomFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrai a seção Logo do jogo (incluindo atualizações)", + "StatusBarGamesLoaded": "{0}/{1} jogos carregados", + "StatusBarSystemVersion": "Versão do firmware: {0}", + "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", + "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", + "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", + "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "Settings": "Configurações", + "SettingsTabGeneral": "Geral", + "SettingsTabGeneralGeneral": "Geral", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar Rich Presence do Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Verificar se há atualizações ao iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Exibir diálogo de confirmação ao sair", + "SettingsTabGeneralHideCursor": "Esconder o cursor do mouse:", + "SettingsTabGeneralHideCursorNever": "Nunca", + "SettingsTabGeneralHideCursorOnIdle": "Esconder o cursor quando ocioso", + "SettingsTabGeneralHideCursorAlways": "Sempre", + "SettingsTabGeneralGameDirectories": "Diretórios de jogo", + "SettingsTabGeneralAdd": "Adicionar", + "SettingsTabGeneralRemove": "Remover", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principal", + "SettingsTabSystemSystemRegion": "Região do sistema:", + "SettingsTabSystemSystemRegionJapan": "Japão", + "SettingsTabSystemSystemRegionUSA": "EUA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Austrália", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Coreia", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Idioma do sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonês", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglês americano", + "SettingsTabSystemSystemLanguageFrench": "Francês", + "SettingsTabSystemSystemLanguageGerman": "Alemão", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Espanhol", + "SettingsTabSystemSystemLanguageChinese": "Chinês", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Holandês", + "SettingsTabSystemSystemLanguagePortuguese": "Português", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanês", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglês britânico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francês canadense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espanhol latino", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinês simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinês tradicional", + "SettingsTabSystemSystemTimeZone": "Fuso horário do sistema:", + "SettingsTabSystemSystemTime": "Hora do sistema:", + "SettingsTabSystemEnableVsync": "Habilitar sincronia vertical", + "SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Habilitar verificação de integridade do sistema de arquivos", + "SettingsTabSystemAudioBackend": "Biblioteca de saída de áudio:", + "SettingsTabSystemAudioBackendDummy": "Nenhuma", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Pode causar instabilidade)", + "SettingsTabSystemExpandDramSize": "Expandir memória para 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignorar serviços não implementados", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsAPI": "API gráfica", + "SettingsTabGraphicsEnableShaderCache": "Habilitar cache de shader", + "SettingsTabGraphicsAnisotropicFiltering": "Filtragem anisotrópica:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Escala de resolução:", + "SettingsTabGraphicsResolutionScaleCustom": "Customizada (não recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Proporção:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Esticar até caber", + "SettingsTabGraphicsDeveloperOptions": "Opções do desenvolvedor", + "SettingsTabGraphicsShaderDumpPath": "Diretório para despejo de shaders:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salvar logs em arquivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar logs de stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar logs de informação", + "SettingsTabLoggingEnableWarningLogs": "Habilitar logs de alerta", + "SettingsTabLoggingEnableErrorLogs": "Habilitar logs de erro", + "SettingsTabLoggingEnableTraceLogs": "Habilitar logs de rastreamento", + "SettingsTabLoggingEnableGuestLogs": "Habilitar logs do programa convidado", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar logs de acesso ao sistema de arquivos", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo global de logs do sistema de arquivos:", + "SettingsTabLoggingDeveloperOptions": "Opções do desenvolvedor (AVISO: Vai reduzir a performance)", + "SettingsTabLoggingDeveloperOptionsNote": "AVISO: Reduzirá o desempenho", + "SettingsTabLoggingGraphicsBackendLogLevel": "Nível de log do backend gráfico:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nenhum", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Erro", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Lentidão", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todos", + "SettingsTabLoggingEnableDebugLogs": "Habilitar logs de depuração", + "SettingsTabInput": "Controle", + "SettingsTabInputEnableDockedMode": "Habilitar modo TV", + "SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado", + "SettingsButtonSave": "Salvar", + "SettingsButtonClose": "Fechar", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancelar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jogador", + "ControllerSettingsPlayer1": "Jogador 1", + "ControllerSettingsPlayer2": "Jogador 2", + "ControllerSettingsPlayer3": "Jogador 3", + "ControllerSettingsPlayer4": "Jogador 4", + "ControllerSettingsPlayer5": "Jogador 5", + "ControllerSettingsPlayer6": "Jogador 6", + "ControllerSettingsPlayer7": "Jogador 7", + "ControllerSettingsPlayer8": "Jogador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Atualizar", + "ControllerSettingsDeviceDisabled": "Desabilitado", + "ControllerSettingsControllerType": "Tipo do controle", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Par de JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon esquerdo", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon direito", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Padrão", + "ControllerSettingsLoad": "Carregar", + "ControllerSettingsAdd": "Adicionar", + "ControllerSettingsRemove": "Remover", + "ControllerSettingsButtons": "Botões", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Direcional", + "ControllerSettingsDPadUp": "Cima", + "ControllerSettingsDPadDown": "Baixo", + "ControllerSettingsDPadLeft": "Esquerda", + "ControllerSettingsDPadRight": "Direita", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Up", + "ControllerSettingsStickDown": "Down", + "ControllerSettingsStickLeft": "Left", + "ControllerSettingsStickRight": "Right", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Invert Stick X", + "ControllerSettingsStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Analógico esquerdo", + "ControllerSettingsRStick": "Analógico direito", + "ControllerSettingsTriggersLeft": "Gatilhos esquerda", + "ControllerSettingsTriggersRight": "Gatilhos direita", + "ControllerSettingsTriggersButtonsLeft": "Botões de gatilho esquerda", + "ControllerSettingsTriggersButtonsRight": "Botões de gatilho direita", + "ControllerSettingsTriggers": "Gatilhos", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botões esquerda", + "ControllerSettingsExtraButtonsRight": "Botões direita", + "ControllerSettingsMisc": "Miscelâneas", + "ControllerSettingsTriggerThreshold": "Sensibilidade do gatilho:", + "ControllerSettingsMotion": "Sensor de movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar sensor compatível com CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot do controle:", + "ControllerSettingsMotionMirrorInput": "Espelhar movimento", + "ControllerSettingsMotionRightJoyConSlot": "Slot do JoyCon direito:", + "ControllerSettingsMotionServerHost": "Endereço do servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidade do giroscópio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta do giroscópio:", + "ControllerSettingsSave": "Salvar", + "ControllerSettingsClose": "Fechar", + "UserProfilesSelectedUserProfile": "Perfil de usuário selecionado:", + "UserProfilesSaveProfileName": "Salvar nome de perfil", + "UserProfilesChangeProfileImage": "Mudar imagem de perfil", + "UserProfilesAvailableUserProfiles": "Perfis de usuário disponíveis:", + "UserProfilesAddNewProfile": "Adicionar novo perfil", + "UserProfilesDelete": "Apagar", + "UserProfilesClose": "Fechar", + "ProfileNameSelectionWatermark": "Escolha um apelido", + "ProfileImageSelectionTitle": "Seleção da imagem de perfil", + "ProfileImageSelectionHeader": "Escolha uma imagem de perfil", + "ProfileImageSelectionNote": "Você pode importar uma imagem customizada, ou selecionar um avatar do firmware", + "ProfileImageSelectionImportImage": "Importar arquivo de imagem", + "ProfileImageSelectionSelectAvatar": "Selecionar avatar do firmware", + "InputDialogTitle": "Diálogo de texto", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Escolha o nome de perfil", + "InputDialogAddNewProfileHeader": "Escreva o nome do perfil", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escolher", + "AvatarSetBackgroundColor": "Definir cor de fundo", + "AvatarClose": "Fechar", + "ControllerSettingsLoadProfileToolTip": "Carregar perfil", + "ControllerSettingsAddProfileToolTip": "Adicionar perfil", + "ControllerSettingsRemoveProfileToolTip": "Remover perfil", + "ControllerSettingsSaveProfileToolTip": "Salvar perfil", + "MenuBarFileToolsTakeScreenshot": "Salvar captura de tela", + "MenuBarFileToolsHideUi": "Esconder Interface", + "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuToggleFavorite": "Alternar favorito", + "GameListContextMenuToggleFavoriteToolTip": "Marca ou desmarca jogo como favorito", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Diretório de tema customizado", + "SettingsTabGeneralThemeBaseStyle": "Estilo base", + "SettingsTabGeneralThemeBaseStyleDark": "Escuro", + "SettingsTabGeneralThemeBaseStyleLight": "Claro", + "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema customizado", + "ButtonBrowse": "Procurar", + "ControllerSettingsConfigureGeneral": "Configurar", + "ControllerSettingsRumble": "Vibração", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibração forte", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibração fraca", + "DialogMessageSaveNotAvailableMessage": "Não há jogos salvos para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Gostaria de criar o diretório de salvamento para esse jogo?", + "DialogConfirmationTitle": "Ryujinx - Confirmação", + "DialogUpdaterTitle": "Ryujinx - Atualizador", + "DialogErrorTitle": "Ryujinx - Erro", + "DialogWarningTitle": "Ryujinx - Alerta", + "DialogExitTitle": "Ryujinx - Sair", + "DialogErrorMessage": "Ryujinx encontrou um erro", + "DialogExitMessage": "Tem certeza que deseja fechar o Ryujinx?", + "DialogExitSubMessage": "Todos os dados que não foram salvos serão perdidos!", + "DialogMessageCreateSaveErrorMessage": "Ocorreu um erro ao criar o diretório de salvamento: {0}", + "DialogMessageFindSaveErrorMessage": "Ocorreu um erro ao tentar encontrar o diretório de salvamento: {0}", + "FolderDialogExtractTitle": "Escolha o diretório onde os arquivos serão extraídos", + "DialogNcaExtractionMessage": "Extraindo seção {0} de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extrator de seções NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Falha na extração. O NCA principal não foi encontrado no arquivo selecionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Falha na extração. Leia o arquivo de log para mais informações.", + "DialogNcaExtractionSuccessMessage": "Extração concluída com êxito.", + "DialogUpdaterConvertFailedMessage": "Falha ao converter a versão atual do Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Cancelando atualização!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Você já está usando a versão mais recente do Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Ocorreu um erro ao tentar obter as informações de atualização do GitHub Release. Isso pode ser causado se uma nova versão estiver sendo compilado pelas Ações do GitHub. Tente novamente em alguns minutos.", + "DialogUpdaterConvertFailedGithubMessage": "Falha ao converter a versão do Ryujinx recebida do AppVeyor.", + "DialogUpdaterDownloadingMessage": "Baixando atualização...", + "DialogUpdaterExtractionMessage": "Extraindo atualização...", + "DialogUpdaterRenamingMessage": "Renomeando atualização...", + "DialogUpdaterAddingFilesMessage": "Adicionando nova atualização...", + "DialogUpdaterCompleteMessage": "Atualização concluída!", + "DialogUpdaterRestartMessage": "Deseja reiniciar o Ryujinx agora?", + "DialogUpdaterArchNotSupportedMessage": "Você não está rodando uma arquitetura de sistema suportada!", + "DialogUpdaterArchNotSupportedSubMessage": "(Apenas sistemas x64 são suportados!)", + "DialogUpdaterNoInternetMessage": "Você não está conectado à Internet!", + "DialogUpdaterNoInternetSubMessage": "Por favor, certifique-se de que você tem uma conexão funcional à Internet!", + "DialogUpdaterDirtyBuildMessage": "Você não pode atualizar uma compilação Dirty do Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, baixe o Ryujinx em https://ryujinx.org/ se está procurando por uma versão suportada.", + "DialogRestartRequiredMessage": "Reinicialização necessária", + "DialogThemeRestartMessage": "O tema foi salvo. Uma reinicialização é necessária para aplicar o tema.", + "DialogThemeRestartSubMessage": "Deseja reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "Gostaria de instalar o firmware incluso neste jogo? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Nenhum firmware instalado foi encontrado, mas Ryujinx conseguiu instalar o firmware {0} do jogo fornecido.\nO emulador será reiniciado.", + "DialogFirmwareNoFirmwareInstalledMessage": "Firmware não foi instalado", + "DialogFirmwareInstalledMessage": "Firmware {0} foi instalado", + "DialogInstallFileTypesSuccessMessage": "Tipos de arquivo instalados com sucesso!", + "DialogInstallFileTypesErrorMessage": "Falha ao instalar tipos de arquivo.", + "DialogUninstallFileTypesSuccessMessage": "Tipos de arquivo desinstalados com sucesso!", + "DialogUninstallFileTypesErrorMessage": "Falha ao desinstalar tipos de arquivo.", + "DialogOpenSettingsWindowLabel": "Abrir janela de configurações", + "DialogControllerAppletTitle": "Applet de controle", + "DialogMessageDialogErrorExceptionMessage": "Erro ao exibir diálogo de mensagem: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Erro ao exibir teclado virtual: {0}", + "DialogErrorAppletErrorExceptionMessage": "Erro ao exibir applet ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara mais informações sobre como corrigir esse erro, siga nosso Guia de Configuração.", + "DialogUserErrorDialogTitle": "Erro do Ryujinx ({0})", + "DialogAmiiboApiTitle": "API Amiibo", + "DialogAmiiboApiFailFetchMessage": "Um erro ocorreu ao tentar obter informações da API.", + "DialogAmiiboApiConnectErrorMessage": "Não foi possível conectar ao servidor da API Amiibo. O serviço pode estar fora do ar ou você precisa verificar sua conexão com a Internet.", + "DialogProfileInvalidProfileErrorMessage": "Perfil {0} é incompatível com o sistema de configuração de controle atual.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "O perfil Padrão não pode ser substituído", + "DialogProfileDeleteProfileTitle": "Apagando perfil", + "DialogProfileDeleteProfileMessage": "Essa ação é irreversível, tem certeza que deseja continuar?", + "DialogWarning": "Alerta", + "DialogPPTCDeletionMessage": "Você está prestes a apagar o cache PPTC para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogPPTCDeletionErrorMessage": "Erro apagando cache PPTC em {0}: {1}", + "DialogShaderDeletionMessage": "Você está prestes a apagar o cache de Shader para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogShaderDeletionErrorMessage": "Erro apagando o cache de Shader em {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx encontrou um erro", + "DialogInvalidTitleIdErrorMessage": "Erro de interface: O jogo selecionado não tem um ID de título válido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Um firmware de sistema válido não foi encontrado em {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "A versão do sistema {0} será instalada.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nIsso substituirá a versão do sistema atual {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDeseja continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versão do sistema {0} instalada com sucesso.", + "DialogUserProfileDeletionWarningMessage": "Não haveria nenhum perfil selecionado se o perfil atual fosse deletado", + "DialogUserProfileDeletionConfirmMessage": "Deseja deletar o perfil selecionado", + "DialogUserProfileUnsavedChangesTitle": "Alerta - Alterações não salvas", + "DialogUserProfileUnsavedChangesMessage": "Você fez alterações para este perfil de usuário que não foram salvas.", + "DialogUserProfileUnsavedChangesSubMessage": "Deseja descartar as alterações?", + "DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?", + "DialogLoadNcaErrorMessage": "{0}. Arquivo com erro: {1}", + "DialogDlcNoDlcErrorMessage": "O arquivo especificado não contém DLCs para o título selecionado!", + "DialogPerformanceCheckLoggingEnabledMessage": "Os logs de depuração estão ativos, esse recurso é feito para ser usado apenas por desenvolvedores.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar os logs de depuração. Gostaria de desabilitar os logs de depuração agora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "O despejo de shaders está ativo, esse recurso é feito para ser usado apenas por desenvolvedores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar o despejo de shaders. Gostaria de desabilitar o despejo de shaders agora?", + "DialogLoadAppGameAlreadyLoadedMessage": "Um jogo já foi carregado", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, pare a emulação ou feche o emulador antes de abrir outro jogo.", + "DialogUpdateAddUpdateErrorMessage": "O arquivo especificado não contém atualizações para o título selecionado!", + "DialogSettingsBackendThreadingWarningTitle": "Alerta - Threading da API gráfica", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx precisa ser reiniciado após mudar essa opção para que ela tenha efeito. Dependendo da sua plataforma, pode ser preciso desabilitar o multithreading do driver de vídeo quando usar o Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Recursos", + "SettingsTabGraphicsBackendMultithreading": "Multithreading da API gráfica:", + "CommonAuto": "Automático", + "CommonOff": "Desligado", + "CommonOn": "Ligado", + "InputDialogYes": "Sim", + "InputDialogNo": "Não", + "DialogProfileInvalidProfileNameErrorMessage": "O nome do arquivo contém caracteres inválidos. Por favor, tente novamente.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Resumir", + "AboutUrlTooltipMessage": "Clique para abrir o site do Ryujinx no seu navegador padrão.", + "AboutDisclaimerMessage": "Ryujinx não é afiliado com a Nintendo™,\nou qualquer um de seus parceiros, de nenhum modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) é usado\nem nossa emulação de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clique para abrir a página do Patreon do Ryujinx no seu navegador padrão.", + "AboutGithubUrlTooltipMessage": "Clique para abrir a página do GitHub do Ryujinx no seu navegador padrão.", + "AboutDiscordUrlTooltipMessage": "Clique para abrir um convite ao servidor do Discord do Ryujinx no seu navegador padrão.", + "AboutTwitterUrlTooltipMessage": "Clique para abrir a página do Twitter do Ryujinx no seu navegador padrão.", + "AboutRyujinxAboutTitle": "Sobre:", + "AboutRyujinxAboutContent": "Ryujinx é um emulador de Nintendo Switch™.\nPor favor, nos dê apoio no Patreon.\nFique por dentro de todas as novidades no Twitter ou Discord.\nDesenvolvedores com interesse em contribuir podem conseguir mais informações no GitHub ou Discord.", + "AboutRyujinxMaintainersTitle": "Mantido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clique para abrir a página de contribuidores no seu navegador padrão.", + "AboutRyujinxSupprtersTitle": "Apoiado no Patreon por:", + "AmiiboSeriesLabel": "Franquia Amiibo", + "AmiiboCharacterLabel": "Personagem", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Exibir todos os Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Usar Uuid de tag aleatório", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID do título", + "DlcManagerTableHeadingContainerPathLabel": "Caminho do container", + "DlcManagerTableHeadingFullPathLabel": "Caminho completo", + "DlcManagerRemoveAllButton": "Remover todos", + "DlcManagerEnableAllButton": "Habilitar todos", + "DlcManagerDisableAllButton": "Desabilitar todos", + "MenuBarOptionsChangeLanguage": "Mudar idioma", + "MenuBarShowFileTypes": "Mostrar tipos de arquivo", + "CommonSort": "Ordenar", + "CommonShowNames": "Exibir nomes", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Recursos & Melhorias", + "ErrorWindowTitle": "Janela de erro", + "ToggleDiscordTooltip": "Habilita ou desabilita Discord Rich Presence", + "AddGameDirBoxTooltip": "Escreva um diretório de jogo para adicionar à lista", + "AddGameDirTooltip": "Adicionar um diretório de jogo à lista", + "RemoveGameDirTooltip": "Remover diretório de jogo selecionado", + "CustomThemeCheckTooltip": "Habilita ou desabilita temas customizados na interface gráfica", + "CustomThemePathTooltip": "Diretório do tema customizado", + "CustomThemeBrowseTooltip": "Navegar até um tema customizado", + "DockModeToggleTooltip": "Habilita ou desabilita modo TV", + "DirectKeyboardTooltip": "Habilita ou desabilita \"acesso direto ao teclado (HID)\" (Permite que o jogo acesse o seu teclado como dispositivo de entrada de texto)", + "DirectMouseTooltip": "Habilita ou desabilita \"acesso direto ao mouse (HID)\" (Permite que o jogo acesse o seu mouse como dispositivo apontador)", + "RegionTooltip": "Mudar a região do sistema", + "LanguageTooltip": "Mudar o idioma do sistema", + "TimezoneTooltip": "Mudar o fuso-horário do sistema", + "TimeTooltip": "Mudar a hora do sistema", + "VSyncToggleTooltip": "Habilita ou desabilita a sincronia vertical", + "PptcToggleTooltip": "Habilita ou desabilita PPTC", + "FsIntegrityToggleTooltip": "Habilita ou desabilita verificação de integridade dos arquivos do jogo", + "AudioBackendTooltip": "Mudar biblioteca de áudio", + "MemoryManagerTooltip": "Muda como a memória do sistema convidado é acessada. Tem um grande impacto na performance da CPU emulada.", + "MemoryManagerSoftwareTooltip": "Usar uma tabela de página via software para tradução de endereços. Maior precisão, porém performance mais baixa.", + "MemoryManagerHostTooltip": "Mapeia memória no espaço de endereço hóspede diretamente. Compilação e execução do JIT muito mais rápida.", + "MemoryManagerUnsafeTooltip": "Mapeia memória diretamente, mas sem limitar o acesso ao espaço de endereçamento do sistema convidado. Mais rápido, porém menos seguro. O aplicativo convidado pode acessar memória de qualquer parte do Ryujinx, então apenas rode programas em que você confia nesse modo.", + "UseHypervisorTooltip": "Usa o Hypervisor em vez de JIT (recompilador dinâmico). Melhora significativamente o desempenho quando disponível, mas pode ser instável no seu estado atual.", + "DRamTooltip": "Expande a memória do sistema emulado de 4GiB para 6GiB", + "IgnoreMissingServicesTooltip": "Habilita ou desabilita a opção de ignorar serviços não implementados", + "GraphicsBackendThreadingTooltip": "Habilita multithreading do backend gráfico", + "GalThreadingTooltip": "Executa comandos do backend gráfico em uma segunda thread. Permite multithreading em tempo de execução da compilação de shader, diminui os travamentos, e melhora performance em drivers sem suporte embutido a multithreading. Pequena variação na performance máxima em drivers com suporte a multithreading. Ryujinx pode precisar ser reiniciado para desabilitar adequadamente o multithreading embutido do driver, ou você pode precisar fazer isso manualmente para ter a melhor performance.", + "ShaderCacheToggleTooltip": "Habilita ou desabilita o cache de shader", + "ResolutionScaleTooltip": "Escala de resolução aplicada às texturas de renderização", + "ResolutionScaleEntryTooltip": "Escala de resolução de ponto flutuante, como 1.5. Valores não inteiros tem probabilidade maior de causar problemas ou quebras.", + "AnisotropyTooltip": "Nível de filtragem anisotrópica (deixe em Auto para usar o valor solicitado pelo jogo)", + "AspectRatioTooltip": "Taxa de proporção aplicada à janela do renderizador.", + "ShaderDumpPathTooltip": "Diretòrio de despejo de shaders", + "FileLogTooltip": "Habilita ou desabilita log para um arquivo no disco", + "StubLogTooltip": "Habilita ou desabilita exibição de mensagens de stub", + "InfoLogTooltip": "Habilita ou desabilita exibição de mensagens informativas", + "WarnLogTooltip": "Habilita ou desabilita exibição de mensagens de alerta", + "ErrorLogTooltip": "Habilita ou desabilita exibição de mensagens de erro", + "TraceLogTooltip": "Habilita ou desabilita exibição de mensagens de rastreamento", + "GuestLogTooltip": "Habilita ou desabilita exibição de mensagens do programa convidado", + "FileAccessLogTooltip": "Habilita ou desabilita exibição de mensagens do acesso de arquivos", + "FSAccessLogModeTooltip": "Habilita exibição de mensagens de acesso ao sistema de arquivos no console. Modos permitidos são 0-3", + "DeveloperOptionTooltip": "Use com cuidado", + "OpenGlLogLevel": "Requer que os níveis de log apropriados estejaam habilitados", + "DebugLogTooltip": "Habilita exibição de mensagens de depuração", + "LoadApplicationFileTooltip": "Abre o navegador de arquivos para seleção de um arquivo do Switch compatível a ser carregado", + "LoadApplicationFolderTooltip": "Abre o navegador de pastas para seleção de pasta extraída do Switch compatível a ser carregada", + "OpenRyujinxFolderTooltip": "Abre o diretório do sistema de arquivos do Ryujinx", + "OpenRyujinxLogsTooltip": "Abre o diretório onde os logs são salvos", + "ExitTooltip": "Sair do Ryujinx", + "OpenSettingsTooltip": "Abrir janela de configurações", + "OpenProfileManagerTooltip": "Abrir janela de gerenciamento de perfis", + "StopEmulationTooltip": "Parar emulação do jogo atual e voltar a seleção de jogos", + "CheckUpdatesTooltip": "Verificar por atualizações para o Ryujinx", + "OpenAboutTooltip": "Abrir janela sobre", + "GridSize": "Tamanho da grade", + "GridSizeTooltip": "Mudar tamanho dos items da grade", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Português do Brasil", + "AboutRyujinxContributorsButtonHeader": "Ver todos os contribuidores", + "SettingsTabSystemAudioVolume": "Volume:", + "AudioVolumeTooltip": "Mudar volume do áudio", + "SettingsTabSystemEnableInternetAccess": "Habilitar acesso à internet do programa convidado", + "EnableInternetAccessTooltip": "Habilita acesso à internet do programa convidado. Se habilitado, o aplicativo vai se comportar como se o sistema Switch emulado estivesse conectado a Internet. Note que em alguns casos, aplicativos podem acessar a Internet mesmo com essa opção desabilitada", + "GameListContextMenuManageCheatToolTip": "Gerenciar Cheats", + "GameListContextMenuManageCheat": "Gerenciar Cheats", + "ControllerSettingsStickRange": "Intervalo:", + "DialogStopEmulationTitle": "Ryujinx - Parar emulação", + "DialogStopEmulationMessage": "Tem certeza que deseja parar a emulação?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Áudio", + "SettingsTabNetwork": "Rede", + "SettingsTabNetworkConnection": "Conexão de rede", + "SettingsTabCpuCache": "Cache da CPU", + "SettingsTabCpuMemory": "Memória da CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, atualize o Ryujinx pelo FlatHub.", + "UpdaterDisabledWarningTitle": "Atualizador desabilitado!", + "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre o diretório alternativo Atmosphere no cartão SD que contém mods para o aplicativo", + "ControllerSettingsRotate90": "Rodar 90° sentido horário", + "IconSize": "Tamanho do ícone", + "IconSizeTooltip": "Muda o tamanho do ícone do jogo", + "MenuBarOptionsShowConsole": "Exibir console", + "ShaderCachePurgeError": "Erro ao deletar o shader em {0}: {1}", + "UserErrorNoKeys": "Chaves não encontradas", + "UserErrorNoFirmware": "Firmware não encontrado", + "UserErrorFirmwareParsingFailed": "Erro na leitura do Firmware", + "UserErrorApplicationNotFound": "Aplicativo não encontrado", + "UserErrorUnknown": "Erro desconhecido", + "UserErrorUndefined": "Erro indefinido", + "UserErrorNoKeysDescription": "Ryujinx não conseguiu encontrar o seu arquivo 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx não conseguiu encontrar nenhum Firmware instalado", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx não conseguiu ler o Firmware fornecido. Geralmente isso é causado por chaves desatualizadas.", + "UserErrorApplicationNotFoundDescription": "Ryujinx não conseguiu encontrar um aplicativo válido no caminho fornecido.", + "UserErrorUnknownDescription": "Um erro desconhecido foi encontrado!", + "UserErrorUndefinedDescription": "Um erro indefinido occoreu! Isso não deveria acontecer, por favor contate um desenvolvedor!", + "OpenSetupGuideMessage": "Abrir o guia de configuração", + "NoUpdate": "Sem atualizações", + "TitleUpdateVersionLabel": "Versão {0} - {1}", + "RyujinxInfo": "Ryujinx - Informação", + "RyujinxConfirm": "Ryujinx - Confirmação", + "FileDialogAllTypes": "Todos os tipos", + "Never": "Nunca", + "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres", + "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres", + "SoftwareKeyboard": "Teclado por Software", + "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "DialogControllerAppletMessagePlayerRange": "O aplicativo requer {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", + "DialogControllerAppletMessage": "O aplicativo requer exatamente {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", + "DialogControllerAppletDockModeSet": "Modo TV ativado. Portátil também não é válido.\n\n", + "UpdaterRenaming": "Renomeando arquivos antigos...", + "UpdaterRenameFailed": "O atualizador não conseguiu renomear o arquivo: {0}", + "UpdaterAddingFiles": "Adicionando novos arquivos...", + "UpdaterExtracting": "Extraíndo atualização...", + "UpdaterDownloading": "Baixando atualização...", + "Game": "Jogo", + "Docked": "TV", + "Handheld": "Portátil", + "ConnectionError": "Erro de conexão.", + "AboutPageDeveloperListMore": "{0} e mais...", + "ApiError": "Erro de API.", + "LoadingHeading": "Carregando {0}", + "CompilingPPTC": "Compilando PTC", + "CompilingShaders": "Compilando Shaders", + "AllKeyboards": "Todos os teclados", + "OpenFileDialogTitle": "Selecione um arquivo suportado para abrir", + "OpenFolderDialogTitle": "Selecione um diretório com um jogo extraído", + "AllSupportedFormats": "Todos os formatos suportados", + "RyujinxUpdater": "Atualizador do Ryujinx", + "SettingsTabHotkeys": "Atalhos do teclado", + "SettingsTabHotkeysHotkeys": "Atalhos do teclado", + "SettingsTabHotkeysToggleVsyncHotkey": "Mudar VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Captura de tela:", + "SettingsTabHotkeysShowUiHotkey": "Exibir UI:", + "SettingsTabHotkeysPauseHotkey": "Pausar:", + "SettingsTabHotkeysToggleMuteHotkey": "Mudo:", + "ControllerMotionTitle": "Configurações do controle de movimento", + "ControllerRumbleTitle": "Configurações de vibração", + "SettingsSelectThemeFileDialogTitle": "Selecionar arquivo do tema", + "SettingsXamlThemeFile": "Arquivo de tema Xaml", + "AvatarWindowTitle": "Gerenciar contas - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Desconhecido", + "Usage": "Uso", + "Writable": "Gravável", + "SelectDlcDialogTitle": "Selecionar arquivos de DLC", + "SelectUpdateDialogTitle": "Selecionar arquivos de atualização", + "UserProfileWindowTitle": "Gerenciador de perfis de usuário", + "CheatWindowTitle": "Gerenciador de Cheats", + "DlcWindowTitle": "Gerenciador de DLC", + "UpdateWindowTitle": "Gerenciador de atualizações", + "CheatWindowHeading": "Cheats disponíveis para {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} DLCs disponíveis para {1} ({2})", + "UserProfilesEditProfile": "Editar selecionado", + "Cancel": "Cancelar", + "Save": "Salvar", + "Discard": "Descartar", + "UserProfilesSetProfileImage": "Definir imagem de perfil", + "UserProfileEmptyNameError": "É necessário um nome", + "UserProfileNoImageError": "A imagem de perfil deve ser definida", + "GameUpdateWindowHeading": "{0} atualizações disponíveis para {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentar a resolução:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuir a resolução:", + "UserProfilesName": "Nome:", + "UserProfilesUserId": "ID de usuário:", + "SettingsTabGraphicsBackend": "Backend gráfico", + "SettingsTabGraphicsBackendTooltip": "Backend gráfico a ser usado", + "SettingsEnableTextureRecompression": "Habilitar recompressão de texturas", + "SettingsEnableTextureRecompressionTooltip": "Comprime certas texturas para reduzir o uso da VRAM.\n\nRecomendado para uso com GPUs com menos de 4GB VRAM.\n\nEm caso de dúvida, deixe DESLIGADO.", + "SettingsTabGraphicsPreferredGpu": "GPU preferencial", + "SettingsTabGraphicsPreferredGpuTooltip": "Selecione a placa de vídeo que será usada com o backend gráfico Vulkan.\n\nNão afeta a GPU que OpenGL usará.\n\nSelecione \"dGPU\" em caso de dúvida. Se não houver nenhuma, não mexa.", + "SettingsAppRequiredRestartMessage": "Reinicialização do Ryujinx necessária", + "SettingsGpuBackendRestartMessage": "Configurações do backend gráfico ou da GPU foram alteradas. Uma reinicialização é necessária para que as mudanças tenham efeito.", + "SettingsGpuBackendRestartSubMessage": "Deseja reiniciar agora?", + "RyujinxUpdaterMessage": "Você quer atualizar o Ryujinx para a última versão?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentar volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuir volume:", + "SettingsEnableMacroHLE": "Habilitar emulação de alto nível para Macros", + "SettingsEnableMacroHLETooltip": "Habilita emulação de alto nível de códigos Macro da GPU.\n\nMelhora a performance, mas pode causar problemas gráficos em alguns jogos.\n\nEm caso de dúvida, deixe ATIVADO.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gerenciar jogos salvos", + "DeleteUserSave": "Deseja apagar o jogo salvo do usuário para este jogo?", + "IrreversibleActionNote": "Esta ação não é reversível.", + "SaveManagerHeading": "Gerenciar jogos salvos para {0}", + "SaveManagerTitle": "Gerenciador de jogos salvos", + "Name": "Nome", + "Size": "Tamanho", + "Search": "Buscar", + "UserProfilesRecoverLostAccounts": "Recuperar contas perdidas", + "Recover": "Recuperar", + "UserProfilesRecoverHeading": "Jogos salvos foram encontrados para as seguintes contas", + "UserProfilesRecoverEmptyList": "Nenhum perfil para recuperar", + "GraphicsAATooltip": "Aplica anti-serrilhamento à renderização do jogo", + "GraphicsAALabel": "Anti-serrilhado:", + "GraphicsScalingFilterLabel": "Filtro de escala:", + "GraphicsScalingFilterTooltip": "Habilita escala do Framebuffer", + "GraphicsScalingFilterLevelLabel": "Nível", + "GraphicsScalingFilterLevelTooltip": "Define o nível do filtro de escala", + "SmaaLow": "SMAA Baixo", + "SmaaMedium": "SMAA Médio", + "SmaaHigh": "SMAA Alto", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Editar usuário", + "UserEditorTitleCreate": "Criar usuário", + "SettingsTabNetworkInterface": "Interface de rede:", + "NetworkInterfaceTooltip": "A interface de rede usada para recursos LAN (rede local)", + "NetworkInterfaceDefault": "Padrão", + "PackagingShaders": "Packaging Shaders", + "AboutChangelogButton": "View Changelog on GitHub", + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/ru_RU.json b/src/Ryujinx.Ava/Assets/Locales/ru_RU.json new file mode 100644 index 000000000..a2128e52e --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/ru_RU.json @@ -0,0 +1,656 @@ +{ + "Language": "Русский", + "MenuBarFileOpenApplet": "Открыть апплет", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открыть апплет Mii Editor в автономном режиме", + "SettingsTabInputDirectMouseAccess": "Прямой доступ к мыши", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера памяти:", + "SettingsTabSystemMemoryManagerModeSoftware": "Программное обеспечение", + "SettingsTabSystemMemoryManagerModeHost": "Хост (быстро)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Хост не установлен (самый быстрый, небезопасный)", + "SettingsTabSystemUseHypervisor": "Использовать Гипервизор", + "MenuBarFile": "_Файл", + "MenuBarFileOpenFromFile": "_Загрузить приложение из файла", + "MenuBarFileOpenUnpacked": "Загрузить _Распакованную игру", + "MenuBarFileOpenEmuFolder": "Открыть папку Ryujinx", + "MenuBarFileOpenLogsFolder": "Открыть папку журналов", + "MenuBarFileExit": "_Выход", + "MenuBarOptions": "Опции", + "MenuBarOptionsToggleFullscreen": "Включить полноэкранный режим", + "MenuBarOptionsStartGamesInFullscreen": "Запустить игру в полноэкранном режиме", + "MenuBarOptionsStopEmulation": "Остановить эмуляцию", + "MenuBarOptionsSettings": "_Параметры", + "MenuBarOptionsManageUserProfiles": "_Управление профилями пользователей", + "MenuBarActions": "_Действия", + "MenuBarOptionsSimulateWakeUpMessage": "Имитировать сообщение пробуждения", + "MenuBarActionsScanAmiibo": "Сканировать Amiibo", + "MenuBarTools": "_Инструменты", + "MenuBarToolsInstallFirmware": "Установить прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Установить прошивку из XCI или ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Установить прошивку из папки", + "MenuBarToolsManageFileTypes": "Управление типами файлов", + "MenuBarToolsInstallFileTypes": "Установить типы файлов", + "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", + "MenuBarHelp": "Помощь", + "MenuBarHelpCheckForUpdates": "Проверка обновления", + "MenuBarHelpAbout": "О программе", + "MenuSearch": "Поиск...", + "GameListHeaderFavorite": "Избранные", + "GameListHeaderIcon": "Значок", + "GameListHeaderApplication": "Название", + "GameListHeaderDeveloper": "Разработчик", + "GameListHeaderVersion": "Версия", + "GameListHeaderTimePlayed": "Время воспроизведения", + "GameListHeaderLastPlayed": "Последняя игра", + "GameListHeaderFileExtension": "Расширение файла", + "GameListHeaderFileSize": "Размер файла", + "GameListHeaderPath": "Путь", + "GameListContextMenuOpenUserSaveDirectory": "Открыть папку сохранений пользователя", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку, содержащую пользовательские сохранения", + "GameListContextMenuOpenDeviceSaveDirectory": "Открыть папку сохраненных устройств", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные устройства", + "GameListContextMenuOpenBcatSaveDirectory": "Открыть папку сохраненных BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT.", + "GameListContextMenuManageTitleUpdates": "Управление обновлениями названий", + "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков", + "GameListContextMenuManageDlc": "Управление DLC", + "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", + "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", + "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды приложений и игр", + "GameListContextMenuCacheManagement": "Управление кэшем", + "GameListContextMenuCacheManagementPurgePptc": "Перестройка очереди PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время запуска следующей игры.", + "GameListContextMenuCacheManagementPurgeShaderCache": "Очистить кэш шейдеров", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Удаляет кеш шейдеров приложения", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Открыть папку PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Открывает папку, содержащую PPTC кэш приложений и игр", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть папку кэша шейдеров", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Открывает папку, содержащую кэш шейдеров приложений и игр", + "GameListContextMenuExtractData": "Извлечь данные", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Извлечение раздела ExeFS из текущих настроек приложения (включая обновления)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Извлечение раздела RomFS из текущих настроек приложения (включая обновления)", + "GameListContextMenuExtractDataLogo": "Логотип", + "GameListContextMenuExtractDataLogoToolTip": "Извлечение раздела с логотипом из текущих настроек приложения (включая обновления)", + "StatusBarGamesLoaded": "{0}/{1} Игр загружено", + "StatusBarSystemVersion": "Версия системы: {0}", + "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", + "LinuxVmMaxMapCountDialogTextPrimary": "Вы хотите увеличить значение vm.max_map_count до {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Некоторые игры могут создавать большую разметку памяти, чем разрешено на данный момент по умолчанию. Ryujinx вылетит при превышении этого лимита.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Да, до следующего перезапуска", + "LinuxVmMaxMapCountDialogButtonPersistent": "Да, постоянно", + "LinuxVmMaxMapCountWarningTextPrimary": "Максимальная разметка памяти меньше, чем рекомендуется.", + "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вы захотите вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", + "Settings": "Параметры", + "SettingsTabGeneral": "Пользовательский интерфейс", + "SettingsTabGeneralGeneral": "Общее", + "SettingsTabGeneralEnableDiscordRichPresence": "Включить Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске", + "SettingsTabGeneralShowConfirmExitDialog": "Показать диалоговое окно \"Подтвердить выход\"", + "SettingsTabGeneralHideCursor": "Скрыть курсор", + "SettingsTabGeneralHideCursorNever": "Никогда", + "SettingsTabGeneralHideCursorOnIdle": "Скрыть курсор в режиме ожидания", + "SettingsTabGeneralHideCursorAlways": "Всегда", + "SettingsTabGeneralGameDirectories": "Папки с играми", + "SettingsTabGeneralAdd": "Добавить", + "SettingsTabGeneralRemove": "Удалить", + "SettingsTabSystem": "Система", + "SettingsTabSystemCore": "Основные настройки", + "SettingsTabSystemSystemRegion": "Регион Системы:", + "SettingsTabSystemSystemRegionJapan": "Япония", + "SettingsTabSystemSystemRegionUSA": "США", + "SettingsTabSystemSystemRegionEurope": "Европа", + "SettingsTabSystemSystemRegionAustralia": "Австралия", + "SettingsTabSystemSystemRegionChina": "Китай", + "SettingsTabSystemSystemRegionKorea": "Корея", + "SettingsTabSystemSystemRegionTaiwan": "Тайвань", + "SettingsTabSystemSystemLanguage": "Язык системы:", + "SettingsTabSystemSystemLanguageJapanese": "Японский", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Английский (США)", + "SettingsTabSystemSystemLanguageFrench": "Французский", + "SettingsTabSystemSystemLanguageGerman": "Германский", + "SettingsTabSystemSystemLanguageItalian": "Итальянский", + "SettingsTabSystemSystemLanguageSpanish": "Испанский", + "SettingsTabSystemSystemLanguageChinese": "Китайский", + "SettingsTabSystemSystemLanguageKorean": "Корейский", + "SettingsTabSystemSystemLanguageDutch": "Нидерландский", + "SettingsTabSystemSystemLanguagePortuguese": "Португальский", + "SettingsTabSystemSystemLanguageRussian": "Русский", + "SettingsTabSystemSystemLanguageTaiwanese": "Тайванский", + "SettingsTabSystemSystemLanguageBritishEnglish": "Английский (Британия)", + "SettingsTabSystemSystemLanguageCanadianFrench": "Французский (Канада)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латиноамериканский)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский упрощённый", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский традиционный", + "SettingsTabSystemSystemTimeZone": "Часовой пояс системы:", + "SettingsTabSystemSystemTime": "Время системы:", + "SettingsTabSystemEnableVsync": "Включить вертикальную синхронизацию", + "SettingsTabSystemEnablePptc": "Включить PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности FS", + "SettingsTabSystemAudioBackend": "Аудио бэкэнд:", + "SettingsTabSystemAudioBackendDummy": "Муляж", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Хаки", + "SettingsTabSystemHacksNote": " (Эти многие настройки вызывают нестабильность)", + "SettingsTabSystemExpandDramSize": "Увеличение размера DRAM до 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Игнорировать отсутствующие службы", + "SettingsTabGraphics": "Графика", + "SettingsTabGraphicsAPI": "Графические API", + "SettingsTabGraphicsEnableShaderCache": "Включить кэш шейдеров", + "SettingsTabGraphicsAnisotropicFiltering": "Анизотропная фильтрация:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Автоматически", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Масштаб:", + "SettingsTabGraphicsResolutionScaleCustom": "Пользовательский (не рекомендуется)", + "SettingsTabGraphicsResolutionScaleNative": "Родной (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Соотношение сторон:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Растянуть до размеров окна", + "SettingsTabGraphicsDeveloperOptions": "Параметры разработчика", + "SettingsTabGraphicsShaderDumpPath": "Путь дампа графического шейдера:", + "SettingsTabLogging": "Журналирование", + "SettingsTabLoggingLogging": "Журналирование", + "SettingsTabLoggingEnableLoggingToFile": "Включить запись в файл", + "SettingsTabLoggingEnableStubLogs": "Включить журнал-заглушку", + "SettingsTabLoggingEnableInfoLogs": "Включить информационный журнал", + "SettingsTabLoggingEnableWarningLogs": "Включить журнал предупреждений", + "SettingsTabLoggingEnableErrorLogs": "Включить журнал ошибок", + "SettingsTabLoggingEnableTraceLogs": "Включить журнал трассировки", + "SettingsTabLoggingEnableGuestLogs": "Включить гостевые журналы", + "SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа файловой системы", + "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа файловой системы:", + "SettingsTabLoggingDeveloperOptions": "Параметры разработчика", + "SettingsTabLoggingDeveloperOptionsNote": "ВНИМАНИЕ: Снижает производительность", + "SettingsTabLoggingGraphicsBackendLogLevel": "Уровень журнала бэкенда графики:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ничего", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Ошибка", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Замедления", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Всё", + "SettingsTabLoggingEnableDebugLogs": "Включить журнал отладки", + "SettingsTabInput": "Управление", + "SettingsTabInputEnableDockedMode": "Включить режим закрепления", + "SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры", + "SettingsButtonSave": "Сохранить", + "SettingsButtonClose": "Закрыть", + "SettingsButtonOk": "Ок", + "SettingsButtonCancel": "Отмена", + "SettingsButtonApply": "Применить", + "ControllerSettingsPlayer": "Игрок", + "ControllerSettingsPlayer1": "Игрок 1", + "ControllerSettingsPlayer2": "Игрок 2", + "ControllerSettingsPlayer3": "Игрок 3", + "ControllerSettingsPlayer4": "Игрок 4", + "ControllerSettingsPlayer5": "Игрок 5", + "ControllerSettingsPlayer6": "Игрок 6", + "ControllerSettingsPlayer7": "Игрок 7", + "ControllerSettingsPlayer8": "Игрок 8", + "ControllerSettingsHandheld": "Портативный", + "ControllerSettingsInputDevice": "Устройство ввода", + "ControllerSettingsRefresh": "Обновить", + "ControllerSettingsDeviceDisabled": "Отключить", + "ControllerSettingsControllerType": "Тип контроллера", + "ControllerSettingsControllerTypeHandheld": "Портативный", + "ControllerSettingsControllerTypeProController": "Pro Контроллер", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Пара", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Левый", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Правый", + "ControllerSettingsProfile": "Профиль", + "ControllerSettingsProfileDefault": "По умолчанию", + "ControllerSettingsLoad": "Загрузить", + "ControllerSettingsAdd": "Добавить", + "ControllerSettingsRemove": "Удалить", + "ControllerSettingsButtons": "Кнопки", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Направляющая панель", + "ControllerSettingsDPadUp": "Вверх", + "ControllerSettingsDPadDown": "Вниз", + "ControllerSettingsDPadLeft": "Влево", + "ControllerSettingsDPadRight": "Вправо", + "ControllerSettingsStickButton": "Кнопка", + "ControllerSettingsStickUp": "Вверх", + "ControllerSettingsStickDown": "Вниз", + "ControllerSettingsStickLeft": "Влево", + "ControllerSettingsStickRight": "Вправо", + "ControllerSettingsStickStick": "Стик", + "ControllerSettingsStickInvertXAxis": "Инвертировать X ось", + "ControllerSettingsStickInvertYAxis": "Инвертировать Y ось", + "ControllerSettingsStickDeadzone": "Мёртвая зона:", + "ControllerSettingsLStick": "Левый стик", + "ControllerSettingsRStick": "Правый стик", + "ControllerSettingsTriggersLeft": "Триггеры слева", + "ControllerSettingsTriggersRight": "Триггеры справа", + "ControllerSettingsTriggersButtonsLeft": "Триггерные кнопки слева", + "ControllerSettingsTriggersButtonsRight": "Триггерные кнопки справа", + "ControllerSettingsTriggers": "Триггеры", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Левые кнопки", + "ControllerSettingsExtraButtonsRight": "Правые кнопки", + "ControllerSettingsMisc": "Разное", + "ControllerSettingsTriggerThreshold": "Порог срабатывания:", + "ControllerSettingsMotion": "Движение", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Используйте движение, совместимое с CemuHook", + "ControllerSettingsMotionControllerSlot": "Слот контроллера:", + "ControllerSettingsMotionMirrorInput": "Зеркальный ввод", + "ControllerSettingsMotionRightJoyConSlot": "Правый JoyCon слот:", + "ControllerSettingsMotionServerHost": "Хост сервера:", + "ControllerSettingsMotionGyroSensitivity": "Чувствительность гироскопа:", + "ControllerSettingsMotionGyroDeadzone": "Мертвая зона гироскопа:", + "ControllerSettingsSave": "Сохранить", + "ControllerSettingsClose": "Закрыть", + "UserProfilesSelectedUserProfile": "Выбранный пользовательский профиль:", + "UserProfilesSaveProfileName": "Сохранить пользовательский профиль", + "UserProfilesChangeProfileImage": "Изменить изображение профиля", + "UserProfilesAvailableUserProfiles": "Доступные профили пользователей:", + "UserProfilesAddNewProfile": "Добавить новый профиль", + "UserProfilesDelete": "Удалить", + "UserProfilesClose": "Закрыть", + "ProfileNameSelectionWatermark": "Выберите псевдоним", + "ProfileImageSelectionTitle": "Выбор изображения профиля", + "ProfileImageSelectionHeader": "Выберите изображение профиля", + "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение профиля или выбрать аватар из системной прошивки.", + "ProfileImageSelectionImportImage": "Импорт файла изображения", + "ProfileImageSelectionSelectAvatar": "Выберите аватар прошивки", + "InputDialogTitle": "Диалоговое окно ввода", + "InputDialogOk": "ОК", + "InputDialogCancel": "Отмена", + "InputDialogAddNewProfileTitle": "Выберите имя профиля", + "InputDialogAddNewProfileHeader": "Пожалуйста, введите имя профиля", + "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", + "AvatarChoose": "Выбор аватара", + "AvatarSetBackgroundColor": "Установить цвет фона", + "AvatarClose": "Закрыть", + "ControllerSettingsLoadProfileToolTip": "Загрузить профиль", + "ControllerSettingsAddProfileToolTip": "Добавить профил", + "ControllerSettingsRemoveProfileToolTip": "Удалить профиль", + "ControllerSettingsSaveProfileToolTip": "Сохранить профиль", + "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", + "MenuBarFileToolsHideUi": "Скрыть UI", + "GameListContextMenuRunApplication": "Запуск приложения", + "GameListContextMenuToggleFavorite": "Переключить Избранное", + "GameListContextMenuToggleFavoriteToolTip": "Переключить любимый статус игры", + "SettingsTabGeneralTheme": "Тема", + "SettingsTabGeneralThemeCustomTheme": "Пользовательский путь к теме", + "SettingsTabGeneralThemeBaseStyle": "Базовый стиль", + "SettingsTabGeneralThemeBaseStyleDark": "Тёмная", + "SettingsTabGeneralThemeBaseStyleLight": "Светлая", + "SettingsTabGeneralThemeEnableCustomTheme": "Включить пользовательскую тему", + "ButtonBrowse": "Обзор", + "ControllerSettingsConfigureGeneral": "Настройка", + "ControllerSettingsRumble": "Вибрация", + "ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации", + "ControllerSettingsRumbleWeakMultiplier": "Множитель слабой вибрации", + "DialogMessageSaveNotAvailableMessage": "Нет сохраненных данных для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Создать сохранение для этой игры?", + "DialogConfirmationTitle": "Ryujinx - Подтверждение", + "DialogUpdaterTitle": "Ryujinx - Обновление", + "DialogErrorTitle": "Ryujinx - Ошибка", + "DialogWarningTitle": "Ryujinx - Предупреждение", + "DialogExitTitle": "Ryujinx - Выход", + "DialogErrorMessage": "Ryujinx обнаружил ошибку", + "DialogExitMessage": "Вы уверены, что хотите закрыть Ryujinx?", + "DialogExitSubMessage": "Все несохраненные данные будут потеряны!", + "DialogMessageCreateSaveErrorMessage": "Произошла ошибка при создании указанных данных сохранения: {0}", + "DialogMessageFindSaveErrorMessage": "Произошла ошибка при поиске указанных данных сохранения: {0}", + "FolderDialogExtractTitle": "Выберите папку для извлечения", + "DialogNcaExtractionMessage": "Извлечение {0} раздел от {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Экстрактор разделов NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ошибка извлечения. Основной NCA не присутствовал в выбранном файле.", + "DialogNcaExtractionCheckLogErrorMessage": "Ошибка извлечения. Прочтите файл журнала для получения дополнительной информации.", + "DialogNcaExtractionSuccessMessage": "Извлечение завершено успешно.", + "DialogUpdaterConvertFailedMessage": "Не удалось преобразовать текущую версию Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Отмена обновления!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы уже используете самую последнюю версию Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Произошла ошибка при попытке получить информацию о выпуске от GitHub Release. Это может быть вызвано тем, что в данный момент в GitHub Actions компилируется новый релиз. Повторите попытку позже.", + "DialogUpdaterConvertFailedGithubMessage": "Не удалось преобразовать полученную версию Ryujinx из Github Release.", + "DialogUpdaterDownloadingMessage": "Загрузка обновления...", + "DialogUpdaterExtractionMessage": "Извлечение обновления...", + "DialogUpdaterRenamingMessage": "Переименование обновления...", + "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", + "DialogUpdaterCompleteMessage": "Обновление завершено!", + "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", + "DialogUpdaterArchNotSupportedMessage": "Вы используете не поддерживаемую системную архитектуру!", + "DialogUpdaterArchNotSupportedSubMessage": "(Поддерживаются только x64 системы)", + "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету!", + "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас есть работающее подключение к интернету!", + "DialogUpdaterDirtyBuildMessage": "Вы не можете обновить Dirty Build!", + "DialogUpdaterDirtyBuildSubMessage": "Загрузите Ryujinx по адресу https://ryujinx.org/ если вам нужна поддерживаемая версия.", + "DialogRestartRequiredMessage": "Требуется перезагрузка", + "DialogThemeRestartMessage": "Тема сохранена. Для применения темы требуется перезапуск.", + "DialogThemeRestartSubMessage": "Вы хотите перезапустить?", + "DialogFirmwareInstallEmbeddedMessage": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь эмулятор запустится.", + "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не установлена", + "DialogFirmwareInstalledMessage": "Прошивка {0} была установлена", + "DialogInstallFileTypesSuccessMessage": "Успешно установлены типы файлов!", + "DialogInstallFileTypesErrorMessage": "Не удалось установить типы файлов.", + "DialogUninstallFileTypesSuccessMessage": "Успешно удалены типы файлов!", + "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", + "DialogOpenSettingsWindowLabel": "Открыть окно настроек", + "DialogControllerAppletTitle": "Апплет контроллера", + "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения диалогового окна сообщений: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", + "DialogErrorAppletErrorExceptionMessage": "Ошибка отображения диалогового окна ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nДля получения дополнительной информации о том, как исправить эту ошибку, следуйте нашему Руководству по установке.", + "DialogUserErrorDialogTitle": "Ошибка Ryujinx! ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Произошла ошибка при получении информации из API.", + "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна, или вам может потребоваться проверить, подключено ли ваше интернет-соединение к сети.", + "DialogProfileInvalidProfileErrorMessage": "Профиль {0} несовместим с текущей системой конфигурации ввода.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Профиль по умолчанию не может быть перезаписан", + "DialogProfileDeleteProfileTitle": "Удаление профиля", + "DialogProfileDeleteProfileMessage": "Это действие необратимо. Вы уверены, что хотите продолжить?", + "DialogWarning": "Внимание", + "DialogPPTCDeletionMessage": "Вы собираетесь удалить кэш PPTC для:\n\n{0}\n\nВы уверены, что хотите продолжить?", + "DialogPPTCDeletionErrorMessage": "Ошибка очистки кэша PPTC в {0}: {1}", + "DialogShaderDeletionMessage": "Вы собираетесь удалить кэш шейдеров для:\n\n{0}\n\nВы уверены, что хотите продолжить?", + "DialogShaderDeletionErrorMessage": "Ошибка очистки кэша шейдеров в {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx обнаружил ошибку", + "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного идентификатора названия.", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Действительная системная прошивка не найдена в {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия системы {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию системы {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nПродолжить?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Установка прошивки...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версия системы {0} успешно установлена.", + "DialogUserProfileDeletionWarningMessage": "Если выбранный профиль будет удален, другие профили не будут открываться.", + "DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль", + "DialogUserProfileUnsavedChangesTitle": "Внимание - Несохраненные изменения", + "DialogUserProfileUnsavedChangesMessage": "Вы внесли изменения в этот профиль пользователя которые не были сохранены.", + "DialogUserProfileUnsavedChangesSubMessage": "Вы хотите отменить изменения?", + "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", + "DialogLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}", + "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", + "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен сброс шейдеров, который предназначен только для разработчиков.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить сброс шейдеров. Вы хотите отключить сброс шейдеров сейчас?", + "DialogLoadAppGameAlreadyLoadedMessage": "Игра уже загружена", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Пожалуйста, остановите эмуляцию или закройте эмулятор перед запуском другой игры.", + "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновления для выбранного заголовка!", + "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx необходимо перезапустить после изменения этой опции, чтобы она полностью применилась. В зависимости от вашей платформы вам может потребоваться вручную отключить собственную многопоточность вашего драйвера при использовании Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Функции & Улучшения", + "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", + "CommonAuto": "Автоматически", + "CommonOff": "Выключен", + "CommonOn": "Включен", + "InputDialogYes": "Да", + "InputDialogNo": "Нет", + "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", + "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsResumeEmulation": "Продолжить", + "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx в браузере по умолчанию.", + "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", + "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiibo api.com) используется\n нашей эмуляции Amiibo.", + "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx Patreon в браузере по умолчанию.", + "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx GitHub в браузере по умолчанию.", + "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx Discord в браузере по умолчанию.", + "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в Twitter в браузере по умолчанию.", + "AboutRyujinxAboutTitle": "О программе:", + "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nУзнавайте все последние новости в нашем Twitter или Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitHub или в Discord.", + "AboutRyujinxMaintainersTitle": "Поддерживается:", + "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу Contributors в браузере по умолчанию.", + "AboutRyujinxSupprtersTitle": "Поддерживается на Patreon:", + "AmiiboSeriesLabel": "Серия Amiibo", + "AmiiboCharacterLabel": "Персонаж", + "AmiiboScanButtonLabel": "Сканировать", + "AmiiboOptionsShowAllLabel": "Показать все Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Хак: Использовать случайный тег Uuid", + "DlcManagerTableHeadingEnabledLabel": "Велючено", + "DlcManagerTableHeadingTitleIdLabel": "Идентификатор заголовка", + "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", + "DlcManagerTableHeadingFullPathLabel": "Полный путь", + "DlcManagerRemoveAllButton": "Убрать все", + "DlcManagerEnableAllButton": "Включить все", + "DlcManagerDisableAllButton": "Отключить все", + "MenuBarOptionsChangeLanguage": "Изменить язык", + "MenuBarShowFileTypes": "Показать типы файлов", + "CommonSort": "Сортировать", + "CommonShowNames": "Показать названия", + "CommonFavorite": "Избранные", + "OrderAscending": "По возрастанию", + "OrderDescending": "По убыванию", + "SettingsTabGraphicsFeatures": "Функции", + "ErrorWindowTitle": "Окно ошибки", + "ToggleDiscordTooltip": "Включает или отключает отображение в Discord статуса \"Сейчас играет\"", + "AddGameDirBoxTooltip": "Введите папку игры для добавления в список", + "AddGameDirTooltip": "AДобавить папку с игрой в список", + "RemoveGameDirTooltip": "Удалить выбранную папку игры", + "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы в графическом интерфейсе", + "CustomThemePathTooltip": "Путь к пользовательской теме интерфейса", + "CustomThemeBrowseTooltip": "Просмотр пользовательской темы интерфейса", + "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nОставьте включенным если не уверены.", + "DirectKeyboardTooltip": "Включить или отключить «поддержку прямого доступа к клавиатуре (HID)» (предоставляет играм доступ к клавиатуре как к устройству ввода текста)", + "DirectMouseTooltip": "Включить или отключить «поддержку прямого доступа к мыши (HID)» (предоставляет играм доступ к вашей мыши как указывающему устройству)", + "RegionTooltip": "Изменение региона системы", + "LanguageTooltip": "Изменение языка системы", + "TimezoneTooltip": "Изменение часового пояса системы", + "TimeTooltip": "Изменение системного времени", + "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш. Если планируете отключить вериткальную синхронизацию, мы рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", + "PptcToggleTooltip": "Сохранение преобразованных JIT-функций таким образом, чтобы их не нужно преобразовывать по новой каждый раз при загрузке игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", + "FsIntegrityToggleTooltip": "Проверяет поврежденные файлы при загрузке игры и если поврежденные файлы обнаружены, отображает ошибку о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для содействия в устранении неполадок.\n\nРекомендуется оставить включенным.", + "AudioBackendTooltip": "Изменяет используемый аудио-бэкенд для рендера звука.\n\nSDL2 является предпочтительным, в то время как OpenAL и SoundIO используются в качестве резервных. При выборе \"Заглушки\" звук будет отсутствовать.\n\nРекомендуется использование SDL2.", + "MemoryManagerTooltip": "Изменение разметки и доступа к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", + "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. Самая высокая точность, но самая низкая производительность.", + "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и запуск.", + "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. Быстрее, но менее безопасно. Гостевое приложение может получить доступ к памяти из любой точки Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", + "UseHypervisorTooltip": "Использует Гипервизор вместо JIT. Значительно увеличивает производительность, но может быть работать нестабильно.", + "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch для разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", + "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon OS. Это поможет избежать вылеты при загрузке определенных игр.\n\nРекомендуется оставить выключенным.", + "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", + "GalThreadingTooltip": "Выполняет команды графического бэкенда на во втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах GPU без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить в Авто.", + "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, который уменьшает статтеры при последующих запусках.\n\nРекомендуется оставить включенным.", + "ResolutionScaleTooltip": "Масштабирование разрешения", + "ResolutionScaleEntryTooltip": "Масштабирование разрешения с плавающей запятой, например 1,5. Неинтегральное масштабирование с большой вероятностью вызовет сбои в работе.", + "AnisotropyTooltip": "Уровень анизотропной фильтрации (установите значение «Авто», чтобы использовать значение в игре по умолчанию)", + "AspectRatioTooltip": "Соотношение сторон, применяемое к окну.", + "ShaderDumpPathTooltip": "Путь дампа графических шейдеров", + "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске. Не влияет на производительность.", + "StubLogTooltip": "Включает ведение журнала-заглушки. Не влияет на производительность.", + "InfoLogTooltip": "Включает печать сообщений информационного журнала. Не влияет на производительность.", + "WarnLogTooltip": "Включает печать сообщений журнала предупреждений. Не влияет на производительность.", + "ErrorLogTooltip": "Включает печать сообщений журнала ошибок. Не влияет на производительность.", + "TraceLogTooltip": "Выводит сообщения журнала трассировки в консоли. Не влияет на производительность.", + "GuestLogTooltip": "Включает печать сообщений гостевого журнала. Не влияет на производительность.", + "FileAccessLogTooltip": "Включает печать сообщений журнала доступа к файлам", + "FSAccessLogModeTooltip": "Включает вывод журнала доступа к файловой системе. Возможные режимы: 0-3", + "DeveloperOptionTooltip": "Используйте с осторожностью", + "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", + "DebugLogTooltip": "Выводит журнал сообщений отладки в консоли.\n\nИспользуйте только в случае просьбы разработчика, так как включение этой функции затруднит чтение журналов и ухудшит работу эмулятора.", + "LoadApplicationFileTooltip": "Открыть файловый менеджер для выбора файла, совместимого с Nintendo Switch.", + "LoadApplicationFolderTooltip": "Открыть файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", + "OpenRyujinxFolderTooltip": "Открывает папку файловой системы Ryujinx. ", + "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются журналы", + "ExitTooltip": "Выйти из Ryujinx", + "OpenSettingsTooltip": "Открыть окно настроек", + "OpenProfileManagerTooltip": "Открыть диспетчер профилей", + "StopEmulationTooltip": "Остановка эмуляции текущей игры и возврат к списку игр", + "CheckUpdatesTooltip": "Проверка наличия обновления Ryujinx", + "OpenAboutTooltip": "Открыть окно «О программе»", + "GridSize": "Размер сетки", + "GridSizeTooltip": "Изменение размера элементов сетки", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальский язык (Бразилия)", + "AboutRyujinxContributorsButtonHeader": "Посмотреть всех участников", + "SettingsTabSystemAudioVolume": "Громкость: ", + "AudioVolumeTooltip": "Изменяет громкость звука", + "SettingsTabSystemEnableInternetAccess": "Включить гостевой доступ в Интернет/сетевой режим", + "EnableInternetAccessTooltip": "Позволяет эмулированному приложению подключаться к Интернету.\n\nПри включении этой функции игры с возможностью сетевой игры могут подключаться друг к другу, если все эмуляторы (или реальные консоли) подключены к одной и той же точке доступа.\n\nНЕ разрешает подключение к серверам Nintendo. Может вызвать сбой в некоторых играх, которые пытаются подключиться к Интернету.\n\nРекомендутеся оставить выключенным.", + "GameListContextMenuManageCheatToolTip": "Управление читами", + "GameListContextMenuManageCheat": "Управление читами", + "ControllerSettingsStickRange": "Диапазон:", + "DialogStopEmulationTitle": "Ryujinx - Остановить эмуляцию", + "DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?", + "SettingsTabCpu": "ЦП", + "SettingsTabAudio": "Аудио", + "SettingsTabNetwork": "Сеть", + "SettingsTabNetworkConnection": "Подключение к сети", + "SettingsTabCpuCache": "Кэш ЦП", + "SettingsTabCpuMemory": "Память ЦП", + "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", + "UpdaterDisabledWarningTitle": "Обновление выключено!", + "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает альтернативную папку SD-карты Atmosphere, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", + "ControllerSettingsRotate90": "Повернуть на 90° по часовой стрелке", + "IconSize": "Размер иконки", + "IconSizeTooltip": "Изменить размер игровых иконок", + "MenuBarOptionsShowConsole": "Показать консоль", + "ShaderCachePurgeError": "Ошибка очистки кэша шейдеров в {0}: {1}", + "UserErrorNoKeys": "Ключи не найдены", + "UserErrorNoFirmware": "Прошивка не найдена", + "UserErrorFirmwareParsingFailed": "Ошибка извлечения прошивки", + "UserErrorApplicationNotFound": "Приложение не найдено", + "UserErrorUnknown": "Неизвестная ошибка", + "UserErrorUndefined": "Неопределенная ошибка", + "UserErrorNoKeysDescription": "Ryujinx не удалось найти ваш 'prod.keys' файл", + "UserErrorNoFirmwareDescription": "Ryujinx не удалось найти ни одной установленной прошивки", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx не удалось распаковать выбранную прошивку. Обычно это вызвано устаревшими ключами.", + "UserErrorApplicationNotFoundDescription": "Ryujinx не удалось найти действительное приложение по указанному пути.", + "UserErrorUnknownDescription": "Произошла неизвестная ошибка!", + "UserErrorUndefinedDescription": "Произошла неизвестная ошибка! Такого не должно происходить. Пожалуйста, свяжитесь с разработчиками!", + "OpenSetupGuideMessage": "Открыть руководство по установке", + "NoUpdate": "Нет обновлений", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Информация", + "RyujinxConfirm": "Ryujinx - Подтверждение", + "FileDialogAllTypes": "Все типы", + "Never": "Никогда", + "SwkbdMinCharacters": "Должно быть не менее {0} символов.", + "SwkbdMinRangeCharacters": "Должно быть {0}-{1} символов", + "SoftwareKeyboard": "Программная клавиатура", + "SoftwareKeyboardModeNumbersOnly": "Должны быть только цифры", + "SoftwareKeyboardModeAlphabet": "Не должно быть CJK-символов", + "SoftwareKeyboardModeASCII": "Текст должен быть только в ASCII кодировке", + "DialogControllerAppletMessagePlayerRange": "Приложение запрашивает {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте сейчас или нажмите \"Закрыть\".", + "DialogControllerAppletMessage": "Приложение запрашивает ровно {0} игроков с:\n\nТИПЫ: {1}\n\nИГРОКИ: {2}\n\n{3}Пожалуйста, откройте \"Настройки\" и перенастройте Ввод или нажмите \"Закрыть\".", + "DialogControllerAppletDockModeSet": "Установлен стационарный режим. Портативный режим недоступен.", + "UpdaterRenaming": "Переименование старых файлов...", + "UpdaterRenameFailed": "Программе обновления не удалось переименовать файл: {0}", + "UpdaterAddingFiles": "Добавление новых файлов...", + "UpdaterExtracting": "Извлечение обновления...", + "UpdaterDownloading": "Загрузка обновления...", + "Game": "Игра", + "Docked": "Стационарный режим", + "Handheld": "Портативный режим", + "ConnectionError": "Ошибка соединения!", + "AboutPageDeveloperListMore": "{0} и больше...", + "ApiError": "Ошибка API.", + "LoadingHeading": "Загрузка {0}", + "CompilingPPTC": "Компиляция PTC", + "CompilingShaders": "Компиляция шейдеров", + "AllKeyboards": "Все клавиатуры", + "OpenFileDialogTitle": "Выберите совместимый файл для открытия", + "OpenFolderDialogTitle": "Выберите папку с распакованной игрой", + "AllSupportedFormats": "Все поддерживаемые форматы", + "RyujinxUpdater": "Ryujinx - Обновление", + "SettingsTabHotkeys": "Горячие клавиши", + "SettingsTabHotkeysHotkeys": "Горячие клавиши", + "SettingsTabHotkeysToggleVsyncHotkey": "Переключить VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Скриншот:", + "SettingsTabHotkeysShowUiHotkey": "Показать UI:", + "SettingsTabHotkeysPauseHotkey": "Пауза:", + "SettingsTabHotkeysToggleMuteHotkey": "Приглушить:", + "ControllerMotionTitle": "Настройки управления движением", + "ControllerRumbleTitle": "Настройки вибрации", + "SettingsSelectThemeFileDialogTitle": "Выбрать файл темы", + "SettingsXamlThemeFile": "Файл темы Xaml", + "AvatarWindowTitle": "Управление аккаунтами - Аватар", + "Amiibo": "Amiibo", + "Unknown": "Неизвестно", + "Usage": "Применение", + "Writable": "Доступно для записи", + "SelectDlcDialogTitle": "Выберите файлы DLC", + "SelectUpdateDialogTitle": "Выберите файлы обновления", + "UserProfileWindowTitle": "Менеджер профилей пользователей", + "CheatWindowTitle": "Менеджер читов", + "DlcWindowTitle": "Управление загружаемым контентом для {0} ({1})", + "UpdateWindowTitle": "Менеджер обновления названий", + "CheatWindowHeading": "Читы доступны для {0} [{1}]", + "BuildId": "ID версии:", + "DlcWindowHeading": "{0} Загружаемый контент", + "UserProfilesEditProfile": "Изменить выбранные", + "Cancel": "Отмена", + "Save": "Сохранить", + "Discard": "Отменить", + "UserProfilesSetProfileImage": "Установить изображение профиля", + "UserProfileEmptyNameError": "Имя обязательно", + "UserProfileNoImageError": "Изображение профиля должно быть установлено", + "GameUpdateWindowHeading": "Обновление доступно для {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Увеличить разрешение:", + "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", + "UserProfilesName": "Имя:", + "UserProfilesUserId": "ID пользователя:", + "SettingsTabGraphicsBackend": "Бэкенд графики", + "SettingsTabGraphicsBackendTooltip": "Использовать графический бэкенд", + "SettingsEnableTextureRecompression": "Включить пережатие текстур", + "SettingsEnableTextureRecompressionTooltip": "Сжимает некоторые текстуры для уменьшения использования видеопамяти.\n\nРекомендуется для GPU с 4 гб видеопамяти и менее.\n\nРекомендуется оставить выключенным.", + "SettingsTabGraphicsPreferredGpu": "Предпочтительный GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Выберите видеокарту, которая будет использоваться с графическим бэкендом Vulkan.\n\nНе влияет на GPU, который будет использовать OpenGL.\n\nУстановите графический процессор, помеченный как \"dGPU\", если вы не уверены. Если его нет, оставьте нетронутым.", + "SettingsAppRequiredRestartMessage": "Требуется перезапуск Ryujinx", + "SettingsGpuBackendRestartMessage": "Графический бэкенд или настройки графического процессора были изменены. Требуется перезапуск для вступления в силу изменений.", + "SettingsGpuBackendRestartSubMessage": "Перезапустить сейчас?", + "RyujinxUpdaterMessage": "Вы хотите обновить Ryujinx до последней версии?", + "SettingsTabHotkeysVolumeUpHotkey": "Увеличить громкость:", + "SettingsTabHotkeysVolumeDownHotkey": "Уменьшить громкость:", + "SettingsEnableMacroHLE": "Включить Macro HLE", + "SettingsEnableMacroHLETooltip": "Высокоуровневая эмуляции макроса GPU.\n\nПовышает производительность, но может вызывать графические сбои в некоторых играх.\n\nРекомендуется оставить включенным.", + "SettingsEnableColorSpacePassthrough": "Пропуск цветового пространства", + "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкэнд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", + "VolumeShort": "Громкость", + "UserProfilesManageSaves": "Управление сохранениями", + "DeleteUserSave": "Вы хотите удалить сохранение пользователя для этой игры?", + "IrreversibleActionNote": "Данное действие является необратимым.", + "SaveManagerHeading": "Редактирование сохранений для {0} ({1})", + "SaveManagerTitle": "Менеджер сохранений", + "Name": "Название", + "Size": "Размер", + "Search": "Поиск", + "UserProfilesRecoverLostAccounts": "Восстановить утерянные аккаунты", + "Recover": "Восстановление", + "UserProfilesRecoverHeading": "Были найдены сохранения для следующих аккаунтов", + "UserProfilesRecoverEmptyList": "Нет профилей для восстановления", + "GraphicsAATooltip": "Применяет сглаживание к рейдеру игры.", + "GraphicsAALabel": "Сглаживание:", + "GraphicsScalingFilterLabel": "Масштабирующий фильтр:", + "GraphicsScalingFilterTooltip": "Включает масштабирование кадрового буфера", + "GraphicsScalingFilterLevelLabel": "Уровень", + "GraphicsScalingFilterLevelTooltip": "Установить уровень фильтра масштабирования", + "SmaaLow": "SMAA Низкое", + "SmaaMedium": "SMAA Среднее", + "SmaaHigh": "SMAA Высокое", + "SmaaUltra": "SMAA Ультра", + "UserEditorTitle": "Редактирование пользователя", + "UserEditorTitleCreate": "Создание пользователя", + "SettingsTabNetworkInterface": "Сетевой Интерфейс", + "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN", + "NetworkInterfaceDefault": "По умолчанию", + "PackagingShaders": "Упаковка шейдеров", + "AboutChangelogButton": "Список изменений на GitHub", + "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии в браузере." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/tr_TR.json b/src/Ryujinx.Ava/Assets/Locales/tr_TR.json new file mode 100644 index 000000000..3f70a781d --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/tr_TR.json @@ -0,0 +1,656 @@ +{ + "Language": "Türkçe", + "MenuBarFileOpenApplet": "Applet'i Aç", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Mii Editör Applet'ini Bağımsız Mod'da Aç", + "SettingsTabInputDirectMouseAccess": "Doğrudan Mouse Erişimi", + "SettingsTabSystemMemoryManagerMode": "Hafıza Yönetim Modu:", + "SettingsTabSystemMemoryManagerModeSoftware": "Yazılım", + "SettingsTabSystemMemoryManagerModeHost": "Host (hızlı)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (en hızlısı, tehlikeli)", + "SettingsTabSystemUseHypervisor": "Hypervisor Kullan", + "MenuBarFile": "_Dosya", + "MenuBarFileOpenFromFile": "_Dosyadan Uygulama Aç", + "MenuBarFileOpenUnpacked": "_Sıkıştırılmamış Oyun Aç", + "MenuBarFileOpenEmuFolder": "Ryujinx Klasörünü aç", + "MenuBarFileOpenLogsFolder": "Logs Klasörünü aç", + "MenuBarFileExit": "_Çıkış", + "MenuBarOptions": "Seçenekler", + "MenuBarOptionsToggleFullscreen": "Tam Ekran Modunu Aç", + "MenuBarOptionsStartGamesInFullscreen": "Oyunları Tam Ekran Modunda Başlat", + "MenuBarOptionsStopEmulation": "Emülasyonu Durdur", + "MenuBarOptionsSettings": "_Seçenekler", + "MenuBarOptionsManageUserProfiles": "_Kullanıcı Profillerini Yönet", + "MenuBarActions": "_Eylemler", + "MenuBarOptionsSimulateWakeUpMessage": "Uyandırma Mesajı Simüle Et", + "MenuBarActionsScanAmiibo": "Bir Amiibo Tara", + "MenuBarTools": "_Araçlar", + "MenuBarToolsInstallFirmware": "Yazılım Yükle", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI veya ZIP'ten Yazılım Yükle", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Bir Dizin Üzerinden Yazılım Yükle", + "MenuBarToolsManageFileTypes": "Dosya uzantılarını yönet", + "MenuBarToolsInstallFileTypes": "Dosya uzantılarını yükle", + "MenuBarToolsUninstallFileTypes": "Dosya uzantılarını kaldır", + "MenuBarHelp": "Yardım", + "MenuBarHelpCheckForUpdates": "Güncellemeleri Denetle", + "MenuBarHelpAbout": "Hakkında", + "MenuSearch": "Ara...", + "GameListHeaderFavorite": "Favori", + "GameListHeaderIcon": "Simge", + "GameListHeaderApplication": "Oyun Adı", + "GameListHeaderDeveloper": "Geliştirici", + "GameListHeaderVersion": "Sürüm", + "GameListHeaderTimePlayed": "Oynama Süresi", + "GameListHeaderLastPlayed": "Son Oynama Tarihi", + "GameListHeaderFileExtension": "Dosya Uzantısı", + "GameListHeaderFileSize": "Dosya Boyutu", + "GameListHeaderPath": "Yol", + "GameListContextMenuOpenUserSaveDirectory": "Kullanıcı Kayıt Dosyası Dizinini Aç", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Uygulamanın Kullanıcı Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuOpenDeviceSaveDirectory": "Kullanıcı Cihaz Dizinini Aç", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Uygulamanın Kullanıcı Cihaz Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuOpenBcatSaveDirectory": "Kullanıcı BCAT Dizinini Aç", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Uygulamanın Kullanıcı BCAT Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuManageTitleUpdates": "Oyun Güncellemelerini Yönet", + "GameListContextMenuManageTitleUpdatesToolTip": "Oyun Güncelleme Yönetim Penceresini Açar", + "GameListContextMenuManageDlc": "DLC'leri Yönet", + "GameListContextMenuManageDlcToolTip": "DLC yönetim penceresini açar", + "GameListContextMenuOpenModsDirectory": "Mod Dizinini Aç", + "GameListContextMenuOpenModsDirectoryToolTip": "Uygulamanın modlarının bulunduğu dizini açar", + "GameListContextMenuCacheManagement": "Önbellek Yönetimi", + "GameListContextMenuCacheManagementPurgePptc": "PPTC Yeniden Yapılandırmasını Başlat", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Oyunun bir sonraki açılışında PPTC'yi yeniden yapılandır", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Önbelleğini Temizle", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Uygulamanın shader önbelleğini temizler", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC Dizinini Aç", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Uygulamanın PPTC Önbelleğinin bulunduğu dizini açar", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader Önbelleği Dizinini Aç", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Uygulamanın shader önbelleğinin bulunduğu dizini açar", + "GameListContextMenuExtractData": "Veriyi Ayıkla", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Uygulamanın geçerli yapılandırmasından ExeFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Uygulamanın geçerli yapılandırmasından RomFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataLogo": "Simge", + "GameListContextMenuExtractDataLogoToolTip": "Uygulamanın geçerli yapılandırmasından Logo kısmını ayıkla (Güncellemeler dahil)", + "StatusBarGamesLoaded": "{0}/{1} Oyun Yüklendi", + "StatusBarSystemVersion": "Sistem Sürümü: {0}", + "LinuxVmMaxMapCountDialogTitle": "Bellek Haritaları İçin Düşük Limit Tespit Edildi ", + "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count değerini {0} sayısına yükseltmek ister misiniz", + "LinuxVmMaxMapCountDialogTextSecondary": "Bazı oyunlar şu an izin verilen bellek haritası limitinden daha fazlasını yaratmaya çalışabilir. Ryujinx bu limitin geçildiği takdirde kendini kapatıcaktır.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Evet, bir sonraki yeniden başlatmaya kadar", + "LinuxVmMaxMapCountDialogButtonPersistent": "Evet, kalıcı olarak", + "LinuxVmMaxMapCountWarningTextPrimary": "İzin verilen maksimum bellek haritası değeri tavsiye edildiğinden daha düşük. ", + "LinuxVmMaxMapCountWarningTextSecondary": "Şu anki vm.max_map_count değeri {0}, bu {1} değerinden daha az. Bazı oyunlar şu an izin verilen bellek haritası limitinden daha fazlasını yaratmaya çalışabilir. Ryujinx bu limitin geçildiği takdirde kendini kapatıcaktır.\n\nManuel olarak bu limiti arttırmayı deneyebilir ya da pkexec'i yükleyebilirsiniz, bu da Ryujinx'in yardımcı olmasına izin verir.", + "Settings": "Ayarlar", + "SettingsTabGeneral": "Kullancı Arayüzü", + "SettingsTabGeneralGeneral": "Genel", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord Zengin İçerik'i Etkinleştir", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Her Açılışta Güncellemeleri Denetle", + "SettingsTabGeneralShowConfirmExitDialog": "\"Çıkışı Onayla\" Diyaloğunu Göster", + "SettingsTabGeneralHideCursor": "İşaretçiyi Gizle:", + "SettingsTabGeneralHideCursorNever": "Hiçbir Zaman", + "SettingsTabGeneralHideCursorOnIdle": "Hareketsiz Durumda", + "SettingsTabGeneralHideCursorAlways": "Her Zaman", + "SettingsTabGeneralGameDirectories": "Oyun Dizinleri", + "SettingsTabGeneralAdd": "Ekle", + "SettingsTabGeneralRemove": "Kaldır", + "SettingsTabSystem": "Sistem", + "SettingsTabSystemCore": "Çekirdek", + "SettingsTabSystemSystemRegion": "Sistem Bölgesi:", + "SettingsTabSystemSystemRegionJapan": "Japonya", + "SettingsTabSystemSystemRegionUSA": "ABD", + "SettingsTabSystemSystemRegionEurope": "Avrupa", + "SettingsTabSystemSystemRegionAustralia": "Avustralya", + "SettingsTabSystemSystemRegionChina": "Çin", + "SettingsTabSystemSystemRegionKorea": "Kore", + "SettingsTabSystemSystemRegionTaiwan": "Tayvan", + "SettingsTabSystemSystemLanguage": "Sistem Dili:", + "SettingsTabSystemSystemLanguageJapanese": "Japonca", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikan İngilizcesi", + "SettingsTabSystemSystemLanguageFrench": "Fransızca", + "SettingsTabSystemSystemLanguageGerman": "Almanca", + "SettingsTabSystemSystemLanguageItalian": "İtalyanca", + "SettingsTabSystemSystemLanguageSpanish": "İspanyolca", + "SettingsTabSystemSystemLanguageChinese": "Çince", + "SettingsTabSystemSystemLanguageKorean": "Korece", + "SettingsTabSystemSystemLanguageDutch": "Flemenkçe", + "SettingsTabSystemSystemLanguagePortuguese": "Portekizce", + "SettingsTabSystemSystemLanguageRussian": "Rusça", + "SettingsTabSystemSystemLanguageTaiwanese": "Tayvanca", + "SettingsTabSystemSystemLanguageBritishEnglish": "İngiliz İngilizcesi", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanada Fransızcası", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin Amerika İspanyolcası", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Basitleştirilmiş Çince", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Geleneksel Çince", + "SettingsTabSystemSystemTimeZone": "Sistem Saat Dilimi:", + "SettingsTabSystemSystemTime": "Sistem Saati:", + "SettingsTabSystemEnableVsync": "Dikey Eşitleme", + "SettingsTabSystemEnablePptc": "PPTC (Profilli Sürekli Çeviri Önbelleği)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Bütünlük Kontrolleri", + "SettingsTabSystemAudioBackend": "Ses Motoru:", + "SettingsTabSystemAudioBackendDummy": "Yapay", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hack'ler", + "SettingsTabSystemHacksNote": " (dengesizlik oluşturabilir)", + "SettingsTabSystemExpandDramSize": "Alternatif bellek düzeni kullan (Geliştirici)", + "SettingsTabSystemIgnoreMissingServices": "Eksik Servisleri Görmezden Gel", + "SettingsTabGraphics": "Grafikler", + "SettingsTabGraphicsAPI": "Grafikler API", + "SettingsTabGraphicsEnableShaderCache": "Shader Önbelleğini Etkinleştir", + "SettingsTabGraphicsAnisotropicFiltering": "Eşyönsüz Doku Süzmesi:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Otomatik", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Çözünürlük Ölçeği:", + "SettingsTabGraphicsResolutionScaleCustom": "Özel (Tavsiye Edilmez)", + "SettingsTabGraphicsResolutionScaleNative": "Yerel (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "En-Boy Oranı:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Pencereye Sığdırmak İçin Genişlet", + "SettingsTabGraphicsDeveloperOptions": "Geliştirici Seçenekleri", + "SettingsTabGraphicsShaderDumpPath": "Grafik Shader Döküm Yolu:", + "SettingsTabLogging": "Loglama", + "SettingsTabLoggingLogging": "Loglama", + "SettingsTabLoggingEnableLoggingToFile": "Logları Dosyaya Kaydetmeyi Etkinleştir", + "SettingsTabLoggingEnableStubLogs": "Stub Loglarını Etkinleştir", + "SettingsTabLoggingEnableInfoLogs": "Bilgi Loglarını Etkinleştir", + "SettingsTabLoggingEnableWarningLogs": "Uyarı Loglarını Etkinleştir", + "SettingsTabLoggingEnableErrorLogs": "Hata Loglarını Etkinleştir", + "SettingsTabLoggingEnableTraceLogs": "Trace Loglarını Etkinleştir", + "SettingsTabLoggingEnableGuestLogs": "Guest Loglarını Etkinleştir", + "SettingsTabLoggingEnableFsAccessLogs": "Fs Erişim Loglarını Etkinleştir", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Evrensel Erişim Log Modu:", + "SettingsTabLoggingDeveloperOptions": "Geliştirici Seçenekleri (UYARI: Performansı düşürecektir)", + "SettingsTabLoggingDeveloperOptionsNote": "UYARI: Oyun performansı azalacak", + "SettingsTabLoggingGraphicsBackendLogLevel": "Grafik Arka Uç Günlük Düzeyi", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Hiçbiri", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Hata", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Yavaşlamalar", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Hepsi", + "SettingsTabLoggingEnableDebugLogs": "Hata Ayıklama Loglarını Etkinleştir", + "SettingsTabInput": "Giriş Yöntemi", + "SettingsTabInputEnableDockedMode": "Docked Modu Etkinleştir", + "SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi", + "SettingsButtonSave": "Kaydet", + "SettingsButtonClose": "Kapat", + "SettingsButtonOk": "Tamam", + "SettingsButtonCancel": "İptal", + "SettingsButtonApply": "Uygula", + "ControllerSettingsPlayer": "Oyuncu", + "ControllerSettingsPlayer1": "Oyuncu 1", + "ControllerSettingsPlayer2": "Oyuncu 2", + "ControllerSettingsPlayer3": "Oyuncu 3", + "ControllerSettingsPlayer4": "Oyuncu 4", + "ControllerSettingsPlayer5": "Oyuncu 5", + "ControllerSettingsPlayer6": "Oyuncu 6", + "ControllerSettingsPlayer7": "Oyuncu 7", + "ControllerSettingsPlayer8": "Oyuncu 8", + "ControllerSettingsHandheld": "Portatif Mod", + "ControllerSettingsInputDevice": "Giriş Cihazı", + "ControllerSettingsRefresh": "Yenile", + "ControllerSettingsDeviceDisabled": "Devre Dışı", + "ControllerSettingsControllerType": "Kontrolcü Tipi", + "ControllerSettingsControllerTypeHandheld": "Portatif Mod", + "ControllerSettingsControllerTypeProController": "Profesyonel Denetleyici", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Çifti", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Sol", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Sağ", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Varsayılan", + "ControllerSettingsLoad": "Yükle", + "ControllerSettingsAdd": "Ekle", + "ControllerSettingsRemove": "Kaldır", + "ControllerSettingsButtons": "Tuşlar", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Yön Tuşları", + "ControllerSettingsDPadUp": "Yukarı", + "ControllerSettingsDPadDown": "Aşağı", + "ControllerSettingsDPadLeft": "Sol", + "ControllerSettingsDPadRight": "Sağ", + "ControllerSettingsStickButton": "Tuş", + "ControllerSettingsStickUp": "Yukarı", + "ControllerSettingsStickDown": "Aşağı", + "ControllerSettingsStickLeft": "Sol", + "ControllerSettingsStickRight": "Sağ", + "ControllerSettingsStickStick": "Analog", + "ControllerSettingsStickInvertXAxis": "X Eksenini Tersine Çevir", + "ControllerSettingsStickInvertYAxis": "Y Eksenini Tersine Çevir", + "ControllerSettingsStickDeadzone": "Ölü Bölge", + "ControllerSettingsLStick": "Sol Analog", + "ControllerSettingsRStick": "Sağ Analog", + "ControllerSettingsTriggersLeft": "Tetikler Sol", + "ControllerSettingsTriggersRight": "Tetikler Sağ", + "ControllerSettingsTriggersButtonsLeft": "Tetik Tuşları Sol", + "ControllerSettingsTriggersButtonsRight": "Tetik Tuşları Sağ", + "ControllerSettingsTriggers": "Tetikler", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Tuşlar Sol", + "ControllerSettingsExtraButtonsRight": "Tuşlar Sağ", + "ControllerSettingsMisc": "Diğer", + "ControllerSettingsTriggerThreshold": "Tetik Eşiği:", + "ControllerSettingsMotion": "Hareket", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook uyumlu hareket kullan", + "ControllerSettingsMotionControllerSlot": "Kontrolcü Yuvası:", + "ControllerSettingsMotionMirrorInput": "Girişi Aynala", + "ControllerSettingsMotionRightJoyConSlot": "Sağ JoyCon Yuvası:", + "ControllerSettingsMotionServerHost": "Sunucu Sahibi:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Hassasiyeti:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Ölü Bölgesi:", + "ControllerSettingsSave": "Kaydet", + "ControllerSettingsClose": "Kapat", + "UserProfilesSelectedUserProfile": "Seçili Kullanıcı Profili:", + "UserProfilesSaveProfileName": "Profil İsmini Kaydet", + "UserProfilesChangeProfileImage": "Profil Resmini Değiştir", + "UserProfilesAvailableUserProfiles": "Mevcut Kullanıcı Profilleri:", + "UserProfilesAddNewProfile": "Yeni Profil Ekle", + "UserProfilesDelete": "Sil", + "UserProfilesClose": "Kapat", + "ProfileNameSelectionWatermark": "Kullanıcı Adı Seç", + "ProfileImageSelectionTitle": "Profil Resmi Seçimi", + "ProfileImageSelectionHeader": "Profil Resmi Seç", + "ProfileImageSelectionNote": "Özel bir profil resmi içeri aktarabilir veya sistem avatarlarından birini seçebilirsiniz", + "ProfileImageSelectionImportImage": "Resim İçeri Aktar", + "ProfileImageSelectionSelectAvatar": "Yazılım Avatarı Seç", + "InputDialogTitle": "Giriş Yöntemi Diyaloğu", + "InputDialogOk": "Tamam", + "InputDialogCancel": "İptal", + "InputDialogAddNewProfileTitle": "Profil İsmini Seç", + "InputDialogAddNewProfileHeader": "Lütfen Bir Profil İsmi Girin", + "InputDialogAddNewProfileSubtext": "(Maksimum Uzunluk: {0})", + "AvatarChoose": "Seç", + "AvatarSetBackgroundColor": "Arka Plan Rengi Ayarla", + "AvatarClose": "Kapat", + "ControllerSettingsLoadProfileToolTip": "Profil Yükle", + "ControllerSettingsAddProfileToolTip": "Profil Ekle", + "ControllerSettingsRemoveProfileToolTip": "Profili Kaldır", + "ControllerSettingsSaveProfileToolTip": "Profili Kaydet", + "MenuBarFileToolsTakeScreenshot": "Ekran Görüntüsü Al", + "MenuBarFileToolsHideUi": "Arayüzü Gizle", + "GameListContextMenuRunApplication": "Uygulamayı Çalıştır", + "GameListContextMenuToggleFavorite": "Favori Ayarla", + "GameListContextMenuToggleFavoriteToolTip": "Oyunu Favorilere Ekle/Çıkar", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Özel Tema Yolu", + "SettingsTabGeneralThemeBaseStyle": "Temel Stil", + "SettingsTabGeneralThemeBaseStyleDark": "Karanlık", + "SettingsTabGeneralThemeBaseStyleLight": "Aydınlık", + "SettingsTabGeneralThemeEnableCustomTheme": "Özel Tema Etkinleştir", + "ButtonBrowse": "Göz At", + "ControllerSettingsConfigureGeneral": "Ayarla", + "ControllerSettingsRumble": "Titreşim", + "ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çoklayıcı", + "ControllerSettingsRumbleWeakMultiplier": "Zayıf Titreşim Seviyesi", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] için kayıt verisi bulunamadı", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Bu oyun için kayıt verisi oluşturmak ister misiniz?", + "DialogConfirmationTitle": "Ryujinx - Onay", + "DialogUpdaterTitle": "Ryujinx - Güncelleyici", + "DialogErrorTitle": "Ryujinx - Hata", + "DialogWarningTitle": "Ryujinx - Uyarı", + "DialogExitTitle": "Ryujinx - Çıkış", + "DialogErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogExitMessage": "Ryujinx'i kapatmak istediğinizden emin misiniz?", + "DialogExitSubMessage": "Kaydedilmeyen bütün veriler kaybedilecek!", + "DialogMessageCreateSaveErrorMessage": "Belirtilen kayıt verisi oluşturulurken bir hata oluştu: {0}", + "DialogMessageFindSaveErrorMessage": "Belirtilen kayıt verisi bulunmaya çalışırken hata: {0}", + "FolderDialogExtractTitle": "İçine ayıklanacak klasörü seç", + "DialogNcaExtractionMessage": "{1} den {0} kısmı ayıklanıyor...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Kısmı Ayıklayıcısı", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ayıklama hatası. Ana NCA seçilen dosyada bulunamadı.", + "DialogNcaExtractionCheckLogErrorMessage": "Ayıklama hatası. Ek bilgi için kayıt dosyasını okuyun.", + "DialogNcaExtractionSuccessMessage": "Ayıklama başarıyla tamamlandı.", + "DialogUpdaterConvertFailedMessage": "Güncel Ryujinx sürümü dönüştürülemedi.", + "DialogUpdaterCancelUpdateMessage": "Güncelleme iptal ediliyor!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Zaten Ryujinx'in en güncel sürümünü kullanıyorsunuz!", + "DialogUpdaterFailedToGetVersionMessage": "GitHub tarafından sürüm bilgileri alınırken bir hata oluştu. Eğer yeni sürüm için hazırlıklar yapılıyorsa bu hatayı almanız olasıdır. Lütfen birkaç dakika sonra tekrar deneyiniz.", + "DialogUpdaterConvertFailedGithubMessage": "Github Release'den alınan Ryujinx sürümü dönüştürülemedi.", + "DialogUpdaterDownloadingMessage": "Güncelleme İndiriliyor...", + "DialogUpdaterExtractionMessage": "Güncelleme Ayıklanıyor...", + "DialogUpdaterRenamingMessage": "Güncelleme Yeniden Adlandırılıyor...", + "DialogUpdaterAddingFilesMessage": "Yeni Güncelleme Ekleniyor...", + "DialogUpdaterCompleteMessage": "Güncelleme Tamamlandı!", + "DialogUpdaterRestartMessage": "Ryujinx'i şimdi yeniden başlatmak istiyor musunuz?", + "DialogUpdaterArchNotSupportedMessage": "Sistem mimariniz desteklenmemektedir!", + "DialogUpdaterArchNotSupportedSubMessage": "(Sadece x64 sistemleri desteklenmektedir!)", + "DialogUpdaterNoInternetMessage": "İnternete bağlı değilsiniz!", + "DialogUpdaterNoInternetSubMessage": "Lütfen aktif bir internet bağlantınız olduğunu kontrol edin!", + "DialogUpdaterDirtyBuildMessage": "Ryujinx'in Dirty build'lerini güncelleyemezsiniz!", + "DialogUpdaterDirtyBuildSubMessage": "Desteklenen bir sürüm için lütfen Ryujinx'i https://ryujinx.org/ sitesinden indirin.", + "DialogRestartRequiredMessage": "Yeniden Başlatma Gerekli", + "DialogThemeRestartMessage": "Tema kaydedildi. Temayı uygulamak için yeniden başlatma gerekiyor.", + "DialogThemeRestartSubMessage": "Yeniden başlatmak ister misiniz", + "DialogFirmwareInstallEmbeddedMessage": "Bu oyunun içine gömülü olan yazılımı yüklemek ister misiniz? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Yüklü firmware bulunamadı ancak Ryujinx sağlanan oyundan {0} firmware sürümünü yükledi.\nEmülatör şimdi başlatılacak.", + "DialogFirmwareNoFirmwareInstalledMessage": "Yazılım Yüklü Değil", + "DialogFirmwareInstalledMessage": "Yazılım {0} yüklendi", + "DialogInstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla yüklendi!", + "DialogInstallFileTypesErrorMessage": "Dosya uzantıları yükleme işlemi başarısız oldu.", + "DialogUninstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla kaldırıldı!", + "DialogUninstallFileTypesErrorMessage": "Dosya uzantıları kaldırma işlemi başarısız oldu.", + "DialogOpenSettingsWindowLabel": "Seçenekler Penceresini Aç", + "DialogControllerAppletTitle": "Kontrolcü Applet'i", + "DialogMessageDialogErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", + "DialogErrorAppletErrorExceptionMessage": "Applet diyaloğu gösterilirken hata: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nBu hatayı düzeltmek adına daha fazla bilgi için kurulum kılavuzumuzu takip edin.", + "DialogUserErrorDialogTitle": "Ryujinx Hatası ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API'dan bilgi alırken bir hata oluştu.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API sunucusuna bağlanılamadı. Sunucu çevrimdışı olabilir veya uygun bir internet bağlantınızın olduğunu kontrol etmeniz gerekebilir.", + "DialogProfileInvalidProfileErrorMessage": "Profil {0} güncel giriş konfigürasyon sistemi ile uyumlu değil.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Varsayılan Profil'in üstüne yazılamaz", + "DialogProfileDeleteProfileTitle": "Profil Siliniyor", + "DialogProfileDeleteProfileMessage": "Bu eylem geri döndürülemez, devam etmek istediğinizden emin misiniz?", + "DialogWarning": "Uyarı", + "DialogPPTCDeletionMessage": "Belirtilen PPTC cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogPPTCDeletionErrorMessage": "Belirtilen PPTC cache temizlenirken hata {0}: {1}", + "DialogShaderDeletionMessage": "Belirtilen Shader cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogShaderDeletionErrorMessage": "Belirtilen Shader cache temizlenirken hata {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogInvalidTitleIdErrorMessage": "Arayüz hatası: Seçilen oyun geçerli bir title ID'ye sahip değil", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} da geçerli bir sistem firmware'i bulunamadı.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Firmware {0} Yükle", + "DialogFirmwareInstallerFirmwareInstallMessage": "Sistem sürümü {0} yüklenecek.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nBu şimdiki sistem sürümünün yerini alacak {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDevam etmek istiyor musunuz?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware yükleniyor...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Sistem sürümü {0} başarıyla yüklendi.", + "DialogUserProfileDeletionWarningMessage": "Seçilen profil silinirse kullanılabilen başka profil kalmayacak", + "DialogUserProfileDeletionConfirmMessage": "Seçilen profili silmek istiyor musunuz", + "DialogUserProfileUnsavedChangesTitle": "Uyarı - Kaydedilmemiş Değişiklikler", + "DialogUserProfileUnsavedChangesMessage": "Kullanıcı profilinizde kaydedilmemiş değişiklikler var.", + "DialogUserProfileUnsavedChangesSubMessage": "Yaptığınız değişiklikleri iptal etmek istediğinize emin misiniz?", + "DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", + "DialogLoadNcaErrorMessage": "{0}. Hatalı Dosya: {1}", + "DialogDlcNoDlcErrorMessage": "Belirtilen dosya seçilen oyun için DLC içermiyor!", + "DialogPerformanceCheckLoggingEnabledMessage": "Sadece geliştiriler için dizayn edilen Trace Loglama seçeneği etkin.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "En iyi performans için trace loglama'nın devre dışı bırakılması tavsiye edilir. Trace loglama seçeneğini şimdi devre dışı bırakmak ister misiniz?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Sadece geliştiriler için dizayn edilen Shader Dumping seçeneği etkin.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "En iyi performans için Shader Dumping'in devre dışı bırakılması tavsiye edilir. Shader Dumping seçeneğini şimdi devre dışı bırakmak ister misiniz?", + "DialogLoadAppGameAlreadyLoadedMessage": "Bir oyun zaten yüklendi", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Lütfen yeni bir oyun açmadan önce emülasyonu durdurun veya emülatörü kapatın.", + "DialogUpdateAddUpdateErrorMessage": "Belirtilen dosya seçilen oyun için güncelleme içermiyor!", + "DialogSettingsBackendThreadingWarningTitle": "Uyarı - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Bu seçeneğin tamamen uygulanması için Ryujinx'in kapatıp açılması gerekir. Kullandığınız işletim sistemine bağlı olarak, Ryujinx'in multithreading'ini kullanırken driver'ınızın multithreading seçeneğini kapatmanız gerekebilir.", + "SettingsTabGraphicsFeaturesOptions": "Özellikler", + "SettingsTabGraphicsBackendMultithreading": "Grafik Backend Multithreading:", + "CommonAuto": "Otomatik", + "CommonOff": "Kapalı", + "CommonOn": "Açık", + "InputDialogYes": "Evet", + "InputDialogNo": "Hayır", + "DialogProfileInvalidProfileNameErrorMessage": "Dosya adı geçersiz karakter içeriyor. Lütfen tekrar deneyin.", + "MenuBarOptionsPauseEmulation": "Durdur", + "MenuBarOptionsResumeEmulation": "Devam Et", + "AboutUrlTooltipMessage": "Ryujinx'in websitesini varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDisclaimerMessage": "Ryujinx, Nintendo™ veya ortaklarıyla herhangi bir şekilde bağlantılı değildir.", + "AboutAmiiboDisclaimerMessage": "Amiibo emülasyonumuzda \nAmiiboAPI (www.amiiboapi.com) kullanılmaktadır.", + "AboutPatreonUrlTooltipMessage": "Ryujinx'in Patreon sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutGithubUrlTooltipMessage": "Ryujinx'in GitHub sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDiscordUrlTooltipMessage": "Varsayılan tarayıcınızda Ryujinx'in Discord'una bir davet açmak için tıklayın.", + "AboutTwitterUrlTooltipMessage": "Ryujinx'in Twitter sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutRyujinxAboutTitle": "Hakkında:", + "AboutRyujinxAboutContent": "Ryujinx bir Nintendo Switch™ emülatörüdür.\nLütfen bizi Patreon'da destekleyin.\nEn son haberleri Twitter veya Discord'umuzdan alın.\nKatkıda bulunmak isteyen geliştiriciler GitHub veya Discord üzerinden daha fazla bilgi edinebilir.", + "AboutRyujinxMaintainersTitle": "Geliştiriciler:", + "AboutRyujinxMaintainersContentTooltipMessage": "Katkıda bulunanlar sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutRyujinxSupprtersTitle": "Patreon Destekleyicileri:", + "AmiiboSeriesLabel": "Amiibo Serisi", + "AmiiboCharacterLabel": "Karakter", + "AmiiboScanButtonLabel": "Tarat", + "AmiiboOptionsShowAllLabel": "Tüm Amiibo'ları Göster", + "AmiiboOptionsUsRandomTagLabel": "Hack: Rastgele bir Uuid kullan", + "DlcManagerTableHeadingEnabledLabel": "Etkin", + "DlcManagerTableHeadingTitleIdLabel": "Başlık ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Yol", + "DlcManagerTableHeadingFullPathLabel": "Tam Yol", + "DlcManagerRemoveAllButton": "Tümünü kaldır", + "DlcManagerEnableAllButton": "Tümünü Aktif Et", + "DlcManagerDisableAllButton": "Tümünü Devre Dışı Bırak", + "MenuBarOptionsChangeLanguage": "Dili Değiştir", + "MenuBarShowFileTypes": "Dosya Uzantılarını Göster", + "CommonSort": "Sırala", + "CommonShowNames": "İsimleri Göster", + "CommonFavorite": "Favori", + "OrderAscending": "Artan", + "OrderDescending": "Azalan", + "SettingsTabGraphicsFeatures": "Özellikler & İyileştirmeler", + "ErrorWindowTitle": "Hata Penceresi", + "ToggleDiscordTooltip": "Ryujinx'i \"şimdi oynanıyor\" Discord aktivitesinde göstermeyi veya göstermemeyi seçin", + "AddGameDirBoxTooltip": "Listeye eklemek için oyun dizini seçin", + "AddGameDirTooltip": "Listeye oyun dizini ekle", + "RemoveGameDirTooltip": "Seçili oyun dizinini kaldır", + "CustomThemeCheckTooltip": "Emülatör pencerelerinin görünümünü değiştirmek için özel bir Avalonia teması kullan", + "CustomThemePathTooltip": "Özel arayüz temasının yolu", + "CustomThemeBrowseTooltip": "Özel arayüz teması için göz at", + "DockModeToggleTooltip": "Docked modu emüle edilen sistemin yerleşik Nintendo Switch gibi davranmasını sağlar. Bu çoğu oyunda grafik kalitesini arttırır. Diğer yandan, bu seçeneği devre dışı bırakmak emüle edilen sistemin elde Ninendo Switch gibi davranmasını sağlayıp grafik kalitesini düşürür.\n\nDocked modu kullanmayı düşünüyorsanız 1. Oyuncu kontrollerini; Handheld modunu kullanmak istiyorsanız Handheld kontrollerini konfigüre edin.\n\nEmin değilseniz aktif halde bırakın.", + "DirectKeyboardTooltip": "Doğrudan Klavye Erişimi (HID) desteği. Oyunların klavyenizi metin giriş cihazı olarak kullanmasını sağlar.", + "DirectMouseTooltip": "Doğrudan Fare Erişimi (HID) desteği. Oyunların farenizi işaret aygıtı olarak kullanmasını sağlar.", + "RegionTooltip": "Sistem Bölgesini Değiştir", + "LanguageTooltip": "Sistem Dilini Değiştir", + "TimezoneTooltip": "Sistem Saat Dilimini Değiştir", + "TimeTooltip": "Sistem Saatini Değiştir", + "VSyncToggleTooltip": "Emüle edilen konsolun Dikey Senkronizasyonu. Çoğu oyun için kare sınırlayıcı işlevi görür, bu seçeneği devre dışı bırakmak bazı oyunların normalden yüksek hızda çalışmasını ve yükleme ekranlarının daha uzun sürmesini veya sıkışıp kalmasını sağlar.\n\nTercih ettiğiniz bir kısayol ile oyun içindeyken etkinleştirilip devre dışı bırakılabilir. Bu seçeneği devre dışı bırakmayı düşünüyorsanız bir kısayol atamanızı öneririz.\n\nEmin değilseniz aktif halde bırakın.", + "PptcToggleTooltip": "Çevrilen JIT fonksiyonlarını oyun her açıldığında çevrilmek zorunda kalmaması için kaydeder.\n\nTeklemeyi azaltır ve ilk açılıştan sonra oyunların ilk açılış süresini ciddi biçimde hızlandırır.\n\nEmin değilseniz aktif halde bırakın.", + "FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.", + "AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.", + "MemoryManagerTooltip": "Guest hafızasının nasıl tahsis edilip erişildiğini değiştirir. Emüle edilen CPU performansını ciddi biçimde etkiler.\n\nEmin değilseniz HOST UNCHECKED seçeneğine ayarlayın.", + "MemoryManagerSoftwareTooltip": "Adres çevirisi için bir işlemci sayfası kullanır. En yüksek doğruluğu ve en yavaş performansı sunar.", + "MemoryManagerHostTooltip": "Hafızayı doğrudan host adres aralığında tahsis eder. Çok daha hızlı JIT derleme ve işletimi sunar.", + "MemoryManagerUnsafeTooltip": "Hafızayı doğrudan tahsis eder, ancak host aralığına erişimden önce adresi maskelemez. Daha iyi performansa karşılık emniyetten ödün verir. Misafir uygulama Ryujinx içerisinden istediği hafızaya erişebilir, bu sebeple bu seçenek ile sadece güvendiğiniz uygulamaları çalıştırın.", + "UseHypervisorTooltip": "JIT yerine Hypervisor kullan. Uygun durumlarda performansı büyük oranda arttırır. Ancak şu anki halinde stabil durumda çalışmayabilir.", + "DRamTooltip": "Emüle edilen sistem hafızasını 4GiB'dan 6GiB'a yükseltir.\n\nBu seçenek yalnızca yüksek çözünürlük doku paketleri veya 4k çözünürlük modları için kullanılır. Performansı artırMAZ!\n\nEmin değilseniz devre dışı bırakın.", + "IgnoreMissingServicesTooltip": "Henüz programlanmamış Horizon işletim sistemi servislerini görmezden gelir. Bu seçenek belirli oyunların açılırken çökmesinin önüne geçmeye yardımcı olabilir.\n\nEmin değilseniz devre dışı bırakın.", + "GraphicsBackendThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", + "GalThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", + "ShaderCacheToggleTooltip": "Sonraki çalışmalarda takılmaları engelleyen bir gölgelendirici disk önbelleğine kaydeder.", + "ResolutionScaleTooltip": "Uygulanabilir grafik hedeflerine uygulanan çözünürlük ölçeği", + "ResolutionScaleEntryTooltip": "Küsüratlı çözünürlük ölçeği, 1.5 gibi. Küsüratlı ölçekler hata oluşturmaya ve çökmeye daha yatkındır.", + "AnisotropyTooltip": "Eşyönsüz doku süzmesi seviyesi (Oyun tarafından istenen değeri kullanmak için Otomatik seçeneğine ayarlayın)", + "AspectRatioTooltip": "Grafik penceresine uygulanan en-boy oranı.", + "ShaderDumpPathTooltip": "Grafik Shader Döküm Yolu", + "FileLogTooltip": "Konsol loglarını diskte bir log dosyasına kaydeder. Performansı etkilemez.", + "StubLogTooltip": "Stub log mesajlarını konsola yazdırır. Performansı etkilemez.", + "InfoLogTooltip": "Bilgi log mesajlarını konsola yazdırır. Performansı etkilemez.", + "WarnLogTooltip": "Uyarı log mesajlarını konsola yazdırır. Performansı etkilemez.", + "ErrorLogTooltip": "Hata log mesajlarını konsola yazdırır. Performansı etkilemez.", + "TraceLogTooltip": "Trace log mesajlarını konsola yazdırır. Performansı etkilemez.", + "GuestLogTooltip": "Guest log mesajlarını konsola yazdırır. Performansı etkilemez.", + "FileAccessLogTooltip": "Dosya sistemi erişim log mesajlarını konsola yazdırır.", + "FSAccessLogModeTooltip": "Konsola FS erişim loglarının yazılmasını etkinleştirir. Kullanılabilir modlar 0-3'tür", + "DeveloperOptionTooltip": "Dikkatli kullanın", + "OpenGlLogLevel": "Uygun log seviyesinin aktif olmasını gerektirir", + "DebugLogTooltip": "Debug log mesajlarını konsola yazdırır.\n\nBu seçeneği yalnızca geliştirici üyemiz belirtirse aktifleştirin, çünkü bu seçenek log dosyasını okumayı zorlaştırır ve emülatörün performansını düşürür.", + "LoadApplicationFileTooltip": "Switch ile uyumlu bir dosya yüklemek için dosya tarayıcısını açar", + "LoadApplicationFolderTooltip": "Switch ile uyumlu ayrıştırılmamış bir uygulama yüklemek için dosya tarayıcısını açar", + "OpenRyujinxFolderTooltip": "Ryujinx dosya sistem klasörünü açar", + "OpenRyujinxLogsTooltip": "Log dosyalarının bulunduğu klasörü açar", + "ExitTooltip": "Ryujinx'ten çıkış yapmayı sağlar", + "OpenSettingsTooltip": "Seçenekler penceresini açar", + "OpenProfileManagerTooltip": "Kullanıcı profil yöneticisi penceresini açar", + "StopEmulationTooltip": "Oynanmakta olan oyunun emülasyonunu durdurup oyun seçimine geri döndürür", + "CheckUpdatesTooltip": "Ryujinx güncellemelerini denetlemeyi sağlar", + "OpenAboutTooltip": "Hakkında penceresini açar", + "GridSize": "Öge Boyutu", + "GridSizeTooltip": "Grid ögelerinin boyutunu değiştirmeyi sağlar", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brezilya Portekizcesi", + "AboutRyujinxContributorsButtonHeader": "Tüm katkıda bulunanları gör", + "SettingsTabSystemAudioVolume": "Ses Seviyesi: ", + "AudioVolumeTooltip": "Ses seviyesini değiştirir", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Erişimi/LAN Modu", + "EnableInternetAccessTooltip": "Emüle edilen uygulamanın internete bağlanmasını sağlar.\n\nLAN modu bulunan oyunlar bu seçenek ile birbirine bağlanabilir ve sistemler aynı access point'e bağlanır. Bu gerçek konsolları da kapsar.\n\nNintendo sunucularına bağlanmayı sağlaMAZ. Internete bağlanmaya çalışan baz oyunların çökmesine sebep olabilr.\n\nEmin değilseniz devre dışı bırakın.", + "GameListContextMenuManageCheatToolTip": "Hileleri yönetmeyi sağlar", + "GameListContextMenuManageCheat": "Hileleri Yönet", + "ControllerSettingsStickRange": "Menzil:", + "DialogStopEmulationTitle": "Ryujinx - Emülasyonu Durdur", + "DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?", + "SettingsTabCpu": "İşlemci", + "SettingsTabAudio": "Ses", + "SettingsTabNetwork": "Ağ", + "SettingsTabNetworkConnection": "Ağ Bağlantısı", + "SettingsTabCpuCache": "İşlemci Belleği", + "SettingsTabCpuMemory": "CPU Hafızası", + "DialogUpdaterFlatpakNotSupportedMessage": "Lütfen Ryujinx'i FlatHub aracılığıyla güncelleyin.", + "UpdaterDisabledWarningTitle": "Güncelleyici Devre Dışı!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mod Dizini", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Uygulama Modlarını içeren alternatif SD kart Atmosfer dizinini açar. Gerçek donanım için paketlenmiş modlar için kullanışlıdır.", + "ControllerSettingsRotate90": "Saat yönünde 90° Döndür", + "IconSize": "Ikon Boyutu", + "IconSizeTooltip": "Oyun ikonlarının boyutunu değiştirmeyi sağlar", + "MenuBarOptionsShowConsole": "Konsol'u Göster", + "ShaderCachePurgeError": "Belirtilen shader cache temizlenirken hata {0}: {1}", + "UserErrorNoKeys": "Keys bulunamadı", + "UserErrorNoFirmware": "Firmware bulunamadı", + "UserErrorFirmwareParsingFailed": "Firmware çözümleme hatası", + "UserErrorApplicationNotFound": "Uygulama bulunamadı", + "UserErrorUnknown": "Bilinmeyen hata", + "UserErrorUndefined": "Tanımlanmayan hata", + "UserErrorNoKeysDescription": "Ryujinx 'prod.keys' dosyasını bulamadı", + "UserErrorNoFirmwareDescription": "Ryujinx yüklü herhangi firmware bulamadı", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx temin edilen firmware'i çözümleyemedi. Bu durum genellikle güncel olmayan keys'den kaynaklanır.", + "UserErrorApplicationNotFoundDescription": "Ryujinx belirtilen yolda geçerli bir uygulama bulamadı.", + "UserErrorUnknownDescription": "Bilinmeyen bir hata oluştu!", + "UserErrorUndefinedDescription": "Tanımlanmayan bir hata oluştu! Bu durum ile karşılaşılmamalıydı, lütfen bir geliştirici ile iletişime geçin!", + "OpenSetupGuideMessage": "Kurulum Kılavuzunu Aç", + "NoUpdate": "Güncelleme Yok", + "TitleUpdateVersionLabel": "Sürüm {0} - {1}", + "RyujinxInfo": "Ryujinx - Bilgi", + "RyujinxConfirm": "Ryujinx - Doğrulama", + "FileDialogAllTypes": "Tüm türler", + "Never": "Hiçbir Zaman", + "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı", + "SwkbdMinRangeCharacters": "{0}-{1} karakter uzunluğunda olmalı", + "SoftwareKeyboard": "Yazılım Klavyesi", + "SoftwareKeyboardModeNumbersOnly": "Sadece Numara Olabilir", + "SoftwareKeyboardModeAlphabet": "Sadece CJK-characters olmayan karakterler olabilir", + "SoftwareKeyboardModeASCII": "Sadece ASCII karakterler olabilir", + "DialogControllerAppletMessagePlayerRange": "Uygulama belirtilen türde {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", + "DialogControllerAppletMessage": "Uygulama belirtilen türde tam olarak {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", + "DialogControllerAppletDockModeSet": "Docked mode etkin. Handheld geçersiz.\n\n", + "UpdaterRenaming": "Eski dosyalar yeniden adlandırılıyor...", + "UpdaterRenameFailed": "Güncelleyici belirtilen dosyayı yeniden adlandıramadı: {0}", + "UpdaterAddingFiles": "Yeni Dosyalar Ekleniyor...", + "UpdaterExtracting": "Güncelleme Ayrıştırılıyor...", + "UpdaterDownloading": "Güncelleme İndiriliyor...", + "Game": "Oyun", + "Docked": "Yerleştirildi", + "Handheld": "El tipi", + "ConnectionError": "Bağlantı Hatası.", + "AboutPageDeveloperListMore": "{0} ve daha fazla...", + "ApiError": "API Hatası.", + "LoadingHeading": "{0} Yükleniyor", + "CompilingPPTC": "PTC Derleniyor", + "CompilingShaders": "Shaderlar Derleniyor", + "AllKeyboards": "Tüm Klavyeler", + "OpenFileDialogTitle": "Açmak için desteklenen bir dosya seçin", + "OpenFolderDialogTitle": "Ayrıştırılmamış oyun içeren bir klasör seçin", + "AllSupportedFormats": "Tüm Desteklenen Formatlar", + "RyujinxUpdater": "Ryujinx Güncelleyicisi", + "SettingsTabHotkeys": "Klavye Kısayolları", + "SettingsTabHotkeysHotkeys": "Klavye Kısayolları", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync'i Etkinleştir/Devre Dışı Bırak:", + "SettingsTabHotkeysScreenshotHotkey": "Ekran Görüntüsü Al:", + "SettingsTabHotkeysShowUiHotkey": "Arayüzü Göster:", + "SettingsTabHotkeysPauseHotkey": "Durdur:", + "SettingsTabHotkeysToggleMuteHotkey": "Sustur:", + "ControllerMotionTitle": "Hareket Kontrol Seçenekleri", + "ControllerRumbleTitle": "Titreşim Seçenekleri", + "SettingsSelectThemeFileDialogTitle": "Tema Dosyası Seç", + "SettingsXamlThemeFile": "Xaml Tema Dosyası", + "AvatarWindowTitle": "Hesapları Yönet - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Bilinmeyen", + "Usage": "Kullanım", + "Writable": "Yazılabilir", + "SelectDlcDialogTitle": "DLC dosyalarını seç", + "SelectUpdateDialogTitle": "Güncelleme dosyalarını seç", + "UserProfileWindowTitle": "Kullanıcı Profillerini Yönet", + "CheatWindowTitle": "Oyun Hilelerini Yönet", + "DlcWindowTitle": "Oyun DLC'lerini Yönet", + "UpdateWindowTitle": "Oyun Güncellemelerini Yönet", + "CheatWindowHeading": "{0} için Hile mevcut [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} için DLC mevcut [{1}]", + "UserProfilesEditProfile": "Seçiliyi Düzenle", + "Cancel": "İptal", + "Save": "Kaydet", + "Discard": "Iskarta", + "UserProfilesSetProfileImage": "Profil Resmi Ayarla", + "UserProfileEmptyNameError": "İsim gerekli", + "UserProfileNoImageError": "Profil resmi ayarlanmalıdır", + "GameUpdateWindowHeading": "{0} için güncellemeler mevcut [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Çözünürlüğü artır:", + "SettingsTabHotkeysResScaleDownHotkey": "Çözünürlüğü azalt:", + "UserProfilesName": "İsim:", + "UserProfilesUserId": "Kullanıcı Adı:", + "SettingsTabGraphicsBackend": "Grafik Arka Ucu", + "SettingsTabGraphicsBackendTooltip": "Kullanılacak Grafik Arka Uç", + "SettingsEnableTextureRecompression": "Yeniden Doku Sıkıştırılmasını Aktif Et", + "SettingsEnableTextureRecompressionTooltip": "4GB VRAM'in Altında Sistemler için önerilir.\n\nEmin değilseniz kapalı bırakın", + "SettingsTabGraphicsPreferredGpu": "Kullanılan GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan Grafik Arka Ucu ile kullanılacak Ekran Kartını Seçin.\n\nOpenGL'nin kullanacağı GPU'yu etkilemez.\n\n Emin değilseniz \"dGPU\" olarak işaretlenmiş GPU'ya ayarlayın. Eğer yoksa, dokunmadan bırakın.\n", + "SettingsAppRequiredRestartMessage": "Ryujinx'i Yeniden Başlatma Gerekli", + "SettingsGpuBackendRestartMessage": "Grafik Motoru ya da GPU ayarları değiştirildi. Bu işlemin uygulanması için yeniden başlatma gerekli.", + "SettingsGpuBackendRestartSubMessage": "Şimdi yeniden başlatmak istiyor musunuz?", + "RyujinxUpdaterMessage": "Ryujinx'i en son sürüme güncellemek ister misiniz?", + "SettingsTabHotkeysVolumeUpHotkey": "Sesi Arttır:", + "SettingsTabHotkeysVolumeDownHotkey": "Sesi Azalt:", + "SettingsEnableMacroHLE": "Macro HLE'yi Aktifleştir", + "SettingsEnableMacroHLETooltip": "GPU Macro kodunun yüksek seviye emülasyonu.\n\nPerformansı arttırır, ama bazı oyunlarda grafik hatalarına yol açabilir.\n\nEmin değilseniz AÇIK bırakın.", + "SettingsEnableColorSpacePassthrough": "Renk Alanı Geçişi", + "SettingsEnableColorSpacePassthroughTooltip": "Vulkan Backend'ini renk alanı belirtmeden renk bilgisinden geçmeye yönlendirir. Geniş gam ekranlı kullanıcılar için bu, renk doğruluğu pahasına daha canlı renklerle sonuçlanabilir.", + "VolumeShort": "Ses", + "UserProfilesManageSaves": "Kayıtları Yönet", + "DeleteUserSave": "Bu oyun için kullanıcı kaydını silmek istiyor musunuz?", + "IrreversibleActionNote": "Bu eylem geri alınamaz.", + "SaveManagerHeading": "{0} için Kayıt Dosyalarını Yönet", + "SaveManagerTitle": "Kayıt Yöneticisi", + "Name": "İsim", + "Size": "Boyut", + "Search": "Ara", + "UserProfilesRecoverLostAccounts": "Kayıp Hesapları Kurtar", + "Recover": "Kurtar", + "UserProfilesRecoverHeading": "Aşağıdaki hesaplar için kayıtlar bulundu", + "UserProfilesRecoverEmptyList": "Kurtarılacak profil bulunamadı", + "GraphicsAATooltip": "Oyuna Kenar Yumuşatma Ekler", + "GraphicsAALabel": "Kenar Yumuşatma:", + "GraphicsScalingFilterLabel": "Ölçekleme Filtresi:", + "GraphicsScalingFilterTooltip": "Çerçeve Arabellek Filtresini Açar", + "GraphicsScalingFilterLevelLabel": "Seviye", + "GraphicsScalingFilterLevelTooltip": "Ölçekleme Filtre Seviyesini Belirle", + "SmaaLow": "Düşük SMAA", + "SmaaMedium": "Orta SMAA", + "SmaaHigh": "Yüksek SMAA", + "SmaaUltra": "En Yüksek SMAA", + "UserEditorTitle": "Kullanıcıyı Düzenle", + "UserEditorTitleCreate": "Kullanıcı Oluştur", + "SettingsTabNetworkInterface": "Ağ Bağlantısı:", + "NetworkInterfaceTooltip": "LAN özellikleri için kullanılan ağ bağlantısı", + "NetworkInterfaceDefault": "Varsayılan", + "PackagingShaders": "Gölgeler Paketleniyor", + "AboutChangelogButton": "GitHub'da Değişiklikleri Görüntüle", + "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın" +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/uk_UA.json b/src/Ryujinx.Ava/Assets/Locales/uk_UA.json new file mode 100644 index 000000000..a119fb4b7 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/uk_UA.json @@ -0,0 +1,656 @@ +{ + "Language": "Yкраїнська", + "MenuBarFileOpenApplet": "Відкрити аплет", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрийте аплет Mii Editor в автономному режимі", + "SettingsTabInputDirectMouseAccess": "Прямий доступ мишею", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам'яті:", + "SettingsTabSystemMemoryManagerModeSoftware": "Програмне забезпечення", + "SettingsTabSystemMemoryManagerModeHost": "Хост (швидко)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Неперевірений хост (найшвидший, небезпечний)", + "SettingsTabSystemUseHypervisor": "Use Hypervisor", + "MenuBarFile": "_Файл", + "MenuBarFileOpenFromFile": "_Завантажити програму з файлу", + "MenuBarFileOpenUnpacked": "Завантажити _розпаковану гру", + "MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx", + "MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін", + "MenuBarFileExit": "_Вихід", + "MenuBarOptions": "Опції", + "MenuBarOptionsToggleFullscreen": "Перемкнути на весь екран", + "MenuBarOptionsStartGamesInFullscreen": "Запускати ігри на весь екран", + "MenuBarOptionsStopEmulation": "Зупинити емуляцію", + "MenuBarOptionsSettings": "_Налаштування", + "MenuBarOptionsManageUserProfiles": "_Керування профілями користувачів", + "MenuBarActions": "_Дії", + "MenuBarOptionsSimulateWakeUpMessage": "Симулювати повідомлення про пробудження", + "MenuBarActionsScanAmiibo": "Сканувати Amiibo", + "MenuBarTools": "_Інструменти", + "MenuBarToolsInstallFirmware": "Встановити прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Встановити прошивку з XCI або ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Встановити прошивку з теки", + "MenuBarToolsManageFileTypes": "Manage file types", + "MenuBarToolsInstallFileTypes": "Install file types", + "MenuBarToolsUninstallFileTypes": "Uninstall file types", + "MenuBarHelp": "Довідка", + "MenuBarHelpCheckForUpdates": "Перевірити оновлення", + "MenuBarHelpAbout": "Про програму", + "MenuSearch": "Пошук...", + "GameListHeaderFavorite": "Вибране", + "GameListHeaderIcon": "Значок", + "GameListHeaderApplication": "Назва", + "GameListHeaderDeveloper": "Розробник", + "GameListHeaderVersion": "Версія", + "GameListHeaderTimePlayed": "Зіграно часу", + "GameListHeaderLastPlayed": "Остання гра", + "GameListHeaderFileExtension": "Розширення файлу", + "GameListHeaderFileSize": "Розмір файлу", + "GameListHeaderPath": "Шлях", + "GameListContextMenuOpenUserSaveDirectory": "Відкрити каталог збереження користувача", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Відкриває каталог, який містить збереження користувача програми", + "GameListContextMenuOpenDeviceSaveDirectory": "Відкрити каталог пристроїв користувача", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Відкриває каталог, який містить збереження пристрою програми", + "GameListContextMenuOpenBcatSaveDirectory": "Відкрити каталог користувача BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Відкриває каталог, який містить BCAT-збереження програми", + "GameListContextMenuManageTitleUpdates": "Керування оновленнями заголовків", + "GameListContextMenuManageTitleUpdatesToolTip": "Відкриває вікно керування оновленням заголовка", + "GameListContextMenuManageDlc": "Керування DLC", + "GameListContextMenuManageDlcToolTip": "Відкриває вікно керування DLC", + "GameListContextMenuOpenModsDirectory": "Відкрити каталог модифікацій", + "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації програм", + "GameListContextMenuCacheManagement": "Керування кешем", + "GameListContextMenuCacheManagementPurgePptc": "Очистити кеш PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Видаляє кеш PPTC програми", + "GameListContextMenuCacheManagementPurgeShaderCache": "Очистити кеш шейдерів", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Видаляє кеш шейдерів програми", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Відкрити каталог PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Відкриває каталог, який містить кеш PPTC програми", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Відкрити каталог кешу шейдерів", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Відкриває каталог, який містить кеш шейдерів програми", + "GameListContextMenuExtractData": "Видобути дані", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Видобуває розділ ExeFS із поточної конфігурації програми (включаючи оновлення)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Видобуває розділ RomFS із поточної конфігурації програми (включаючи оновлення)", + "GameListContextMenuExtractDataLogo": "Логотип", + "GameListContextMenuExtractDataLogoToolTip": "Видобуває розділ логотипу з поточної конфігурації програми (включаючи оновлення)", + "StatusBarGamesLoaded": "{0}/{1} Ігор завантажено", + "StatusBarSystemVersion": "Версія системи: {0}", + "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", + "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", + "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", + "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "Settings": "Налаштування", + "SettingsTabGeneral": "Інтерфейс користувача", + "SettingsTabGeneralGeneral": "Загальні", + "SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску", + "SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».", + "SettingsTabGeneralHideCursor": "Hide Cursor:", + "SettingsTabGeneralHideCursorNever": "Never", + "SettingsTabGeneralHideCursorOnIdle": "Приховати курсор у режимі очікування", + "SettingsTabGeneralHideCursorAlways": "Always", + "SettingsTabGeneralGameDirectories": "Каталоги ігор", + "SettingsTabGeneralAdd": "Додати", + "SettingsTabGeneralRemove": "Видалити", + "SettingsTabSystem": "Система", + "SettingsTabSystemCore": "Ядро", + "SettingsTabSystemSystemRegion": "Регіон системи:", + "SettingsTabSystemSystemRegionJapan": "Японія", + "SettingsTabSystemSystemRegionUSA": "США", + "SettingsTabSystemSystemRegionEurope": "Європа", + "SettingsTabSystemSystemRegionAustralia": "Австралія", + "SettingsTabSystemSystemRegionChina": "Китай", + "SettingsTabSystemSystemRegionKorea": "Корея", + "SettingsTabSystemSystemRegionTaiwan": "Тайвань", + "SettingsTabSystemSystemLanguage": "Мова системи:", + "SettingsTabSystemSystemLanguageJapanese": "Японська", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Англійська (США)", + "SettingsTabSystemSystemLanguageFrench": "Французька", + "SettingsTabSystemSystemLanguageGerman": "Німецька", + "SettingsTabSystemSystemLanguageItalian": "Італійська", + "SettingsTabSystemSystemLanguageSpanish": "Іспанська", + "SettingsTabSystemSystemLanguageChinese": "Китайська", + "SettingsTabSystemSystemLanguageKorean": "Корейська", + "SettingsTabSystemSystemLanguageDutch": "Нідерландська", + "SettingsTabSystemSystemLanguagePortuguese": "Португальська", + "SettingsTabSystemSystemLanguageRussian": "Російська", + "SettingsTabSystemSystemLanguageTaiwanese": "Тайванська", + "SettingsTabSystemSystemLanguageBritishEnglish": "Англійська (Великобританія)", + "SettingsTabSystemSystemLanguageCanadianFrench": "Французька (Канада)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Іспанська (Латиноамериканська)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Спрощена китайська", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Традиційна китайська", + "SettingsTabSystemSystemTimeZone": "Часовий пояс системи:", + "SettingsTabSystemSystemTime": "Час системи:", + "SettingsTabSystemEnableVsync": "Вертикальна синхронізація", + "SettingsTabSystemEnablePptc": "PPTC (профільований постійний кеш перекладу)", + "SettingsTabSystemEnableFsIntegrityChecks": "Перевірка цілісності FS", + "SettingsTabSystemAudioBackend": "Аудіосистема:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Хитрощі", + "SettingsTabSystemHacksNote": " (може викликати нестабільність)", + "SettingsTabSystemExpandDramSize": "Використовувати альтернативне розташування пам'яті (розробники)", + "SettingsTabSystemIgnoreMissingServices": "Ігнорувати відсутні служби", + "SettingsTabGraphics": "Графіка", + "SettingsTabGraphicsAPI": "Графічний API", + "SettingsTabGraphicsEnableShaderCache": "Увімкнути кеш шейдерів", + "SettingsTabGraphicsAnisotropicFiltering": "Анізотропна фільтрація:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Авто", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Роздільна здатність:", + "SettingsTabGraphicsResolutionScaleCustom": "Користувацька (не рекомендовано)", + "SettingsTabGraphicsResolutionScaleNative": "Стандартний (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Співвідношення сторін:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Розтягнути до розміру вікна", + "SettingsTabGraphicsDeveloperOptions": "Налаштування виробника", + "SettingsTabGraphicsShaderDumpPath": "Шлях скидання графічного шейдера:", + "SettingsTabLogging": "Налагодження", + "SettingsTabLoggingLogging": "Налагодження", + "SettingsTabLoggingEnableLoggingToFile": "Увімкнути налагодження у файл", + "SettingsTabLoggingEnableStubLogs": "Увімкнути журнали заглушки", + "SettingsTabLoggingEnableInfoLogs": "Увімкнути інформаційні журнали", + "SettingsTabLoggingEnableWarningLogs": "Увімкнути журнали попереджень", + "SettingsTabLoggingEnableErrorLogs": "Увімкнути журнали помилок", + "SettingsTabLoggingEnableTraceLogs": "Увімкнути журнали трасування", + "SettingsTabLoggingEnableGuestLogs": "Увімкнути журнали гостей", + "SettingsTabLoggingEnableFsAccessLogs": "Увімкнути журнали доступу Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журналу глобального доступу Fs:", + "SettingsTabLoggingDeveloperOptions": "Параметри розробника (УВАГА: знизиться продуктивність)", + "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", + "SettingsTabLoggingGraphicsBackendLogLevel": "Рівень журналу графічного сервера:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ні", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Помилка", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Уповільнення", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Все", + "SettingsTabLoggingEnableDebugLogs": "Увімкнути журнали налагодження", + "SettingsTabInput": "Введення", + "SettingsTabInputEnableDockedMode": "Режим док-станції", + "SettingsTabInputDirectKeyboardAccess": "Прямий доступ з клавіатури", + "SettingsButtonSave": "Зберегти", + "SettingsButtonClose": "Закрити", + "SettingsButtonOk": "Гаразд", + "SettingsButtonCancel": "Скасувати", + "SettingsButtonApply": "Застосувати", + "ControllerSettingsPlayer": "Гравець", + "ControllerSettingsPlayer1": "Гравець 1", + "ControllerSettingsPlayer2": "Гравець 2", + "ControllerSettingsPlayer3": "Гравець 3", + "ControllerSettingsPlayer4": "Гравець 4", + "ControllerSettingsPlayer5": "Гравець 5", + "ControllerSettingsPlayer6": "Гравець 6", + "ControllerSettingsPlayer7": "Гравець 7", + "ControllerSettingsPlayer8": "Гравець 8", + "ControllerSettingsHandheld": "Портативний", + "ControllerSettingsInputDevice": "Пристрій введення", + "ControllerSettingsRefresh": "Оновити", + "ControllerSettingsDeviceDisabled": "Вимкнено", + "ControllerSettingsControllerType": "Тип контролера", + "ControllerSettingsControllerTypeHandheld": "Портативний", + "ControllerSettingsControllerTypeProController": "Контролер Pro", + "ControllerSettingsControllerTypeJoyConPair": "Обидва JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "Лівий JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Правий JoyCon", + "ControllerSettingsProfile": "Профіль", + "ControllerSettingsProfileDefault": "Типовий", + "ControllerSettingsLoad": "Завантажити", + "ControllerSettingsAdd": "Додати", + "ControllerSettingsRemove": "Видалити", + "ControllerSettingsButtons": "Кнопки", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Панель направлення", + "ControllerSettingsDPadUp": "Вгору", + "ControllerSettingsDPadDown": "Вниз", + "ControllerSettingsDPadLeft": "Вліво", + "ControllerSettingsDPadRight": "Вправо", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Up", + "ControllerSettingsStickDown": "Down", + "ControllerSettingsStickLeft": "Left", + "ControllerSettingsStickRight": "Right", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Invert Stick X", + "ControllerSettingsStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Лівий джойстик", + "ControllerSettingsRStick": "Правий джойстик", + "ControllerSettingsTriggersLeft": "Тригери ліворуч", + "ControllerSettingsTriggersRight": "Тригери праворуч", + "ControllerSettingsTriggersButtonsLeft": "Кнопки тригерів ліворуч", + "ControllerSettingsTriggersButtonsRight": "Кнопки тригерів праворуч", + "ControllerSettingsTriggers": "Тригери", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Кнопки ліворуч", + "ControllerSettingsExtraButtonsRight": "Кнопки праворуч", + "ControllerSettingsMisc": "Різне", + "ControllerSettingsTriggerThreshold": "Поріг спрацьовування:", + "ControllerSettingsMotion": "Рух", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Використовувати рух, сумісний з CemuHook", + "ControllerSettingsMotionControllerSlot": "Слот контролера:", + "ControllerSettingsMotionMirrorInput": "Дзеркальний вхід", + "ControllerSettingsMotionRightJoyConSlot": "Правий слот JoyCon:", + "ControllerSettingsMotionServerHost": "Хост сервера:", + "ControllerSettingsMotionGyroSensitivity": "Чутливість гіроскопа:", + "ControllerSettingsMotionGyroDeadzone": "Мертва зона гіроскопа:", + "ControllerSettingsSave": "Зберегти", + "ControllerSettingsClose": "Закрити", + "UserProfilesSelectedUserProfile": "Вибраний профіль користувача:", + "UserProfilesSaveProfileName": "Зберегти ім'я профілю", + "UserProfilesChangeProfileImage": "Змінити зображення профілю", + "UserProfilesAvailableUserProfiles": "Доступні профілі користувачів:", + "UserProfilesAddNewProfile": "Створити профіль", + "UserProfilesDelete": "Delete", + "UserProfilesClose": "Закрити", + "ProfileNameSelectionWatermark": "Choose a nickname", + "ProfileImageSelectionTitle": "Вибір зображення профілю", + "ProfileImageSelectionHeader": "Виберіть зображення профілю", + "ProfileImageSelectionNote": "Ви можете імпортувати власне зображення профілю або вибрати аватар із мікропрограми системи", + "ProfileImageSelectionImportImage": "Імпорт файлу зображення", + "ProfileImageSelectionSelectAvatar": "Виберіть аватар прошивки ", + "InputDialogTitle": "Діалог введення", + "InputDialogOk": "Гаразд", + "InputDialogCancel": "Скасувати", + "InputDialogAddNewProfileTitle": "Виберіть ім'я профілю", + "InputDialogAddNewProfileHeader": "Будь ласка, введіть ім'я профілю", + "InputDialogAddNewProfileSubtext": "(Макс. довжина: {0})", + "AvatarChoose": "Вибрати", + "AvatarSetBackgroundColor": "Встановити колір фону", + "AvatarClose": "Закрити", + "ControllerSettingsLoadProfileToolTip": "Завантажити профіль", + "ControllerSettingsAddProfileToolTip": "Додати профіль", + "ControllerSettingsRemoveProfileToolTip": "Видалити профіль", + "ControllerSettingsSaveProfileToolTip": "Зберегти профіль", + "MenuBarFileToolsTakeScreenshot": "Зробити знімок екрана", + "MenuBarFileToolsHideUi": "Сховати інтерфейс", + "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuToggleFavorite": "Перемкнути вибране", + "GameListContextMenuToggleFavoriteToolTip": "Перемкнути улюблений статус гри", + "SettingsTabGeneralTheme": "Тема", + "SettingsTabGeneralThemeCustomTheme": "Користувацький шлях до теми", + "SettingsTabGeneralThemeBaseStyle": "Базовий стиль", + "SettingsTabGeneralThemeBaseStyleDark": "Темна", + "SettingsTabGeneralThemeBaseStyleLight": "Світла", + "SettingsTabGeneralThemeEnableCustomTheme": "Увімкнути користуваьку тему", + "ButtonBrowse": "Огляд", + "ControllerSettingsConfigureGeneral": "Налаштування", + "ControllerSettingsRumble": "Вібрація", + "ControllerSettingsRumbleStrongMultiplier": "Множник сильної вібрації", + "ControllerSettingsRumbleWeakMultiplier": "Множник слабкої вібрації", + "DialogMessageSaveNotAvailableMessage": "Немає збережених даних для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Хочете створити дані збереження для цієї гри?", + "DialogConfirmationTitle": "Ryujinx - Підтвердження", + "DialogUpdaterTitle": "Ryujinx - Програма оновлення", + "DialogErrorTitle": "Ryujinx - Помилка", + "DialogWarningTitle": "Ryujinx - Попередження", + "DialogExitTitle": "Ryujinx - Вихід", + "DialogErrorMessage": "У Ryujinx сталася помилка", + "DialogExitMessage": "Ви впевнені, що бажаєте закрити Ryujinx?", + "DialogExitSubMessage": "Усі незбережені дані буде втрачено!", + "DialogMessageCreateSaveErrorMessage": "Під час створення вказаних даних збереження сталася помилка: {0}", + "DialogMessageFindSaveErrorMessage": "Під час пошуку вказаних даних збереження сталася помилка: {0}", + "FolderDialogExtractTitle": "Виберіть папку для видобування", + "DialogNcaExtractionMessage": "Видобування розділу {0} з {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Екстрактор розділів NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Помилка видобування. Основний NCA не був присутній у вибраному файлі.", + "DialogNcaExtractionCheckLogErrorMessage": "Помилка видобування. Прочитайте файл журналу для отримання додаткової інформації.", + "DialogNcaExtractionSuccessMessage": "Видобування успішно завершено.", + "DialogUpdaterConvertFailedMessage": "Не вдалося конвертувати поточну версію Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Скасування оновлення!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Ви вже використовуєте останню версію Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Під час спроби отримати інформацію про випуск із GitHub Release сталася помилка. Це може бути спричинено, якщо новий випуск компілюється GitHub Actions. Повторіть спробу через кілька хвилин.", + "DialogUpdaterConvertFailedGithubMessage": "Не вдалося конвертувати отриману версію Ryujinx із випуску Github.", + "DialogUpdaterDownloadingMessage": "Завантаження оновлення...", + "DialogUpdaterExtractionMessage": "Видобування оновлення...", + "DialogUpdaterRenamingMessage": "Перейменування оновлення...", + "DialogUpdaterAddingFilesMessage": "Додавання нового оновлення...", + "DialogUpdaterCompleteMessage": "Оновлення завершено!", + "DialogUpdaterRestartMessage": "Перезапустити Ryujinx зараз?", + "DialogUpdaterArchNotSupportedMessage": "Ви використовуєте не підтримувану архітектуру системи!", + "DialogUpdaterArchNotSupportedSubMessage": "(Підтримуються лише системи x64!)", + "DialogUpdaterNoInternetMessage": "Ви не підключені до Інтернету!", + "DialogUpdaterNoInternetSubMessage": "Будь ласка, переконайтеся, що у вас є робоче підключення до Інтернету!", + "DialogUpdaterDirtyBuildMessage": "Ви не можете оновити брудну збірку Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Будь ласка, завантажте Ryujinx на https://ryujinx.org/, якщо ви шукаєте підтримувану версію.", + "DialogRestartRequiredMessage": "Потрібен перезапуск", + "DialogThemeRestartMessage": "Тему збережено. Щоб застосувати тему, потрібен перезапуск.", + "DialogThemeRestartSubMessage": "Ви хочете перезапустити", + "DialogFirmwareInstallEmbeddedMessage": "Бажаєте встановити прошивку, вбудовану в цю гру? (Прошивка {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\\nТепер запуститься емулятор.", + "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не встановлена", + "DialogFirmwareInstalledMessage": "Встановлено прошивку {0}", + "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", + "DialogInstallFileTypesErrorMessage": "Failed to install file types.", + "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", + "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", + "DialogOpenSettingsWindowLabel": "Відкрити вікно налаштувань", + "DialogControllerAppletTitle": "Аплет контролера", + "DialogMessageDialogErrorExceptionMessage": "Помилка показу діалогового вікна повідомлення: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Помилка показу програмної клавіатури: {0}", + "DialogErrorAppletErrorExceptionMessage": "Помилка показу діалогового вікна ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nДля отримання додаткової інформації про те, як виправити цю помилку, дотримуйтесь нашого посібника з налаштування.", + "DialogUserErrorDialogTitle": "Помилка Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Під час отримання інформації з API сталася помилка.", + "DialogAmiiboApiConnectErrorMessage": "Неможливо підключитися до сервера Amiibo API. Можливо, служба не працює або вам потрібно перевірити, чи є підключення до Інтернету.", + "DialogProfileInvalidProfileErrorMessage": "Профіль {0} несумісний із поточною системою конфігурації вводу.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Стандартний профіль не можна перезаписати", + "DialogProfileDeleteProfileTitle": "Видалення профілю", + "DialogProfileDeleteProfileMessage": "Цю дію неможливо скасувати. Ви впевнені, що бажаєте продовжити?", + "DialogWarning": "Увага", + "DialogPPTCDeletionMessage": "Ви збираєтеся видалити кеш PPTC для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", + "DialogPPTCDeletionErrorMessage": "Помилка очищення кешу PPTC на {0}: {1}", + "DialogShaderDeletionMessage": "Ви збираєтеся видалити кеш шейдерів для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", + "DialogShaderDeletionErrorMessage": "Помилка очищення кешу шейдерів на {0}: {1}", + "DialogRyujinxErrorMessage": "У Ryujinx сталася помилка", + "DialogInvalidTitleIdErrorMessage": "Помилка інтерфейсу: вибрана гра не мала дійсного ідентифікатора назви", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Дійсна прошивка системи не знайдена в {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Встановити прошивку {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Буде встановлено версію системи {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЦе замінить поточну версію системи {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nВи хочете продовжити?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Встановлення прошивки...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версію системи {0} успішно встановлено.", + "DialogUserProfileDeletionWarningMessage": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться", + "DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль", + "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", + "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", + "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", + "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", + "DialogLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}", + "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", + "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Ви увімкнули скидання шейдерів, призначений лише для розробників.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути скидання шейдерів. Ви хочете вимкнути скидання шейдерів зараз?", + "DialogLoadAppGameAlreadyLoadedMessage": "Гру вже завантажено", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Зупиніть емуляцію або закрийте емулятор перед запуском іншої гри.", + "DialogUpdateAddUpdateErrorMessage": "Зазначений файл не містить оновлення для вибраного заголовка!", + "DialogSettingsBackendThreadingWarningTitle": "Попередження - потокове керування сервером", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx потрібно перезапустити після зміни цього параметра, щоб він застосовувався повністю. Залежно від вашої платформи вам може знадобитися вручну вимкнути власну багатопотоковість драйвера під час використання Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Особливості", + "SettingsTabGraphicsBackendMultithreading": "Багатопотоковість графічного сервера:", + "CommonAuto": "Авто", + "CommonOff": "Вимкнути", + "CommonOn": "Увімкнути", + "InputDialogYes": "Так", + "InputDialogNo": "Ні", + "DialogProfileInvalidProfileNameErrorMessage": "Ім'я файлу містить неприпустимі символи. Будь ласка, спробуйте ще раз.", + "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsResumeEmulation": "Продовжити", + "AboutUrlTooltipMessage": "Натисніть, щоб відкрити сайт Ryujinx у браузері за замовчування.", + "AboutDisclaimerMessage": "Ryujinx жодним чином не пов’язано з Nintendo™,\nчи будь-яким із їхніх партнерів.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) використовується в нашій емуляції Amiibo.", + "AboutPatreonUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Patreon Ryujinx у вашому браузері за замовчування.", + "AboutGithubUrlTooltipMessage": "Натисніть, щоб відкрити сторінку GitHub Ryujinx у браузері за замовчуванням.", + "AboutDiscordUrlTooltipMessage": "Натисніть, щоб відкрити запрошення на сервер Discord Ryujinx у браузері за замовчуванням.", + "AboutTwitterUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Twitter Ryujinx у браузері за замовчуванням.", + "AboutRyujinxAboutTitle": "Про програму:", + "AboutRyujinxAboutContent": "Ryujinx — це емулятор для Nintendo Switch™.\nБудь ласка, підтримайте нас на Patreon.\nОтримуйте всі останні новини в нашому Twitter або Discord.\nРозробники, які хочуть зробити внесок, можуть дізнатися більше на нашому GitHub або в Discord.", + "AboutRyujinxMaintainersTitle": "Підтримується:", + "AboutRyujinxMaintainersContentTooltipMessage": "Натисніть, щоб відкрити сторінку співавторів у вашому браузері за замовчування.", + "AboutRyujinxSupprtersTitle": "Підтримується на Patreon:", + "AmiiboSeriesLabel": "Серія Amiibo", + "AmiiboCharacterLabel": "Персонаж", + "AmiiboScanButtonLabel": "Сканувати", + "AmiiboOptionsShowAllLabel": "Показати всі Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Хитрість: Використовувати випадковий тег Uuid", + "DlcManagerTableHeadingEnabledLabel": "Увімкнено", + "DlcManagerTableHeadingTitleIdLabel": "ID заголовка", + "DlcManagerTableHeadingContainerPathLabel": "Шлях до контейнеру", + "DlcManagerTableHeadingFullPathLabel": "Повний шлях", + "DlcManagerRemoveAllButton": "Видалити все", + "DlcManagerEnableAllButton": "Увімкнути всі", + "DlcManagerDisableAllButton": "Вимкнути всі", + "MenuBarOptionsChangeLanguage": "Змінити мову", + "MenuBarShowFileTypes": "Show File Types", + "CommonSort": "Сортувати", + "CommonShowNames": "Показати назви", + "CommonFavorite": "Вибрані", + "OrderAscending": "За зростанням", + "OrderDescending": "За спаданням", + "SettingsTabGraphicsFeatures": "Функції та вдосконалення", + "ErrorWindowTitle": "Вікно помилок", + "ToggleDiscordTooltip": "Виберіть, чи відображати Ryujinx у вашій «поточній грі» в Discord", + "AddGameDirBoxTooltip": "Введіть каталог ігор, щоб додати до списку", + "AddGameDirTooltip": "Додати каталог гри до списку", + "RemoveGameDirTooltip": "Видалити вибраний каталог гри", + "CustomThemeCheckTooltip": "Використовуйте користувацьку тему Avalonia для графічного інтерфейсу, щоб змінити вигляд меню емулятора", + "CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу", + "CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу", + "DockModeToggleTooltip": "У режимі док-станції емульована система веде себе як приєднаний Nintendo Switch. Це покращує точність графіки в більшості ігор. І навпаки, вимкнення цього призведе до того, що емульована система поводитиметься як портативний комутатор Nintendo, погіршуючи якість графіки.\n\nНалаштуйте елементи керування для гравця 1, якщо плануєте використовувати режим док-станції; налаштуйте ручні елементи керування, якщо плануєте використовувати портативний режим.\n\nЗалиште увімкненим, якщо не впевнені.", + "DirectKeyboardTooltip": "Підтримка прямого доступу з клавіатури (HID). Надає іграм доступ до клавіатури як пристрою для введення тексту.", + "DirectMouseTooltip": "Підтримка прямого доступу миші (HID). Надає іграм доступ до миші як вказівного пристрою.", + "RegionTooltip": "Змінити регіон системи", + "LanguageTooltip": "Змінити мову системи", + "TimezoneTooltip": "Змінити часовий пояс системи", + "TimeTooltip": "Змінити час системи", + "VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею за вашим бажанням. Ми рекомендуємо зробити це, якщо ви плануєте вимкнути його.\n\nЗалиште увімкненим, якщо не впевнені.", + "PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.", + "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", + "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", + "MemoryManagerTooltip": "Змінює спосіб відображення та доступу до гостьової пам’яті. Значно впливає на продуктивність емульованого ЦП.\n\nВстановіть «Неперевірений хост», якщо не впевнені.", + "MemoryManagerSoftwareTooltip": "Використовує програмну таблицю сторінок для перекладу адрес. Найвища точність, але найповільніша продуктивність.", + "MemoryManagerHostTooltip": "Пряме відображення пам'яті в адресному просторі хосту. Набагато швидша компіляція та виконання JIT.", + "MemoryManagerUnsafeTooltip": "Пряме відображення пам’яті, але не маскує адресу в гостьовому адресному просторі перед доступом. Швидше, але ціною безпеки. Гостьова програма може отримати доступ до пам’яті з будь-якого місця в Ryujinx, тому запускайте в цьому режимі лише програми, яким ви довіряєте.", + "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "DRamTooltip": "Використовує альтернативний макет MemoryMode для імітації моделі розробки Switch.\n\nЦе корисно лише для пакетів текстур з вищою роздільною здатністю або модифікацій із роздільною здатністю 4K. НЕ покращує продуктивність.\n\nЗалиште вимкненим, якщо не впевнені.", + "IgnoreMissingServicesTooltip": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим, якщо не впевнені.", + "GraphicsBackendThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені", + "GalThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.", + "ShaderCacheToggleTooltip": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.", + "ResolutionScaleTooltip": "Масштаб роздільної здатності, застосована до відповідних цілей візуалізації", + "ResolutionScaleEntryTooltip": "Масштаб роздільної здатності з плаваючою комою, наприклад 1,5. Не інтегральні масштаби, швидше за все, спричинять проблеми або збій.", + "AnisotropyTooltip": "Рівень анізотропної фільтрації (встановіть на «Авто», щоб використовувати значення, яке вимагає гра)", + "AspectRatioTooltip": "Співвідношення сторін, застосоване до вікна візуалізації.", + "ShaderDumpPathTooltip": "Шлях скидання графічних шейдерів", + "FileLogTooltip": "Зберігає журнал консолі у файл журналу на диску. Не впливає на продуктивність.", + "StubLogTooltip": "Друкує повідомлення журналу-заглушки на консолі. Не впливає на продуктивність.", + "InfoLogTooltip": "Друкує повідомлення інформаційного журналу на консолі. Не впливає на продуктивність.", + "WarnLogTooltip": "Друкує повідомлення журналу попереджень у консолі. Не впливає на продуктивність.", + "ErrorLogTooltip": "Друкує повідомлення журналу помилок у консолі. Не впливає на продуктивність.", + "TraceLogTooltip": "Друкує повідомлення журналу трасування на консолі. Не впливає на продуктивність.", + "GuestLogTooltip": "Друкує повідомлення журналу гостей у консолі. Не впливає на продуктивність.", + "FileAccessLogTooltip": "Друкує повідомлення журналу доступу до файлів у консолі.", + "FSAccessLogModeTooltip": "Вмикає виведення журналу доступу до FS на консоль. Можливі режими 0-3", + "DeveloperOptionTooltip": "Використовуйте з обережністю", + "OpenGlLogLevel": "Потрібно увімкнути відповідні рівні журналу", + "DebugLogTooltip": "Друкує повідомлення журналу налагодження на консолі.\n\nВикористовуйте це лише за спеціальною вказівкою співробітника, оскільки це ускладнить читання журналів і погіршить роботу емулятора.", + "LoadApplicationFileTooltip": "Відкриває файловий провідник, щоб вибрати для завантаження сумісний файл Switch", + "LoadApplicationFolderTooltip": "Відкриває файловий провідник, щоб вибрати сумісну з комутатором розпаковану програму для завантаження", + "OpenRyujinxFolderTooltip": "Відкриває папку файлової системи Ryujinx", + "OpenRyujinxLogsTooltip": "Відкриває папку, куди записуються журнали", + "ExitTooltip": "Виходить з Ryujinx", + "OpenSettingsTooltip": "Відкриває вікно налаштувань", + "OpenProfileManagerTooltip": "Відкриває вікно диспетчера профілів користувачів", + "StopEmulationTooltip": "Зупиняє емуляцію поточної гри та повертається до вибору гри", + "CheckUpdatesTooltip": "Перевіряє наявність оновлень для Ryujinx", + "OpenAboutTooltip": "Відкриває вікно «Про програму».", + "GridSize": "Розмір сітки", + "GridSizeTooltip": "Змінити розмір елементів сітки", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальська (Бразилія)", + "AboutRyujinxContributorsButtonHeader": "Переглянути всіх співавторів", + "SettingsTabSystemAudioVolume": "Гучність: ", + "AudioVolumeTooltip": "Змінити гучність звуку", + "SettingsTabSystemEnableInternetAccess": "Гостьовий доступ до Інтернету/режим LAN", + "EnableInternetAccessTooltip": "Дозволяє емульованій програмі підключатися до Інтернету.\n\nІгри з режимом локальної мережі можуть підключатися одна до одної, якщо це увімкнено, і системи підключені до однієї точки доступу. Сюди входять і справжні консолі.\n\nНЕ дозволяє підключатися до серверів Nintendo. Може призвести до збою в деяких іграх, які намагаються підключитися до Інтернету.\n\nЗалиште вимкненим, якщо не впевнені.", + "GameListContextMenuManageCheatToolTip": "Керування читами", + "GameListContextMenuManageCheat": "Керування читами", + "ControllerSettingsStickRange": "Діапазон:", + "DialogStopEmulationTitle": "Ryujinx - Зупинити емуляцію", + "DialogStopEmulationMessage": "Ви впевнені, що хочете зупинити емуляцію?", + "SettingsTabCpu": "ЦП", + "SettingsTabAudio": "Аудіо", + "SettingsTabNetwork": "Мережа", + "SettingsTabNetworkConnection": "Підключення до мережі", + "SettingsTabCpuCache": "Кеш ЦП", + "SettingsTabCpuMemory": "Пам'ять ЦП", + "DialogUpdaterFlatpakNotSupportedMessage": "Будь ласка, оновіть Ryujinx через FlatHub.", + "UpdaterDisabledWarningTitle": "Програму оновлення вимкнено!", + "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, який містить модифікації програми. Корисно для модифікацій, упакованих для реального обладнання.", + "ControllerSettingsRotate90": "Повернути на 90° за годинниковою стрілкою", + "IconSize": "Розмір значка", + "IconSizeTooltip": "Змінити розмір значків гри", + "MenuBarOptionsShowConsole": "Показати консоль", + "ShaderCachePurgeError": "Помилка очищення кешу шейдера {0}: {1}", + "UserErrorNoKeys": "Ключі не знайдено", + "UserErrorNoFirmware": "Прошивка не знайдена", + "UserErrorFirmwareParsingFailed": "Помилка аналізу прошивки", + "UserErrorApplicationNotFound": "Додаток не знайдено", + "UserErrorUnknown": "Невідома помилка", + "UserErrorUndefined": "Невизначена помилка", + "UserErrorNoKeysDescription": "Ryujinx не вдалося знайти ваш файл «prod.keys».", + "UserErrorNoFirmwareDescription": "Ryujinx не вдалося знайти встановлену прошивку", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx не вдалося проаналізувати прошивку. Зазвичай це спричинено застарілими ключами.", + "UserErrorApplicationNotFoundDescription": "Ryujinx не вдалося знайти дійсний додаток за вказаним шляхом", + "UserErrorUnknownDescription": "Сталася невідома помилка!", + "UserErrorUndefinedDescription": "Сталася невизначена помилка! Цього не повинно статися, зверніться до розробника!", + "OpenSetupGuideMessage": "Відкрити посібник із налаштування", + "NoUpdate": "Немає оновлень", + "TitleUpdateVersionLabel": "Версія {0} - {1}", + "RyujinxInfo": "Ryujin x - Інформація", + "RyujinxConfirm": "Ryujinx - Підтвердження", + "FileDialogAllTypes": "Всі типи", + "Never": "Ніколи", + "SwkbdMinCharacters": "Мінімальна кількість символів: {0}", + "SwkbdMinRangeCharacters": "Має бути {0}-{1} символів", + "SoftwareKeyboard": "Програмна клавіатура", + "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "DialogControllerAppletMessagePlayerRange": "Програма запитує {0} гравця(ів) з:\n\nТИПИ: {1}\n\nГРАВЦІ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", + "DialogControllerAppletMessage": "Програма запитує рівно стільки гравців: {0} з:\n\nТИПАМИ: {1}\n\nГРАВЦІВ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", + "DialogControllerAppletDockModeSet": "Встановлено режим док-станції. Ручний також недійсний.\n", + "UpdaterRenaming": "Перейменування старих файлів...", + "UpdaterRenameFailed": "Програмі оновлення не вдалося перейменувати файл: {0}", + "UpdaterAddingFiles": "Додавання нових файлів...", + "UpdaterExtracting": "Видобування оновлення...", + "UpdaterDownloading": "Завантаження оновлення...", + "Game": "Гра", + "Docked": "Док-станція", + "Handheld": "Портативний", + "ConnectionError": "Помилка з'єднання.", + "AboutPageDeveloperListMore": "{0} та інші...", + "ApiError": "Помилка API.", + "LoadingHeading": "Завантаження {0}", + "CompilingPPTC": "Компіляція PTC", + "CompilingShaders": "Компіляція шейдерів", + "AllKeyboards": "Всі клавіатури", + "OpenFileDialogTitle": "Виберіть підтримуваний файл для відкриття", + "OpenFolderDialogTitle": "Виберіть теку з розпакованою грою", + "AllSupportedFormats": "Усі підтримувані формати", + "RyujinxUpdater": "Програма оновлення Ryujinx", + "SettingsTabHotkeys": "Гарячі клавіші клавіатури", + "SettingsTabHotkeysHotkeys": "Гарячі клавіші клавіатури", + "SettingsTabHotkeysToggleVsyncHotkey": "Увімк/вимк вертикальну синхронізацію:", + "SettingsTabHotkeysScreenshotHotkey": "Знімок екрана:", + "SettingsTabHotkeysShowUiHotkey": "Показати інтерфейс:", + "SettingsTabHotkeysPauseHotkey": "Пауза:", + "SettingsTabHotkeysToggleMuteHotkey": "Вимкнути звук:", + "ControllerMotionTitle": "Налаштування керування рухом", + "ControllerRumbleTitle": "Налаштування вібрації", + "SettingsSelectThemeFileDialogTitle": "Виберіть файл теми", + "SettingsXamlThemeFile": "Файл теми Xaml", + "AvatarWindowTitle": "Керування обліковими записами - Аватар", + "Amiibo": "Amiibo", + "Unknown": "Невідомо", + "Usage": "Використання", + "Writable": "Можливість запису", + "SelectDlcDialogTitle": "Виберіть файли DLC", + "SelectUpdateDialogTitle": "Виберіть файли оновлення", + "UserProfileWindowTitle": "Менеджер профілів користувачів", + "CheatWindowTitle": "Менеджер читів", + "DlcWindowTitle": "Менеджер вмісту для завантаження", + "UpdateWindowTitle": "Менеджер оновлення назв", + "CheatWindowHeading": "Коди доступні для {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "Вміст для завантаження, доступний для {1} ({2}): {0}", + "UserProfilesEditProfile": "Редагувати вибране", + "Cancel": "Скасувати", + "Save": "Зберегти", + "Discard": "Скасувати", + "UserProfilesSetProfileImage": "Встановити зображення профілю", + "UserProfileEmptyNameError": "Назва обов'язкова", + "UserProfileNoImageError": "Зображення профілю обов'язкове", + "GameUpdateWindowHeading": "{0} Доступні оновлення для {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Збільшити роздільну здатність:", + "SettingsTabHotkeysResScaleDownHotkey": "Зменшити роздільну здатність:", + "UserProfilesName": "Ім'я", + "UserProfilesUserId": "ID користувача:", + "SettingsTabGraphicsBackend": "Графічний сервер", + "SettingsTabGraphicsBackendTooltip": "Графічний сервер для використання", + "SettingsEnableTextureRecompression": "Увімкнути рекомпресію текстури", + "SettingsEnableTextureRecompressionTooltip": "Стискає певні текстури, щоб зменшити використання VRAM.\n\nРекомендовано для використання з графічними процесорами, які мають менш ніж 4 ГБ відеопам’яті.\n\nЗалиште вимкненим, якщо не впевнені.", + "SettingsTabGraphicsPreferredGpu": "Бажаний GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nЯкщо не впевнені, встановіть графічний процесор, позначений як «dGPU». Якщо такого немає, залиште це.", + "SettingsAppRequiredRestartMessage": "Необхідно перезапустити Ryujinx", + "SettingsGpuBackendRestartMessage": "Налаштування графічного сервера або GPU було змінено. Для цього знадобиться перезапуск", + "SettingsGpuBackendRestartSubMessage": "Ви хочете перезапустити зараз?", + "RyujinxUpdaterMessage": "Хочете оновити Ryujinx до останньої версії?", + "SettingsTabHotkeysVolumeUpHotkey": "Збільшити гучність:", + "SettingsTabHotkeysVolumeDownHotkey": "Зменшити гучність:", + "SettingsEnableMacroHLE": "Увімкнути макрос HLE", + "SettingsEnableMacroHLETooltip": "Високорівнева емуляція коду макросу GPU.\n\nПокращує продуктивність, але може викликати графічні збої в деяких іграх.\n\nЗалиште увімкненим, якщо не впевнені.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Гуч", + "UserProfilesManageSaves": "Керувати збереженнями", + "DeleteUserSave": "Ви хочете видалити збереження користувача для цієї гри?", + "IrreversibleActionNote": "Цю дію не можна скасувати.", + "SaveManagerHeading": "Керувати збереженнями для {0}", + "SaveManagerTitle": "Менеджер збереження", + "Name": "Назва", + "Size": "Розмір", + "Search": "Пошук", + "UserProfilesRecoverLostAccounts": "Відновлення втрачених облікових записів", + "Recover": "Відновити", + "UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів", + "UserProfilesRecoverEmptyList": "No profiles to recover", + "GraphicsAATooltip": "Applies anti-aliasing to the game render", + "GraphicsAALabel": "Anti-Aliasing:", + "GraphicsScalingFilterLabel": "Scaling Filter:", + "GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling", + "GraphicsScalingFilterLevelLabel": "Level", + "GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level", + "SmaaLow": "SMAA Low", + "SmaaMedium": "SMAA Medium", + "SmaaHigh": "SMAA High", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Edit User", + "UserEditorTitleCreate": "Create User", + "SettingsTabNetworkInterface": "Network Interface:", + "NetworkInterfaceTooltip": "The network interface used for LAN features", + "NetworkInterfaceDefault": "Default", + "PackagingShaders": "Packaging Shaders", + "AboutChangelogButton": "View Changelog on GitHub", + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_CN.json b/src/Ryujinx.Ava/Assets/Locales/zh_CN.json new file mode 100644 index 000000000..d09a80ec6 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/zh_CN.json @@ -0,0 +1,656 @@ +{ + "Language": "简体中文", + "MenuBarFileOpenApplet": "打开小程序", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的 Mii 小程序", + "SettingsTabInputDirectMouseAccess": "直通鼠标操作", + "SettingsTabSystemMemoryManagerMode": "内存管理模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "软件", + "SettingsTabSystemMemoryManagerModeHost": "本机 (快速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机 (最快)", + "SettingsTabSystemUseHypervisor": "使用 Hypervisor 虚拟化", + "MenuBarFile": "文件", + "MenuBarFileOpenFromFile": "加载文件", + "MenuBarFileOpenUnpacked": "加载解包后的游戏", + "MenuBarFileOpenEmuFolder": "打开 Ryujinx 文件夹", + "MenuBarFileOpenLogsFolder": "打开日志文件夹", + "MenuBarFileExit": "退出", + "MenuBarOptions": "选项", + "MenuBarOptionsToggleFullscreen": "切换全屏", + "MenuBarOptionsStartGamesInFullscreen": "全屏模式启动游戏", + "MenuBarOptionsStopEmulation": "停止模拟", + "MenuBarOptionsSettings": "设置", + "MenuBarOptionsManageUserProfiles": "管理用户账户", + "MenuBarActions": "操作", + "MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息", + "MenuBarActionsScanAmiibo": "扫描 Amiibo", + "MenuBarTools": "工具", + "MenuBarToolsInstallFirmware": "安装固件", + "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 安装固件", + "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹安装固件", + "MenuBarToolsManageFileTypes": "管理文件扩展名", + "MenuBarToolsInstallFileTypes": "关联文件扩展名", + "MenuBarToolsUninstallFileTypes": "取消关联扩展名", + "MenuBarHelp": "帮助", + "MenuBarHelpCheckForUpdates": "检查更新", + "MenuBarHelpAbout": "关于", + "MenuSearch": "搜索…", + "GameListHeaderFavorite": "收藏", + "GameListHeaderIcon": "图标", + "GameListHeaderApplication": "名称", + "GameListHeaderDeveloper": "制作商", + "GameListHeaderVersion": "版本", + "GameListHeaderTimePlayed": "游玩时长", + "GameListHeaderLastPlayed": "最近游玩", + "GameListHeaderFileExtension": "扩展名", + "GameListHeaderFileSize": "大小", + "GameListHeaderPath": "路径", + "GameListContextMenuOpenUserSaveDirectory": "打开用户存档目录", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏存档的目录", + "GameListContextMenuOpenDeviceSaveDirectory": "打开系统目录", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开包含游戏系统设置的目录", + "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 目录", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开包含游戏 BCAT 数据的目录", + "GameListContextMenuManageTitleUpdates": "管理游戏更新", + "GameListContextMenuManageTitleUpdatesToolTip": "打开更新管理器", + "GameListContextMenuManageDlc": "管理 DLC", + "GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口", + "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", + "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", + "GameListContextMenuCacheManagement": "缓存管理", + "GameListContextMenuCacheManagementPurgePptc": "清除已编译的 PPTC 文件", + "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入目录把 .info 文件一并删除", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存", + "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 目录", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开包含游戏 PPTC 缓存的目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "打开着色器缓存目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开包含游戏着色器缓存的目录", + "GameListContextMenuExtractData": "提取数据", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "从游戏的当前状态中提取 ExeFS 分区 (包括更新)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "从游戏的当前状态中提取 RomFS 分区 (包括更新)", + "GameListContextMenuExtractDataLogo": "图标", + "GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标 (包括更新)", + "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", + "StatusBarSystemVersion": "系统版本:{0}", + "LinuxVmMaxMapCountDialogTitle": "检测到内存映射的限制过低", + "LinuxVmMaxMapCountDialogTextPrimary": "你想要将 vm.max_map_count 的值增加到 {0} 吗", + "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会试图创建超过当前允许的内存映射数量。当超过此限制时,Ryujinx会立即崩溃。", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "确定,关闭后重置", + "LinuxVmMaxMapCountDialogButtonPersistent": "确定,永久保存", + "LinuxVmMaxMapCountWarningTextPrimary": "内存映射的最大数量低于推荐值。", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count ({0}) 的当前值小于 {1}。 有些游戏可能会试图创建超过当前允许的内存映射量。当大于此限制时,Ryujinx 会立即崩溃。\n\n你可以手动增加内存映射限制或者安装 pkexec,它可以辅助Ryujinx解决该问题。", + "Settings": "设置", + "SettingsTabGeneral": "用户界面", + "SettingsTabGeneralGeneral": "常规", + "SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示", + "SettingsTabGeneralCheckUpdatesOnLaunch": "自动检查更新", + "SettingsTabGeneralShowConfirmExitDialog": "显示 \"确认退出\" 对话框", + "SettingsTabGeneralHideCursor": "隐藏鼠标指针:", + "SettingsTabGeneralHideCursorNever": "从不", + "SettingsTabGeneralHideCursorOnIdle": "自动隐藏", + "SettingsTabGeneralHideCursorAlways": "始终", + "SettingsTabGeneralGameDirectories": "游戏目录", + "SettingsTabGeneralAdd": "添加", + "SettingsTabGeneralRemove": "删除", + "SettingsTabSystem": "系统", + "SettingsTabSystemCore": "核心", + "SettingsTabSystemSystemRegion": "系统区域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "美国", + "SettingsTabSystemSystemRegionEurope": "欧洲", + "SettingsTabSystemSystemRegionAustralia": "澳大利亚", + "SettingsTabSystemSystemRegionChina": "中国", + "SettingsTabSystemSystemRegionKorea": "韩国", + "SettingsTabSystemSystemRegionTaiwan": "台湾地区", + "SettingsTabSystemSystemLanguage": "系统语言:", + "SettingsTabSystemSystemLanguageJapanese": "日语", + "SettingsTabSystemSystemLanguageAmericanEnglish": "美式英语", + "SettingsTabSystemSystemLanguageFrench": "法语", + "SettingsTabSystemSystemLanguageGerman": "德语", + "SettingsTabSystemSystemLanguageItalian": "意大利语", + "SettingsTabSystemSystemLanguageSpanish": "西班牙语", + "SettingsTabSystemSystemLanguageChinese": "简体中文", + "SettingsTabSystemSystemLanguageKorean": "韩语", + "SettingsTabSystemSystemLanguageDutch": "荷兰语", + "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙语", + "SettingsTabSystemSystemLanguageRussian": "俄语", + "SettingsTabSystemSystemLanguageTaiwanese": "繁体中文(台湾)", + "SettingsTabSystemSystemLanguageBritishEnglish": "英式英语", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法语", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙语", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "简体中文(推荐)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "繁体中文(推荐)", + "SettingsTabSystemSystemTimeZone": "系统时区:", + "SettingsTabSystemSystemTime": "系统时钟:", + "SettingsTabSystemEnableVsync": "启用垂直同步", + "SettingsTabSystemEnablePptc": "开启 PPTC 缓存", + "SettingsTabSystemEnableFsIntegrityChecks": "文件系统完整性检查", + "SettingsTabSystemAudioBackend": "音频后端:", + "SettingsTabSystemAudioBackendDummy": "无", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "音频输入/输出", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "修正", + "SettingsTabSystemHacksNote": "(会引起模拟器不稳定)", + "SettingsTabSystemExpandDramSize": "使用开发机的内存布局", + "SettingsTabSystemIgnoreMissingServices": "忽略缺失的服务", + "SettingsTabGraphics": "图形", + "SettingsTabGraphicsAPI": "图形 API", + "SettingsTabGraphicsEnableShaderCache": "启用着色器缓存", + "SettingsTabGraphicsAnisotropicFiltering": "各向异性过滤:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自动", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "分辨率缩放:", + "SettingsTabGraphicsResolutionScaleCustom": "自定义(不推荐)", + "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "宽高比:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "拉伸以适应窗口", + "SettingsTabGraphicsDeveloperOptions": "开发者选项", + "SettingsTabGraphicsShaderDumpPath": "图形着色器转储路径:", + "SettingsTabLogging": "日志", + "SettingsTabLoggingLogging": "日志", + "SettingsTabLoggingEnableLoggingToFile": "保存日志为文件", + "SettingsTabLoggingEnableStubLogs": "启用 Stub 日志", + "SettingsTabLoggingEnableInfoLogs": "启用信息日志", + "SettingsTabLoggingEnableWarningLogs": "启用警告日志", + "SettingsTabLoggingEnableErrorLogs": "启用错误日志", + "SettingsTabLoggingEnableTraceLogs": "启用跟踪日志", + "SettingsTabLoggingEnableGuestLogs": "启用来宾日志", + "SettingsTabLoggingEnableFsAccessLogs": "启用访问日志", + "SettingsTabLoggingFsGlobalAccessLogMode": "全局访问日志模式:", + "SettingsTabLoggingDeveloperOptions": "开发者选项", + "SettingsTabLoggingDeveloperOptionsNote": "警告:会降低性能", + "SettingsTabLoggingGraphicsBackendLogLevel": "图形后端日志级别:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "无", + "SettingsTabLoggingGraphicsBackendLogLevelError": "错误", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "减速", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", + "SettingsTabLoggingEnableDebugLogs": "启用调试日志", + "SettingsTabInput": "输入", + "SettingsTabInputEnableDockedMode": "主机模式", + "SettingsTabInputDirectKeyboardAccess": "直通键盘控制", + "SettingsButtonSave": "保存", + "SettingsButtonClose": "取消", + "SettingsButtonOk": "确定", + "SettingsButtonCancel": "取消", + "SettingsButtonApply": "应用", + "ControllerSettingsPlayer": "玩家", + "ControllerSettingsPlayer1": "玩家 1", + "ControllerSettingsPlayer2": "玩家 2", + "ControllerSettingsPlayer3": "玩家 3", + "ControllerSettingsPlayer4": "玩家 4", + "ControllerSettingsPlayer5": "玩家 5", + "ControllerSettingsPlayer6": "玩家 6", + "ControllerSettingsPlayer7": "玩家 7", + "ControllerSettingsPlayer8": "玩家 8", + "ControllerSettingsHandheld": "掌机模式", + "ControllerSettingsInputDevice": "输入设备", + "ControllerSettingsRefresh": "刷新", + "ControllerSettingsDeviceDisabled": "关闭", + "ControllerSettingsControllerType": "手柄类型", + "ControllerSettingsControllerTypeHandheld": "掌机", + "ControllerSettingsControllerTypeProController": "Pro 手柄", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon 组合", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", + "ControllerSettingsProfile": "预设", + "ControllerSettingsProfileDefault": "默认布局", + "ControllerSettingsLoad": "加载", + "ControllerSettingsAdd": "新建", + "ControllerSettingsRemove": "删除", + "ControllerSettingsButtons": "按键", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "方向键", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsStickButton": "按下摇杆", + "ControllerSettingsStickUp": "上", + "ControllerSettingsStickDown": "下", + "ControllerSettingsStickLeft": "左", + "ControllerSettingsStickRight": "右", + "ControllerSettingsStickStick": "摇杆", + "ControllerSettingsStickInvertXAxis": "反转 X 轴方向", + "ControllerSettingsStickInvertYAxis": "反转 Y 轴方向", + "ControllerSettingsStickDeadzone": "死区:", + "ControllerSettingsLStick": "左摇杆", + "ControllerSettingsRStick": "右摇杆", + "ControllerSettingsTriggersLeft": "左扳机", + "ControllerSettingsTriggersRight": "右扳机", + "ControllerSettingsTriggersButtonsLeft": "左扳机键", + "ControllerSettingsTriggersButtonsRight": "右扳机键", + "ControllerSettingsTriggers": "扳机", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左背键", + "ControllerSettingsExtraButtonsRight": "右背键", + "ControllerSettingsMisc": "其他", + "ControllerSettingsTriggerThreshold": "扳机阈值:", + "ControllerSettingsMotion": "体感", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 体感协议", + "ControllerSettingsMotionControllerSlot": "手柄:", + "ControllerSettingsMotionMirrorInput": "镜像操作", + "ControllerSettingsMotionRightJoyConSlot": "右JoyCon:", + "ControllerSettingsMotionServerHost": "服务器Host:", + "ControllerSettingsMotionGyroSensitivity": "陀螺仪敏感度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:", + "ControllerSettingsSave": "保存", + "ControllerSettingsClose": "关闭", + "UserProfilesSelectedUserProfile": "选择的用户账户:", + "UserProfilesSaveProfileName": "保存名称", + "UserProfilesChangeProfileImage": "更换头像", + "UserProfilesAvailableUserProfiles": "现有账户:", + "UserProfilesAddNewProfile": "新建账户", + "UserProfilesDelete": "删除", + "UserProfilesClose": "关闭", + "ProfileNameSelectionWatermark": "选择昵称", + "ProfileImageSelectionTitle": "选择头像", + "ProfileImageSelectionHeader": "选择合适的头像图片", + "ProfileImageSelectionNote": "您可以导入自定义头像,或从系统中选择头像", + "ProfileImageSelectionImportImage": "导入图像文件", + "ProfileImageSelectionSelectAvatar": "选择系统头像", + "InputDialogTitle": "输入对话框", + "InputDialogOk": "完成", + "InputDialogCancel": "取消", + "InputDialogAddNewProfileTitle": "选择用户名称", + "InputDialogAddNewProfileHeader": "请输入账户名称", + "InputDialogAddNewProfileSubtext": "(最大长度:{0})", + "AvatarChoose": "选择头像", + "AvatarSetBackgroundColor": "设置背景色", + "AvatarClose": "关闭", + "ControllerSettingsLoadProfileToolTip": "加载预设", + "ControllerSettingsAddProfileToolTip": "新增预设", + "ControllerSettingsRemoveProfileToolTip": "删除预设", + "ControllerSettingsSaveProfileToolTip": "保存预设", + "MenuBarFileToolsTakeScreenshot": "保存截图", + "MenuBarFileToolsHideUi": "隐藏界面", + "GameListContextMenuRunApplication": "运行应用", + "GameListContextMenuToggleFavorite": "收藏", + "GameListContextMenuToggleFavoriteToolTip": "标记喜爱的游戏", + "SettingsTabGeneralTheme": "主题", + "SettingsTabGeneralThemeCustomTheme": "自选主题路径", + "SettingsTabGeneralThemeBaseStyle": "主题色调", + "SettingsTabGeneralThemeBaseStyleDark": "暗黑", + "SettingsTabGeneralThemeBaseStyleLight": "浅色", + "SettingsTabGeneralThemeEnableCustomTheme": "使用自选主题界面", + "ButtonBrowse": "浏览", + "ControllerSettingsConfigureGeneral": "配置", + "ControllerSettingsRumble": "震动", + "ControllerSettingsRumbleStrongMultiplier": "强震动幅度", + "ControllerSettingsRumbleWeakMultiplier": "弱震动幅度", + "DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档", + "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档文件夹?", + "DialogConfirmationTitle": "Ryujinx - 设置", + "DialogUpdaterTitle": "Ryujinx - 更新", + "DialogErrorTitle": "Ryujinx - 错误", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 关闭", + "DialogErrorMessage": "Ryujinx 发生错误", + "DialogExitMessage": "是否关闭 Ryujinx?", + "DialogExitSubMessage": "未保存的进度将会丢失!", + "DialogMessageCreateSaveErrorMessage": "创建特定的存档时出错:{0}", + "DialogMessageFindSaveErrorMessage": "查找特定的存档时出错:{0}", + "FolderDialogExtractTitle": "选择要解压到的文件夹", + "DialogNcaExtractionMessage": "提取 {1} 的 {0} 分区...", + "DialogNcaExtractionTitle": "Ryujinx - NCA分区提取", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败。所选文件中不含主NCA文件", + "DialogNcaExtractionCheckLogErrorMessage": "提取失败。请查看日志文件获取详情。", + "DialogNcaExtractionSuccessMessage": "提取成功。", + "DialogUpdaterConvertFailedMessage": "无法转换当前 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "更新取消!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 是最新版本。", + "DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效。\n可能由于 GitHub Actions 正在编译新版本。请过一会再试。", + "DialogUpdaterConvertFailedGithubMessage": "无法转换从 Github 接收到的 Ryujinx 版本。", + "DialogUpdaterDownloadingMessage": "下载新版本中...", + "DialogUpdaterExtractionMessage": "正在提取更新...", + "DialogUpdaterRenamingMessage": "正在删除旧文件...", + "DialogUpdaterAddingFilesMessage": "安装更新中...", + "DialogUpdaterCompleteMessage": "更新成功!", + "DialogUpdaterRestartMessage": "立即重启 Ryujinx 完成更新?", + "DialogUpdaterArchNotSupportedMessage": "您运行的系统架构不受支持!", + "DialogUpdaterArchNotSupportedSubMessage": "(仅支持 x64 系统)", + "DialogUpdaterNoInternetMessage": "没有连接到互联网", + "DialogUpdaterNoInternetSubMessage": "请确保互联网连接正常。", + "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支持的版本,请您在 https://ryujinx.org/ 下载。", + "DialogRestartRequiredMessage": "需要重启模拟器", + "DialogThemeRestartMessage": "主题设置已保存。需要重新启动才能生效。", + "DialogThemeRestartSubMessage": "您是否要重启?", + "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的固件吗?(固件 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,但 Ryujinx 可以从现有的游戏安装固件{0}.\n模拟器现在可以运行。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安装固件", + "DialogFirmwareInstalledMessage": "已安装固件 {0}", + "DialogInstallFileTypesSuccessMessage": "关联文件类型成功!", + "DialogInstallFileTypesErrorMessage": "关联文件类型失败。", + "DialogUninstallFileTypesSuccessMessage": "成功解除文件类型关联!", + "DialogUninstallFileTypesErrorMessage": "解除文件类型关联失败。", + "DialogOpenSettingsWindowLabel": "打开设置窗口", + "DialogControllerAppletTitle": "控制器小窗口", + "DialogMessageDialogErrorExceptionMessage": "显示消息对话框时出错:{0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错:{0}", + "DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错:{0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的设置指南。", + "DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。", + "DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器。服务器可能已关闭,或者您没有连接网络。", + "DialogProfileInvalidProfileErrorMessage": "预设 {0} 与当前输入配置系统不兼容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "默认预设不能被覆盖", + "DialogProfileDeleteProfileTitle": "删除预设", + "DialogProfileDeleteProfileMessage": "删除后不可恢复,确认删除吗?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存\n\n确定吗?", + "DialogPPTCDeletionErrorMessage": "清除位于 {0} 的 PPTC 缓存时出错:{1}", + "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存\n\n确定吗?", + "DialogShaderDeletionErrorMessage": "清除位于 {0} 的着色器缓存时出错:{1}", + "DialogRyujinxErrorMessage": "Ryujinx 遇到错误", + "DialogInvalidTitleIdErrorMessage": "UI错误:所选游戏没有有效的标题ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路径 {0} 找不到有效的系统固件。", + "DialogFirmwareInstallerFirmwareInstallTitle": "固件 {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n会替换当前系统版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否确认继续?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装固件中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统版本 {0} 。", + "DialogUserProfileDeletionWarningMessage": "删除后将没有可选择的用户账户", + "DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户", + "DialogUserProfileUnsavedChangesTitle": "警告 - 未保存的更改", + "DialogUserProfileUnsavedChangesMessage": "您为该用户做出的部分改动尚未保存。", + "DialogUserProfileUnsavedChangesSubMessage": "是否舍弃这些改动?", + "DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新", + "DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?", + "DialogLoadNcaErrorMessage": "{0}. 错误的文件:{1}", + "DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,仅供开发人员使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "为了获得最佳性能,建议禁用着色器转储。您是否要立即禁用?", + "DialogLoadAppGameAlreadyLoadedMessage": "已有游戏正在运行", + "DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭程序,再启动另一个游戏。", + "DialogUpdateAddUpdateErrorMessage": "选择的文件不包含所选游戏的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 后端多线程", + "DialogSettingsBackendThreadingWarningMessage": "改变此选项后必须重启 Ryujinx 才能生效。\n\n取决于您的硬件,可能需要手动禁用驱动面板中的线程优化。", + "SettingsTabGraphicsFeaturesOptions": "功能", + "SettingsTabGraphicsBackendMultithreading": "多线程图形后端:", + "CommonAuto": "自动(推荐)", + "CommonOff": "关闭", + "CommonOn": "打开", + "InputDialogYes": "是", + "InputDialogNo": "否", + "DialogProfileInvalidProfileNameErrorMessage": "文件名包含无效字符,请重试。", + "MenuBarOptionsPauseEmulation": "暂停", + "MenuBarOptionsResumeEmulation": "继续", + "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 官网。", + "AboutDisclaimerMessage": "Ryujinx 以任何方式与 Nintendo™ 及其任何商业伙伴都没有关联", + "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com) ", + "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", + "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", + "AboutDiscordUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Discord 邀请链接。", + "AboutTwitterUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Twitter 主页。", + "AboutRyujinxAboutTitle": "关于:", + "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模拟器。\n您可以在 Patreon 上赞助 Ryujinx。\n关注 Twitter 或 Discord 可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来 GitHub 或 Discord 加入我们!", + "AboutRyujinxMaintainersTitle": "由以下作者维护:", + "AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者页面", + "AboutRyujinxSupprtersTitle": "感谢 Patreon 的赞助者:", + "AmiiboSeriesLabel": "Amiibo 系列", + "AmiiboCharacterLabel": "角色", + "AmiiboScanButtonLabel": "扫描", + "AmiiboOptionsShowAllLabel": "显示所有 Amiibo 系列", + "AmiiboOptionsUsRandomTagLabel": "修复:使用随机标记的 UUID", + "DlcManagerTableHeadingEnabledLabel": "启用", + "DlcManagerTableHeadingTitleIdLabel": "游戏ID", + "DlcManagerTableHeadingContainerPathLabel": "文件夹路径", + "DlcManagerTableHeadingFullPathLabel": "完整路径", + "DlcManagerRemoveAllButton": "全部删除", + "DlcManagerEnableAllButton": "全部启用", + "DlcManagerDisableAllButton": "全部禁用", + "MenuBarOptionsChangeLanguage": "更改语言", + "MenuBarShowFileTypes": "主页显示的文件类型", + "CommonSort": "排序", + "CommonShowNames": "显示名称", + "CommonFavorite": "收藏", + "OrderAscending": "从小到大", + "OrderDescending": "从大到小", + "SettingsTabGraphicsFeatures": "功能与增强", + "ErrorWindowTitle": "错误窗口", + "ToggleDiscordTooltip": "控制是否在 Discord 中显示您的游玩状态", + "AddGameDirBoxTooltip": "输入要添加的游戏目录", + "AddGameDirTooltip": "添加游戏目录到列表中", + "RemoveGameDirTooltip": "移除选中的目录", + "CustomThemeCheckTooltip": "使用自定义UI主题来更改模拟器的外观样式", + "CustomThemePathTooltip": "自定义主题的目录", + "CustomThemeBrowseTooltip": "查找自定义主题", + "DockModeToggleTooltip": "启用 Switch 的主机模式。\n绝大多数游戏画质会提高,略微增加性能消耗。\n在掌机和主机模式切换的过程中,您可能需要重新设置手柄类型。", + "DirectKeyboardTooltip": "开启 \"直连键盘访问(HID)支持\"\n(部分游戏可以使用您的键盘输入文字)", + "DirectMouseTooltip": "开启 \"直连鼠标访问(HID)支持\"\n(部分游戏可以使用您的鼠标导航)", + "RegionTooltip": "更改系统区域", + "LanguageTooltip": "更改系统语言", + "TimezoneTooltip": "更改系统时区", + "TimeTooltip": "更改系统时钟", + "VSyncToggleTooltip": "关闭后,小部分游戏可以超过60FPS帧率,以获得高帧率体验。\n但是可能出现软锁或读盘时间增加。\n如不确定,就请保持开启状态。", + "PptcToggleTooltip": "缓存编译完成的游戏CPU指令。减少启动时间和卡顿,提高游戏响应速度。\n如不确定,就请保持开启状态。", + "FsIntegrityToggleTooltip": "检查游戏文件内容的完整性。\n遇到损坏的文件则记录到日志文件,有助于排查错误。\n对性能没有影响。\n如不确定,就请保持开启状态。", + "AudioBackendTooltip": "默认推荐SDL2,但每种音频后端对各类游戏兼容性不同,遇到音频问题可以尝试切换后端。", + "MemoryManagerTooltip": "改变 Switch 内存映射到电脑内存的方式,会影响CPU性能消耗。", + "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最精确但是速度最慢。", + "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译效率更高。", + "MemoryManagerUnsafeTooltip": "直接映射内存页,但不检查内存溢出,使得即时编译效率更高。\nRyujinx 可以访问任何位置的内存,因而相对不安全。\n此模式下只应运行您信任的游戏或软件(即官方游戏)。", + "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译。在可用的情况下能大幅提高性能。但目前可能不稳定。", + "DRamTooltip": "使用Switch开发机的内存布局。\n不会提高任何性能,某些高清纹理包或 4k 分辨率 MOD 可能需要此选项。\n如果不确定,请始终关闭该选项。", + "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启选项。\n如您的游戏已经正常运行,请保持此选项关闭。", + "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", + "GalThreadingTooltip": "在第二个线程上执行图形后端命令。\n\n加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为自动。", + "ShaderCacheToggleTooltip": "开启后,模拟器会保存编译完成的着色器到磁盘,减少游戏渲染新特效和场景时的卡顿。", + "ResolutionScaleTooltip": "缩放渲染的分辨率", + "ResolutionScaleEntryTooltip": "尽可能使用例如1.5的浮点倍数。非整数的倍率易引起 BUG。", + "AnisotropyTooltip": "各向异性过滤等级。提高倾斜视角纹理的清晰度\n('自动'使用游戏默认的等级)", + "AspectRatioTooltip": "渲染窗口的宽高比。", + "ShaderDumpPathTooltip": "转储图形着色器的路径", + "FileLogTooltip": "保存日志文件到硬盘。不会影响性能。", + "StubLogTooltip": "在控制台中打印 stub 日志消息。不影响性能。", + "InfoLogTooltip": "在控制台中打印信息日志消息。不影响性能。", + "WarnLogTooltip": "在控制台中打印警告日志消息。不影响性能。", + "ErrorLogTooltip": "打印控制台中的错误日志消息。不影响性能。", + "TraceLogTooltip": "在控制台中打印跟踪日志消息。不影响性能。", + "GuestLogTooltip": "在控制台中打印访客日志消息。不影响性能。", + "FileAccessLogTooltip": "在控制台中打印文件访问日志信息。", + "FSAccessLogModeTooltip": "启用访问日志输出到控制台。可能的模式为 0-3", + "DeveloperOptionTooltip": "谨慎使用", + "OpenGlLogLevel": "需要打开适当的日志等级", + "DebugLogTooltip": "记录Debug消息", + "LoadApplicationFileTooltip": "选择 Switch 支持的游戏格式并加载", + "LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏并加载", + "OpenRyujinxFolderTooltip": "打开 Ryujinx 系统目录", + "OpenRyujinxLogsTooltip": "打开日志存放的目录", + "ExitTooltip": "关闭 Ryujinx", + "OpenSettingsTooltip": "打开设置窗口", + "OpenProfileManagerTooltip": "打开用户账户管理界面", + "StopEmulationTooltip": "停止运行当前游戏并回到主界面", + "CheckUpdatesTooltip": "检查 Ryujinx 新版本", + "OpenAboutTooltip": "打开“关于”窗口", + "GridSize": "网格尺寸", + "GridSizeTooltip": "调整网格模式的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙语", + "AboutRyujinxContributorsButtonHeader": "查看所有参与者", + "SettingsTabSystemAudioVolume": "音量:", + "AudioVolumeTooltip": "调节音量", + "SettingsTabSystemEnableInternetAccess": "允许网络访问/局域网模式", + "EnableInternetAccessTooltip": "允许模拟的游戏进程访问互联网。\n当多个模拟器/真实的 Switch 连接到同一个局域网时,带有 LAN 模式的游戏可以相互通信。\n即使开启选项也无法访问 Nintendo 服务器。此外可能导致某些尝试联网的游戏崩溃。\n如果您不确定,请关闭该选项。", + "GameListContextMenuManageCheatToolTip": "管理金手指", + "GameListContextMenuManageCheat": "管理金手指", + "ControllerSettingsStickRange": "范围:", + "DialogStopEmulationTitle": "Ryujinx - 停止模拟", + "DialogStopEmulationMessage": "是否确定停止模拟?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音频", + "SettingsTabNetwork": "网络", + "SettingsTabNetworkConnection": "网络连接", + "SettingsTabCpuCache": "CPU 缓存", + "SettingsTabCpuMemory": "CPU 内存", + "DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx。", + "UpdaterDisabledWarningTitle": "更新已禁用!", + "GameListContextMenuOpenSdModsDirectory": "打开 Atmosphere MOD 目录", + "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于 Atmosphere 自制系统的 MOD 目录", + "ControllerSettingsRotate90": "顺时针旋转 90°", + "IconSize": "图标尺寸", + "IconSizeTooltip": "更改游戏图标大小", + "MenuBarOptionsShowConsole": "显示控制台", + "ShaderCachePurgeError": "清除着色器缓存时出错:{0}: {1}", + "UserErrorNoKeys": "找不到密钥", + "UserErrorNoFirmware": "找不到固件", + "UserErrorFirmwareParsingFailed": "固件解析错误", + "UserErrorApplicationNotFound": "找不到应用程序", + "UserErrorUnknown": "未知错误", + "UserErrorUndefined": "未定义错误", + "UserErrorNoKeysDescription": "Ryujinx 找不到 'prod.keys' 文件", + "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安装的固件", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 无法解密选择的固件。这通常是由于使用了过旧的密钥。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 在选中路径找不到有效的应用程序。", + "UserErrorUnknownDescription": "发生未知错误!", + "UserErrorUndefinedDescription": "发生了未定义错误!此类错误不应出现,请联系开发人员!", + "OpenSetupGuideMessage": "打开设置教程", + "NoUpdate": "无更新", + "TitleUpdateVersionLabel": "版本 {0}", + "RyujinxInfo": "Ryujinx - 信息", + "RyujinxConfirm": "Ryujinx - 确认", + "FileDialogAllTypes": "全部类型", + "Never": "从不", + "SwkbdMinCharacters": "至少应为 {0} 个字长", + "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长", + "SoftwareKeyboard": "软件键盘", + "SoftwareKeyboardModeNumbersOnly": "只接受数字", + "SoftwareKeyboardModeAlphabet": "只接受非中日韩文字", + "SoftwareKeyboardModeASCII": "只接受 ASCII 符号", + "DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", + "DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3} 请打开设置窗口,重新配置手柄输入;或者关闭返回。", + "DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式", + "UpdaterRenaming": "正在删除旧文件...", + "UpdaterRenameFailed": "更新过程中无法重命名文件:{0}", + "UpdaterAddingFiles": "安装更新中...", + "UpdaterExtracting": "正在提取更新...", + "UpdaterDownloading": "下载新版本中...", + "Game": "游戏", + "Docked": "主机模式", + "Handheld": "掌机模式", + "ConnectionError": "连接错误。", + "AboutPageDeveloperListMore": "{0} 等开发者...", + "ApiError": "API错误。", + "LoadingHeading": "正在启动 {0}", + "CompilingPPTC": "编译PPTC缓存中", + "CompilingShaders": "编译着色器中", + "AllKeyboards": "所有键盘", + "OpenFileDialogTitle": "选择一个支持的文件以打开", + "OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹", + "AllSupportedFormats": "所有支持的格式", + "RyujinxUpdater": "Ryujinx 更新程序", + "SettingsTabHotkeys": "快捷键", + "SettingsTabHotkeysHotkeys": "键盘快捷键", + "SettingsTabHotkeysToggleVsyncHotkey": "切换垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "截屏:", + "SettingsTabHotkeysShowUiHotkey": "隐藏 界面:", + "SettingsTabHotkeysPauseHotkey": "暂停:", + "SettingsTabHotkeysToggleMuteHotkey": "静音:", + "ControllerMotionTitle": "体感操作设置", + "ControllerRumbleTitle": "震动设置", + "SettingsSelectThemeFileDialogTitle": "选择主题文件", + "SettingsXamlThemeFile": "Xaml 主题文件", + "AvatarWindowTitle": "管理账户 - 头像", + "Amiibo": "Amiibo", + "Unknown": "未知", + "Usage": "扫描可获得", + "Writable": "可写入", + "SelectDlcDialogTitle": "选择 DLC 文件", + "SelectUpdateDialogTitle": "选择更新文件", + "UserProfileWindowTitle": "管理用户账户", + "CheatWindowTitle": "金手指管理器", + "DlcWindowTitle": "管理 {0} ({1}) 的 DLC", + "UpdateWindowTitle": "游戏更新管理器", + "CheatWindowHeading": "适用于 {0} [{1}] 的金手指", + "BuildId": "游戏版本ID:", + "DlcWindowHeading": "{0} 个适用于 {1} ({2}) 的 DLC", + "UserProfilesEditProfile": "编辑选中账户", + "Cancel": "取消", + "Save": "保存", + "Discard": "返回", + "UserProfilesSetProfileImage": "选择头像", + "UserProfileEmptyNameError": "必须输入名称", + "UserProfileNoImageError": "请选择您的头像", + "GameUpdateWindowHeading": "管理 {0} ({1}) 的更新", + "SettingsTabHotkeysResScaleUpHotkey": "提高分辨率:", + "SettingsTabHotkeysResScaleDownHotkey": "降低分辨率:", + "UserProfilesName": "名称:", + "UserProfilesUserId": "用户ID:", + "SettingsTabGraphicsBackend": "图形后端", + "SettingsTabGraphicsBackendTooltip": "显卡使用的图形后端", + "SettingsEnableTextureRecompression": "启用纹理重压缩", + "SettingsEnableTextureRecompressionTooltip": "压缩某些纹理以减少显存的使用。\n适合显存小于 4GiB 的 GPU 开启。\n如果您不确定,请保持此项关闭。", + "SettingsTabGraphicsPreferredGpu": "首选 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "选择 Vulkan API 使用的显卡。\n此选项不会影响 OpenGL API。\n如果您不确定,建议选择\"dGPU(独立显卡)\"。如果没有独立显卡,则无需改动此选项。", + "SettingsAppRequiredRestartMessage": "Ryujinx 需要重启", + "SettingsGpuBackendRestartMessage": "您修改了图形 API 或显卡设置。需要重新启动才能生效", + "SettingsGpuBackendRestartSubMessage": "是否重启模拟器?", + "RyujinxUpdaterMessage": "是否更新 Ryujinx 到最新的版本?", + "SettingsTabHotkeysVolumeUpHotkey": "音量加:", + "SettingsTabHotkeysVolumeDownHotkey": "音量减:", + "SettingsEnableMacroHLE": "启用 HLE 宏", + "SettingsEnableMacroHLETooltip": "GPU 宏代码的高级模拟。\n提高性能表现,但可能在某些游戏中引起图形错误。\n如果您不确定,请保持此项开启。", + "SettingsEnableColorSpacePassthrough": "颜色空间穿透", + "SettingsEnableColorSpacePassthroughTooltip": "指示 Vulkan 后端在不指定颜色空间的情况下传递颜色信息。对于具有宽色域显示器的用户来说,这可能会以颜色正确性为代价,产生更鲜艳的颜色。", + "VolumeShort": "音量", + "UserProfilesManageSaves": "管理存档", + "DeleteUserSave": "确定删除这个游戏的存档吗?", + "IrreversibleActionNote": "删除后不可恢复。", + "SaveManagerHeading": "管理 {0} ({1}) 的存档", + "SaveManagerTitle": "存档管理器", + "Name": "名称", + "Size": "大小", + "Search": "搜索", + "UserProfilesRecoverLostAccounts": "恢复丢失的账户", + "Recover": "恢复", + "UserProfilesRecoverHeading": "找到了这些用户的存档数据", + "UserProfilesRecoverEmptyList": "没有可以恢复的配置文件", + "GraphicsAATooltip": "将抗锯齿使用到游戏渲染中", + "GraphicsAALabel": "抗锯齿:", + "GraphicsScalingFilterLabel": "缩放过滤:", + "GraphicsScalingFilterTooltip": "对帧缓冲区进行缩放", + "GraphicsScalingFilterLevelLabel": "等级", + "GraphicsScalingFilterLevelTooltip": "设置缩放过滤级别", + "SmaaLow": "SMAA 低质量", + "SmaaMedium": "SMAA 中质量", + "SmaaHigh": "SMAA 高质量", + "SmaaUltra": "SMAA 极致质量", + "UserEditorTitle": "编辑用户", + "UserEditorTitleCreate": "创建用户", + "SettingsTabNetworkInterface": "网络接口:", + "NetworkInterfaceTooltip": "用于局域网功能的网络接口", + "NetworkInterfaceDefault": "默认", + "PackagingShaders": "整合着色器中", + "AboutChangelogButton": "在Github上查看更新日志", + "AboutChangelogButtonTooltipMessage": "点击这里在您的默认浏览器中打开此版本的更新日志。" +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_TW.json b/src/Ryujinx.Ava/Assets/Locales/zh_TW.json new file mode 100644 index 000000000..a2f59f60d --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/zh_TW.json @@ -0,0 +1,656 @@ +{ + "Language": "英文 (美國)", + "MenuBarFileOpenApplet": "開啟 Applet 應用程序", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "開啟獨立的Mii修改器應用程序", + "SettingsTabInputDirectMouseAccess": "滑鼠直接操作", + "SettingsTabSystemMemoryManagerMode": "記憶體管理模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "軟體", + "SettingsTabSystemMemoryManagerModeHost": "主機模式 (快速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "主機略過檢查模式 (最快, 但不安全)", + "SettingsTabSystemUseHypervisor": "使用 Hypervisor", + "MenuBarFile": "_檔案", + "MenuBarFileOpenFromFile": "_載入檔案", + "MenuBarFileOpenUnpacked": "載入_已解開封裝的遊戲", + "MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾", + "MenuBarFileOpenLogsFolder": "開啟日誌資料夾", + "MenuBarFileExit": "_退出", + "MenuBarOptions": "選項", + "MenuBarOptionsToggleFullscreen": "切換全螢幕模式", + "MenuBarOptionsStartGamesInFullscreen": "使用全螢幕模式啟動遊戲", + "MenuBarOptionsStopEmulation": "停止模擬", + "MenuBarOptionsSettings": "_設定", + "MenuBarOptionsManageUserProfiles": "_管理使用者帳戶", + "MenuBarActions": "_動作", + "MenuBarOptionsSimulateWakeUpMessage": "模擬喚醒訊息", + "MenuBarActionsScanAmiibo": "掃描 Amiibo", + "MenuBarTools": "_工具", + "MenuBarToolsInstallFirmware": "安裝韌體", + "MenuBarFileToolsInstallFirmwareFromFile": "從 XCI 或 ZIP 安裝韌體", + "MenuBarFileToolsInstallFirmwareFromDirectory": "從資料夾安裝韌體", + "MenuBarToolsManageFileTypes": "管理檔案類型", + "MenuBarToolsInstallFileTypes": "註冊檔案類型", + "MenuBarToolsUninstallFileTypes": "取消註冊檔案類型", + "MenuBarHelp": "幫助", + "MenuBarHelpCheckForUpdates": "檢查更新", + "MenuBarHelpAbout": "關於", + "MenuSearch": "搜尋...", + "GameListHeaderFavorite": "收藏", + "GameListHeaderIcon": "圖示", + "GameListHeaderApplication": "名稱", + "GameListHeaderDeveloper": "開發人員", + "GameListHeaderVersion": "版本", + "GameListHeaderTimePlayed": "遊玩時數", + "GameListHeaderLastPlayed": "最近遊玩", + "GameListHeaderFileExtension": "副檔名", + "GameListHeaderFileSize": "檔案大小", + "GameListHeaderPath": "路徑", + "GameListContextMenuOpenUserSaveDirectory": "開啟使用者存檔資料夾", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟此遊戲的存檔資料夾", + "GameListContextMenuOpenDeviceSaveDirectory": "開啟系統資料夾", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟此遊戲的系統設定資料夾", + "GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 資料夾", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟此遊戲的 BCAT 資料夾\n", + "GameListContextMenuManageTitleUpdates": "管理遊戲更新", + "GameListContextMenuManageTitleUpdatesToolTip": "開啟遊戲更新管理視窗", + "GameListContextMenuManageDlc": "管理 DLC", + "GameListContextMenuManageDlcToolTip": "開啟 DLC 管理視窗", + "GameListContextMenuOpenModsDirectory": "開啟模組資料夾", + "GameListContextMenuOpenModsDirectoryToolTip": "開啟此遊戲的模組資料夾", + "GameListContextMenuCacheManagement": "快取管理", + "GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 快取", + "GameListContextMenuCacheManagementPurgePptcToolTip": "刪除遊戲的 PPTC 快取", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除著色器快取", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除遊戲的著色器快取", + "GameListContextMenuCacheManagementOpenPptcDirectory": "開啟 PPTC 資料夾", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟此遊戲的 PPTC 快取資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "開啟著色器快取資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟此遊戲的著色器快取資料夾", + "GameListContextMenuExtractData": "提取資料", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "從遊戲的目前狀態中提取 ExeFS 分區(包含更新)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "從遊戲的目前狀態中提取 RomFS 分區(包含更新)", + "GameListContextMenuExtractDataLogo": "圖示", + "GameListContextMenuExtractDataLogoToolTip": "從遊戲的目前狀態中提取圖示(包含更新)", + "StatusBarGamesLoaded": "{0}/{1} 遊戲載入完成", + "StatusBarSystemVersion": "系統版本: {0}", + "LinuxVmMaxMapCountDialogTitle": "檢測到映射的記憶體上限過低", + "LinuxVmMaxMapCountDialogTextPrimary": "你願意增加 vm.max_map_count to {0} 的數值嗎?", + "LinuxVmMaxMapCountDialogTextSecondary": "遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "碓定 (直至下一次重新啟動)", + "LinuxVmMaxMapCountDialogButtonPersistent": "碓定 (永遠設定)", + "LinuxVmMaxMapCountWarningTextPrimary": "映射記憶體的最大值少於目前建議的下限.", + "LinuxVmMaxMapCountWarningTextSecondary": "目前 vm.max_map_count ({0}) 的數值少於 {1}. 遊戲佔用的記憶體超出了映射的上限. Ryujinx的處理程序即將面臨崩潰.\n\n你可能需要手動增加上限或安裝 pkexec, 這些都能協助Ryujinx完成此操作.", + "Settings": "設定", + "SettingsTabGeneral": "使用者介面", + "SettingsTabGeneralGeneral": "一般", + "SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示", + "SettingsTabGeneralCheckUpdatesOnLaunch": "自動檢查更新", + "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認離開」對話框", + "SettingsTabGeneralHideCursor": "隱藏滑鼠遊標:", + "SettingsTabGeneralHideCursorNever": "永不", + "SettingsTabGeneralHideCursorOnIdle": "自動隱藏滑鼠", + "SettingsTabGeneralHideCursorAlways": "總是", + "SettingsTabGeneralGameDirectories": "遊戲資料夾", + "SettingsTabGeneralAdd": "新增", + "SettingsTabGeneralRemove": "刪除", + "SettingsTabSystem": "系統", + "SettingsTabSystemCore": "核心", + "SettingsTabSystemSystemRegion": "系統區域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "美國", + "SettingsTabSystemSystemRegionEurope": "歐洲", + "SettingsTabSystemSystemRegionAustralia": "澳洲", + "SettingsTabSystemSystemRegionChina": "中國", + "SettingsTabSystemSystemRegionKorea": "韓國", + "SettingsTabSystemSystemRegionTaiwan": "台灣", + "SettingsTabSystemSystemLanguage": "系統語言:", + "SettingsTabSystemSystemLanguageJapanese": "日文", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英文 (美國)", + "SettingsTabSystemSystemLanguageFrench": "法文", + "SettingsTabSystemSystemLanguageGerman": "德文", + "SettingsTabSystemSystemLanguageItalian": "義大利文", + "SettingsTabSystemSystemLanguageSpanish": "西班牙文", + "SettingsTabSystemSystemLanguageChinese": "中文 (中國)", + "SettingsTabSystemSystemLanguageKorean": "韓文", + "SettingsTabSystemSystemLanguageDutch": "荷蘭文", + "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙文", + "SettingsTabSystemSystemLanguageRussian": "俄文", + "SettingsTabSystemSystemLanguageTaiwanese": "中文 (台灣)", + "SettingsTabSystemSystemLanguageBritishEnglish": "英文 (英國)", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法語", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉丁美洲西班牙文", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文", + "SettingsTabSystemSystemLanguageTraditionalChinese": "繁體中文", + "SettingsTabSystemSystemTimeZone": "系統時區:", + "SettingsTabSystemSystemTime": "系統時鐘:", + "SettingsTabSystemEnableVsync": "垂直同步", + "SettingsTabSystemEnablePptc": "啟用 PPTC 快取", + "SettingsTabSystemEnableFsIntegrityChecks": "開啟檔案系統完整性檢查", + "SettingsTabSystemAudioBackend": "音效處理後台架構:", + "SettingsTabSystemAudioBackendDummy": "模擬", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "修正", + "SettingsTabSystemHacksNote": " (會引起模擬器不穩定)", + "SettingsTabSystemExpandDramSize": "使用額外的記憶體佈局 (開發人員)", + "SettingsTabSystemIgnoreMissingServices": "忽略缺少的服務", + "SettingsTabGraphics": "圖像", + "SettingsTabGraphicsAPI": "圖像處理應用程式介面", + "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", + "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", + "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", + "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", + "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", + "SettingsTabGraphicsAnisotropicFiltering16x": "16倍", + "SettingsTabGraphicsResolutionScale": "解析度比例:", + "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不建議使用)", + "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "螢幕長寬比例:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "伸展至螢幕大小", + "SettingsTabGraphicsDeveloperOptions": "開發者選項", + "SettingsTabGraphicsShaderDumpPath": "圖形著色器轉存路徑:", + "SettingsTabLogging": "日誌", + "SettingsTabLoggingLogging": "日誌", + "SettingsTabLoggingEnableLoggingToFile": "儲存記錄日誌為檔案", + "SettingsTabLoggingEnableStubLogs": "啟用 Stub 記錄", + "SettingsTabLoggingEnableInfoLogs": "啟用資訊記錄", + "SettingsTabLoggingEnableWarningLogs": "啟用警告記錄", + "SettingsTabLoggingEnableErrorLogs": "啟用錯誤記錄", + "SettingsTabLoggingEnableTraceLogs": "啟用追蹤記錄", + "SettingsTabLoggingEnableGuestLogs": "啟用賓客記錄", + "SettingsTabLoggingEnableFsAccessLogs": "啟用檔案存取記錄", + "SettingsTabLoggingFsGlobalAccessLogMode": "記錄全域檔案存取模式:", + "SettingsTabLoggingDeveloperOptions": "開發者選項", + "SettingsTabLoggingDeveloperOptionsNote": "警告:此操作會降低效能", + "SettingsTabLoggingGraphicsBackendLogLevel": "圖像處理後台記錄等級:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "無", + "SettingsTabLoggingGraphicsBackendLogLevelError": "錯誤", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "減速", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", + "SettingsTabLoggingEnableDebugLogs": "啟用除錯記錄", + "SettingsTabInput": "輸入", + "SettingsTabInputEnableDockedMode": "Docked 模式", + "SettingsTabInputDirectKeyboardAccess": "鍵盤直接操作", + "SettingsButtonSave": "儲存", + "SettingsButtonClose": "關閉", + "SettingsButtonOk": "確定", + "SettingsButtonCancel": "取消", + "SettingsButtonApply": "套用", + "ControllerSettingsPlayer": "玩家", + "ControllerSettingsPlayer1": "玩家 1", + "ControllerSettingsPlayer2": "玩家 2", + "ControllerSettingsPlayer3": "玩家 3", + "ControllerSettingsPlayer4": "玩家 4", + "ControllerSettingsPlayer5": "玩家 5", + "ControllerSettingsPlayer6": "玩家 6", + "ControllerSettingsPlayer7": "玩家 7", + "ControllerSettingsPlayer8": "玩家 8", + "ControllerSettingsHandheld": "掌機模式", + "ControllerSettingsInputDevice": "輸入裝置", + "ControllerSettingsRefresh": "更新", + "ControllerSettingsDeviceDisabled": "關閉", + "ControllerSettingsControllerType": "控制器類型", + "ControllerSettingsControllerTypeHandheld": "掌機", + "ControllerSettingsControllerTypeProController": "Nintendo Switch Pro控制器", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", + "ControllerSettingsProfile": "配置檔案", + "ControllerSettingsProfileDefault": "預設", + "ControllerSettingsLoad": "載入", + "ControllerSettingsAdd": "建立", + "ControllerSettingsRemove": "刪除", + "ControllerSettingsButtons": "按鍵", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "方向鍵", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsStickButton": "按鍵", + "ControllerSettingsStickUp": "上", + "ControllerSettingsStickDown": "下", + "ControllerSettingsStickLeft": "左", + "ControllerSettingsStickRight": "右", + "ControllerSettingsStickStick": "搖桿", + "ControllerSettingsStickInvertXAxis": "搖桿左右反向", + "ControllerSettingsStickInvertYAxis": "搖桿上下反向", + "ControllerSettingsStickDeadzone": "盲區:", + "ControllerSettingsLStick": "左搖桿", + "ControllerSettingsRStick": "右搖桿", + "ControllerSettingsTriggersLeft": "左 Triggers", + "ControllerSettingsTriggersRight": "右 Triggers", + "ControllerSettingsTriggersButtonsLeft": "左 Triggers 鍵", + "ControllerSettingsTriggersButtonsRight": "右 Triggers 鍵", + "ControllerSettingsTriggers": "板機", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左按鍵", + "ControllerSettingsExtraButtonsRight": "右按鍵", + "ControllerSettingsMisc": "其他", + "ControllerSettingsTriggerThreshold": "Triggers 閾值:", + "ControllerSettingsMotion": "傳感器", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 相容性傳感協定", + "ControllerSettingsMotionControllerSlot": "控制器插槽:", + "ControllerSettingsMotionMirrorInput": "鏡像輸入", + "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon:", + "ControllerSettingsMotionServerHost": "伺服器IP地址:", + "ControllerSettingsMotionGyroSensitivity": "陀螺儀敏感度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺儀盲區:", + "ControllerSettingsSave": "儲存", + "ControllerSettingsClose": "關閉", + "UserProfilesSelectedUserProfile": "選擇使用者帳戶:", + "UserProfilesSaveProfileName": "儲存帳戶名稱", + "UserProfilesChangeProfileImage": "更換帳戶頭像", + "UserProfilesAvailableUserProfiles": "現有的使用者帳戶:", + "UserProfilesAddNewProfile": "建立帳戶", + "UserProfilesDelete": "刪除", + "UserProfilesClose": "關閉", + "ProfileNameSelectionWatermark": "選擇一個暱稱", + "ProfileImageSelectionTitle": "帳戶頭像選擇", + "ProfileImageSelectionHeader": "選擇帳戶頭像", + "ProfileImageSelectionNote": "你可以導入自訂頭像,或從系統中選擇頭像", + "ProfileImageSelectionImportImage": "導入圖片檔案", + "ProfileImageSelectionSelectAvatar": "選擇系統頭像", + "InputDialogTitle": "輸入對話框", + "InputDialogOk": "完成", + "InputDialogCancel": "取消", + "InputDialogAddNewProfileTitle": "選擇帳戶名稱", + "InputDialogAddNewProfileHeader": "請輸入帳戶名稱", + "InputDialogAddNewProfileSubtext": "(最大長度:{0})", + "AvatarChoose": "選擇", + "AvatarSetBackgroundColor": "設定背景顏色", + "AvatarClose": "關閉", + "ControllerSettingsLoadProfileToolTip": "載入配置檔案", + "ControllerSettingsAddProfileToolTip": "新增配置檔案", + "ControllerSettingsRemoveProfileToolTip": "刪除配置檔案", + "ControllerSettingsSaveProfileToolTip": "儲存配置檔案", + "MenuBarFileToolsTakeScreenshot": "儲存截圖", + "MenuBarFileToolsHideUi": "隱藏使用者介面", + "GameListContextMenuRunApplication": "執行程式", + "GameListContextMenuToggleFavorite": "標記為收藏", + "GameListContextMenuToggleFavoriteToolTip": "啟用或取消收藏標記", + "SettingsTabGeneralTheme": "佈景主題", + "SettingsTabGeneralThemeCustomTheme": "自訂佈景主題路徑", + "SettingsTabGeneralThemeBaseStyle": "基本佈景主題式樣", + "SettingsTabGeneralThemeBaseStyleDark": "深色模式", + "SettingsTabGeneralThemeBaseStyleLight": "淺色模式", + "SettingsTabGeneralThemeEnableCustomTheme": "使用自訂佈景主題", + "ButtonBrowse": "瀏覽", + "ControllerSettingsConfigureGeneral": "配置", + "ControllerSettingsRumble": "震動", + "ControllerSettingsRumbleStrongMultiplier": "強震動調節", + "ControllerSettingsRumbleWeakMultiplier": "弱震動調節", + "DialogMessageSaveNotAvailableMessage": "沒有{0} [{1:x16}]的遊戲存檔", + "DialogMessageSaveNotAvailableCreateSaveMessage": "是否建立該遊戲的存檔資料夾?", + "DialogConfirmationTitle": "Ryujinx - 設定", + "DialogUpdaterTitle": "Ryujinx - 更新", + "DialogErrorTitle": "Ryujinx - 錯誤", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 關閉", + "DialogErrorMessage": "Ryujinx 遇到了錯誤", + "DialogExitMessage": "你確定要關閉 Ryujinx 嗎?", + "DialogExitSubMessage": "所有未儲存的資料將會遺失!", + "DialogMessageCreateSaveErrorMessage": "建立特定的存檔時出現錯誤: {0}", + "DialogMessageFindSaveErrorMessage": "查找特定的存檔時出現錯誤: {0}", + "FolderDialogExtractTitle": "選擇要解壓到的資料夾", + "DialogNcaExtractionMessage": "提取{1}的{0}分區...", + "DialogNcaExtractionTitle": "Ryujinx - NCA分區提取", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不含主NCA檔案", + "DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請查看日誌檔案取得詳情。", + "DialogNcaExtractionSuccessMessage": "提取成功。", + "DialogUpdaterConvertFailedMessage": "無法轉換目前 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "更新取消!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "你使用的 Ryujinx 是最新版本。", + "DialogUpdaterFailedToGetVersionMessage": "嘗試從 Github 取得版本訊息時失敗。可能是因為 GitHub Actions 正在編譯新版本。請於數分數後重試。", + "DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github 接收到的 Ryujinx 版本。", + "DialogUpdaterDownloadingMessage": "下載最新版本中...", + "DialogUpdaterExtractionMessage": "正在提取更新...", + "DialogUpdaterRenamingMessage": "正在刪除舊檔案...", + "DialogUpdaterAddingFilesMessage": "安裝更新中...", + "DialogUpdaterCompleteMessage": "更新成功!", + "DialogUpdaterRestartMessage": "你確定要立即重新啟動 Ryujinx 嗎?", + "DialogUpdaterArchNotSupportedMessage": "你執行的系統架構不被支援!", + "DialogUpdaterArchNotSupportedSubMessage": "(僅支援 x64 系統)", + "DialogUpdaterNoInternetMessage": "你沒有連接到網際網絡!", + "DialogUpdaterNoInternetSubMessage": "請確保網際網絡連接正常!", + "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "如果你希望使用被受支援的Ryujinx版本,請你在官方網址 https://ryujinx.org/ 下載.", + "DialogRestartRequiredMessage": "模擬器必須重新啟動", + "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能生效。", + "DialogThemeRestartSubMessage": "你確定要現在重新啟動嗎?", + "DialogFirmwareInstallEmbeddedMessage": "要安裝遊戲內建的韌體嗎?(韌體 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}.\\n模擬器現在可以執行。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體", + "DialogFirmwareInstalledMessage": "已安裝韌體{0}", + "DialogInstallFileTypesSuccessMessage": "成功註冊檔案類型!", + "DialogInstallFileTypesErrorMessage": "註冊檔案類型失敗。", + "DialogUninstallFileTypesSuccessMessage": "成功取消註冊檔案類型!", + "DialogUninstallFileTypesErrorMessage": "取消註冊檔案類型失敗。", + "DialogOpenSettingsWindowLabel": "開啟設定視窗", + "DialogControllerAppletTitle": "控制器小視窗", + "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話框時出現錯誤: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出現錯誤: {0}", + "DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話框時出現錯誤: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有關修復此錯誤的更多訊息,可以遵循我們的設定指南。", + "DialogUserErrorDialogTitle": "Ryujinx 錯誤 ({0})", + "DialogAmiiboApiTitle": "Amiibo 應用程式介面", + "DialogAmiiboApiFailFetchMessage": "從 API 取得訊息時出錯。", + "DialogAmiiboApiConnectErrorMessage": "無法連接到 Amiibo API 伺服器。伺服器可能已關閉,或你沒有連接到網際網路。", + "DialogProfileInvalidProfileErrorMessage": "配置檔案 {0} 與目前輸入系統不相容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "無法覆蓋預設的配置檔案", + "DialogProfileDeleteProfileTitle": "刪除帳戶", + "DialogProfileDeleteProfileMessage": "此操作不可撤銷, 您確定要繼續嗎?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "下一次重啟時將會重新建立以下遊戲的 PPTC 快取\n\n{0}\n\n你確定要繼續嗎?", + "DialogPPTCDeletionErrorMessage": "清除位於{0}的 PPTC 快取時出錯: {1}", + "DialogShaderDeletionMessage": "即將刪除以下遊戲的著色器快取:\n\n{0}\n\n你確定要繼續嗎?", + "DialogShaderDeletionErrorMessage": "清除{0}的著色器快取時出現錯誤: {1}", + "DialogRyujinxErrorMessage": "Ryujinx 遇到錯誤", + "DialogInvalidTitleIdErrorMessage": "UI 錯誤:所選遊戲沒有有效的標題ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路徑{0}找不到有效的系統韌體。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體{0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "將安裝{0}版本的系統。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將替換目前系統版本{0}。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "你確定要繼續嗎?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安裝韌體中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本{0}。", + "DialogUserProfileDeletionWarningMessage": "刪除後將沒有可選擇的使用者帳戶", + "DialogUserProfileDeletionConfirmMessage": "你確定要刪除選擇中的帳戶嗎?", + "DialogUserProfileUnsavedChangesTitle": "警告 - 有未儲存的更改", + "DialogUserProfileUnsavedChangesMessage": "你對此帳戶所做的更改尚未儲存.", + "DialogUserProfileUnsavedChangesSubMessage": "你確定要捨棄更改嗎?", + "DialogControllerSettingsModifiedConfirmMessage": "目前的輸入配置檔案已更新", + "DialogControllerSettingsModifiedConfirmSubMessage": "你確定要儲存嗎?", + "DialogLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}", + "DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "你啟用了跟蹤記錄,它的設計僅限開發人員使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用追蹤記錄。你是否要立即停用?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "你啟用了著色器轉存,它的設計僅限開發人員使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為了獲得最佳效能,建議停用著色器轉存。你是否要立即停用?", + "DialogLoadAppGameAlreadyLoadedMessage": "目前已載入此遊戲", + "DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉程式,再啟動另一個遊戲。", + "DialogUpdateAddUpdateErrorMessage": "選擇的檔案不包含所選遊戲的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 後台多工執行中", + "DialogSettingsBackendThreadingWarningMessage": "更改此選項後必須重啟 Ryujinx 才能生效。根據你的硬體,您開啟該選項時,可能需要手動停用驅動程式本身的GPU多執行緒。", + "SettingsTabGraphicsFeaturesOptions": "功能", + "SettingsTabGraphicsBackendMultithreading": "圖像處理後台多線程支援:", + "CommonAuto": "自動(推薦)", + "CommonOff": "關閉", + "CommonOn": "打開", + "InputDialogYes": "是", + "InputDialogNo": "否", + "DialogProfileInvalidProfileNameErrorMessage": "檔案名包含無效字元,請重試。", + "MenuBarOptionsPauseEmulation": "暫停", + "MenuBarOptionsResumeEmulation": "繼續", + "AboutUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的官方網站。", + "AboutDisclaimerMessage": "Ryujinx 與 Nintendo™ 並沒有任何關聯, 包括其合作伙伴, 及任何形式上。", + "AboutAmiiboDisclaimerMessage": "我們的 Amiibo 模擬使用了\nAmiiboAPI (www.amiiboapi.com) ", + "AboutPatreonUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Patreon 贊助網頁。", + "AboutGithubUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 GitHub 儲存庫。", + "AboutDiscordUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Discord 伺服器邀請連結。", + "AboutTwitterUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Twitter 首頁。", + "AboutRyujinxAboutTitle": "關於:", + "AboutRyujinxAboutContent": "Ryujinx 是 Nintendo Switch™ 的一款模擬器。\n懇請您在 Patreon 上贊助我們。\n關注 Twitter 或 Discord 可以取得我們的最新動態。\n如果您對開發本軟體感興趣,歡迎來 GitHub 和 Discord 加入我們!", + "AboutRyujinxMaintainersTitle": "開發及維護名單:", + "AboutRyujinxMaintainersContentTooltipMessage": "在瀏覽器中打開貢獻者的網頁", + "AboutRyujinxSupprtersTitle": "Patreon 的贊助人:", + "AmiiboSeriesLabel": "Amiibo 系列", + "AmiiboCharacterLabel": "角色", + "AmiiboScanButtonLabel": "掃描", + "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", + "AmiiboOptionsUsRandomTagLabel": "侵略:使用隨機標記的 Uuid 編碼", + "DlcManagerTableHeadingEnabledLabel": "已啟用", + "DlcManagerTableHeadingTitleIdLabel": "遊戲ID", + "DlcManagerTableHeadingContainerPathLabel": "資料夾路徑", + "DlcManagerTableHeadingFullPathLabel": "完整路徑", + "DlcManagerRemoveAllButton": "全部刪除", + "DlcManagerEnableAllButton": "啟用全部", + "DlcManagerDisableAllButton": "停用全部", + "MenuBarOptionsChangeLanguage": "更改語言", + "MenuBarShowFileTypes": "顯示檔案類型", + "CommonSort": "排序", + "CommonShowNames": "顯示名稱", + "CommonFavorite": "收藏", + "OrderAscending": "從小到大", + "OrderDescending": "從大到小", + "SettingsTabGraphicsFeatures": "功能及優化", + "ErrorWindowTitle": "錯誤視窗", + "ToggleDiscordTooltip": "啟用或關閉 Discord 動態狀態展示", + "AddGameDirBoxTooltip": "輸入要添加的遊戲資料夾", + "AddGameDirTooltip": "添加遊戲資料夾到列表中", + "RemoveGameDirTooltip": "移除選擇中的遊戲資料夾", + "CustomThemeCheckTooltip": "啟用或關閉自訂佈景主題", + "CustomThemePathTooltip": "自訂佈景主題的資料夾", + "CustomThemeBrowseTooltip": "查找自訂佈景主題", + "DockModeToggleTooltip": "是否開啟 Switch 的 Docked 模式", + "DirectKeyboardTooltip": "支援鍵盤直接存取 (HID協定) . 可供給遊戲使用你的鍵盤作為輸入文字裝置.", + "DirectMouseTooltip": "支援滑鼠直接存取 (HID協定) . 可供給遊戲使用你的滑鼠作為瞄準裝置.", + "RegionTooltip": "更改系統區域", + "LanguageTooltip": "更改系統語言", + "TimezoneTooltip": "更改系統時區", + "TimeTooltip": "更改系統時鐘", + "VSyncToggleTooltip": "模擬遊戲主機垂直同步更新頻率. 重要地反映著遊戲本身的速度; 關閉它可能會令後使用動態更新率的遊戲速度過高, 或會引致載入錯誤等等.\n\n可在遊戲中利用自訂快速鍵開關此功能. 我們也建議使用快速鍵, 如果你計劃關上它.\n\n如果不確定請設定為\"開啟\".", + "PptcToggleTooltip": "開啟以後減少遊戲啟動時間和卡頓", + "FsIntegrityToggleTooltip": "是否檢查遊戲檔案內容的完整性", + "AudioBackendTooltip": "更改音效處理後台架構.\n\n推薦使用SDL2架構, 而OpenAL及SoundIO架構用作後備. Dummy是沒有音效的.\n\n如果不確定請設定為\"SDL2\".", + "MemoryManagerTooltip": "更改模擬器記憶體至電腦記憶體的映射和存取方式,極其影響CPU效能.\n\n如果不確定, 請設定為\"主機略過檢查模式\".", + "MemoryManagerSoftwareTooltip": "使用軟體虛擬分頁表換算, 最精確但是速度最慢.", + "MemoryManagerHostTooltip": "直接地映射模擬記憶體到電腦記憶體. 對 JIT 編譯和執行效率有顯著提升. ", + "MemoryManagerUnsafeTooltip": "直接地映射模擬記憶體到電腦記憶體, 但是不檢查記憶體溢出. 由於 Ryujinx 的子程式可以存取任何位置的記憶體, 因而相對不安全. 故在此模式下只應執行你信任的遊戲或軟體.", + "UseHypervisorTooltip": "使用 Hypervisor 代替 JIT。在本功能可用時就可以大幅增大效能,但目前狀態還不穩定。", + "DRamTooltip": "利用可選擇性的記憶體模式來模擬Switch發展中型號.\n\n此選項只會對高畫質材質包或4K模組有用. 而這並不會提升效能. \n\n如果不確定請關閉本功能.", + "IgnoreMissingServicesTooltip": "忽略某些未被實施的系統服務. 此功能有助於繞過當啟動遊戲時帶來的故障.\n\n如果不確定請關閉本功能。", + "GraphicsBackendThreadingTooltip": "執行雙線程後台繪圖指令, 能夠減少著色器編譯斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", + "GalThreadingTooltip": "執行雙線程後台繪圖指令.\n\n能夠加速著色器編譯及減少斷續, 並提高GPU驅動效能, 即將它不支持多線程處理. 而對於多線程處理也有少量提升.\n\n如果你不確定請設定為\"自動\"", + "ShaderCacheToggleTooltip": "儲存著色器快取到硬碟,減少存取斷續。\n\n如果不確定請設定為\"開啟\"。", + "ResolutionScaleTooltip": "解析度繪圖倍率", + "ResolutionScaleEntryTooltip": "盡量使用如1.5的浮點倍數。非整數的倍率易引起錯誤", + "AnisotropyTooltip": "各向異性過濾等級。提高傾斜視角材質的清晰度\n(選擇「自動」將使用遊戲預設指定的等級)", + "AspectRatioTooltip": "模擬器視窗解析度的長寬比", + "ShaderDumpPathTooltip": "圖形著色器轉存路徑", + "FileLogTooltip": "是否儲存日誌檔案到硬碟", + "StubLogTooltip": "在控制台顯示及記錄 Stub 訊息", + "InfoLogTooltip": "在控制台顯示及記錄資訊訊息", + "WarnLogTooltip": "在控制台顯示及記錄警告訊息\n", + "ErrorLogTooltip": "在控制台顯示及記錄錯誤訊息", + "TraceLogTooltip": "在控制台顯示及記錄追蹤訊息", + "GuestLogTooltip": "在控制台顯示及記錄賓客訊息", + "FileAccessLogTooltip": "在控制台顯示及記錄檔案存取訊息", + "FSAccessLogModeTooltip": "在控制台顯示及記錄FS 存取訊息. 可選的模式是 0-3", + "DeveloperOptionTooltip": "使用請謹慎", + "OpenGlLogLevel": "需要打開適當的日誌等級", + "DebugLogTooltip": "在控制台顯示及記錄除錯訊息.\n\n僅限於受訓的成員使用, 因為它很難理解而且令模擬的效能非常地差.\n", + "LoadApplicationFileTooltip": "選擇 Switch 支援的遊戲格式並載入", + "LoadApplicationFolderTooltip": "選擇解包後的 Switch 遊戲並載入", + "OpenRyujinxFolderTooltip": "開啟 Ryujinx 系統資料夾", + "OpenRyujinxLogsTooltip": "開啟存放日誌的資料夾", + "ExitTooltip": "關閉 Ryujinx", + "OpenSettingsTooltip": "開啟設定視窗", + "OpenProfileManagerTooltip": "開啟使用者帳戶管理視窗", + "StopEmulationTooltip": "停止執行目前遊戲並回到選擇界面", + "CheckUpdatesTooltip": "檢查 Ryujinx 新版本", + "OpenAboutTooltip": "開啟關於視窗", + "GridSize": "網格尺寸", + "GridSizeTooltip": "調整網格模式的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙語", + "AboutRyujinxContributorsButtonHeader": "查看所有參與者", + "SettingsTabSystemAudioVolume": "音量:", + "AudioVolumeTooltip": "調節音量", + "SettingsTabSystemEnableInternetAccess": "啟用網路連接", + "EnableInternetAccessTooltip": "開啟網路存取。此選項打開後,效果類似於 Switch 連接到網路的狀態。注意即使此選項關閉,應用程式偶爾也有可能連接到網路", + "GameListContextMenuManageCheatToolTip": "管理金手指", + "GameListContextMenuManageCheat": "管理金手指", + "ControllerSettingsStickRange": "範圍:", + "DialogStopEmulationTitle": "Ryujinx - 停止模擬", + "DialogStopEmulationMessage": "你確定要停止模擬嗎?", + "SettingsTabCpu": "處理器", + "SettingsTabAudio": "音訊", + "SettingsTabNetwork": "網路", + "SettingsTabNetworkConnection": "網路連接", + "SettingsTabCpuCache": "CPU 快取", + "SettingsTabCpuMemory": "CPU 模式", + "DialogUpdaterFlatpakNotSupportedMessage": "請透過 Flathub 更新 Ryujinx。", + "UpdaterDisabledWarningTitle": "更新已停用!", + "GameListContextMenuOpenSdModsDirectory": "開啟 Atmosphere 模組資料夾", + "GameListContextMenuOpenSdModsDirectoryToolTip": "開啟此遊戲額外的SD記憶卡Atmosphere模組資料夾. 有用於包裝在真實主機上的模組.\n", + "ControllerSettingsRotate90": "順時針旋轉 90°", + "IconSize": "圖示尺寸", + "IconSizeTooltip": "更改遊戲圖示大小", + "MenuBarOptionsShowConsole": "顯示控制台", + "ShaderCachePurgeError": "清除 {0} 著色器快取時出現錯誤: {1}", + "UserErrorNoKeys": "找不到金鑰", + "UserErrorNoFirmware": "找不到韌體", + "UserErrorFirmwareParsingFailed": "韌體解析錯誤", + "UserErrorApplicationNotFound": "找不到應用程式", + "UserErrorUnknown": "未知錯誤", + "UserErrorUndefined": "未定義錯誤", + "UserErrorNoKeysDescription": "Ryujinx 找不到 『prod.keys』 檔案", + "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安裝的韌體", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解密選擇的韌體。這通常是由於金鑰過舊。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 在選中路徑找不到有效的應用程式。", + "UserErrorUnknownDescription": "發生未知錯誤!", + "UserErrorUndefinedDescription": "發生了未定義錯誤!此類錯誤不應出現,請聯絡開發人員!", + "OpenSetupGuideMessage": "開啟設定教學", + "NoUpdate": "沒有新版本", + "TitleUpdateVersionLabel": "版本 {0} - {1}", + "RyujinxInfo": "Ryujinx - 訊息", + "RyujinxConfirm": "Ryujinx - 確認", + "FileDialogAllTypes": "全部類型", + "Never": "從不", + "SwkbdMinCharacters": "至少應為 {0} 個字長", + "SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長", + "SoftwareKeyboard": "軟體鍵盤", + "SoftwareKeyboardModeNumbersOnly": "只接受數字", + "SoftwareKeyboardModeAlphabet": "不支援中日韓統一表意文字字元", + "SoftwareKeyboardModeASCII": "只接受 ASCII 符號", + "DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", + "DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面並配置控制器,或者關閉本視窗。", + "DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n", + "UpdaterRenaming": "正在重新命名舊檔案...", + "UpdaterRenameFailed": "更新過程中無法重新命名檔案: {0}", + "UpdaterAddingFiles": "安裝更新中...", + "UpdaterExtracting": "正在提取更新...", + "UpdaterDownloading": "下載新版本中...", + "Game": "遊戲", + "Docked": "主機模式", + "Handheld": "掌機模式", + "ConnectionError": "連接錯誤。", + "AboutPageDeveloperListMore": "{0} 等開發者...", + "ApiError": "API 錯誤", + "LoadingHeading": "正在啟動 {0}", + "CompilingPPTC": "編譯 PPTC 快取中", + "CompilingShaders": "編譯著色器中", + "AllKeyboards": "所有鍵盤", + "OpenFileDialogTitle": "選擇支援的檔案格式", + "OpenFolderDialogTitle": "選擇一個包含已解開封裝遊戲的資料夾\n", + "AllSupportedFormats": "全部支援的格式", + "RyujinxUpdater": "Ryujinx 更新程式", + "SettingsTabHotkeys": "快捷鍵", + "SettingsTabHotkeysHotkeys": "鍵盤快捷鍵", + "SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "截圖:", + "SettingsTabHotkeysShowUiHotkey": "隱藏使用者介面:", + "SettingsTabHotkeysPauseHotkey": "暫停:", + "SettingsTabHotkeysToggleMuteHotkey": "靜音:", + "ControllerMotionTitle": "體感操作設定", + "ControllerRumbleTitle": "震動設定", + "SettingsSelectThemeFileDialogTitle": "選擇主題檔案", + "SettingsXamlThemeFile": "Xaml 主題檔案", + "AvatarWindowTitle": "管理帳號 - 頭貼", + "Amiibo": "Amiibo", + "Unknown": "未知", + "Usage": "用途", + "Writable": "可寫入", + "SelectDlcDialogTitle": "選擇 DLC 檔案", + "SelectUpdateDialogTitle": "選擇更新檔", + "UserProfileWindowTitle": "管理使用者帳戶", + "CheatWindowTitle": "管理遊戲金手指", + "DlcWindowTitle": "管理遊戲 DLC", + "UpdateWindowTitle": "管理遊戲更新", + "CheatWindowHeading": "金手指可用於 {0} [{1}]", + "BuildId": "版本編號:", + "DlcWindowHeading": "DLC 可用於 {0} [{1}]", + "UserProfilesEditProfile": "編輯所選", + "Cancel": "取消", + "Save": "儲存", + "Discard": "放棄更改", + "UserProfilesSetProfileImage": "設定帳戶頭像", + "UserProfileEmptyNameError": "使用者名稱為必填", + "UserProfileNoImageError": "必須設定帳戶頭像", + "GameUpdateWindowHeading": "更新可用於 {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", + "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", + "UserProfilesName": "使用者名稱:", + "UserProfilesUserId": "使用者 ID:", + "SettingsTabGraphicsBackend": "圖像處理後台架構", + "SettingsTabGraphicsBackendTooltip": "用來圖像處理的後台架構", + "SettingsEnableTextureRecompression": "開啟材質重新壓縮", + "SettingsEnableTextureRecompressionTooltip": "壓縮某些材質以減少 VRAM 使用。\n\n推薦用於小於 4GiB VRAM 的 GPU。\n\n如果不確定請關閉本功能。", + "SettingsTabGraphicsPreferredGpu": "優先選取的 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "選擇支持運行Vulkan圖像處理架構的GPU.\n\n此設定不會影響GPU運行OpenGL.\n\n如果不確定, 請設定標籤為\"dGPU\"的GPU. 如果沒有, 請保留原始設定.", + "SettingsAppRequiredRestartMessage": "必須重啟 Ryujinx", + "SettingsGpuBackendRestartMessage": "圖像處理後台架構或GPU相關設定已被修改。需要重新啟動才能套用。", + "SettingsGpuBackendRestartSubMessage": "你確定要現在重新啟動嗎?", + "RyujinxUpdaterMessage": "你確定要將 Ryujinx 更新到最新版本嗎?", + "SettingsTabHotkeysVolumeUpHotkey": "增加音量:", + "SettingsTabHotkeysVolumeDownHotkey": "降低音量:", + "SettingsEnableMacroHLE": "啟用 Macro HLE", + "SettingsEnableMacroHLETooltip": "GPU 微代碼的高階模擬。\n\n可以提升效能,但可能會導致某些遊戲出現圖形顯示故障。\n\n如果不確定請設定為\"開啟\"。", + "SettingsEnableColorSpacePassthrough": "色彩空間直通", + "SettingsEnableColorSpacePassthroughTooltip": "指揮Vulkan後端直通色彩資訊而不需指定色彩空間,對於廣色域顯示的使用者,這會造成較多抖色,犧牲色彩正確性。", + "VolumeShort": "音量", + "UserProfilesManageSaves": "管理遊戲存檔", + "DeleteUserSave": "你確定要刪除此遊戲的存檔嗎?", + "IrreversibleActionNote": "本動作將無法挽回。", + "SaveManagerHeading": "管理 {0} 的遊戲存檔", + "SaveManagerTitle": "遊戲存檔管理器", + "Name": "名稱", + "Size": "大小", + "Search": "搜尋", + "UserProfilesRecoverLostAccounts": "恢復遺失的帳戶", + "Recover": "恢復", + "UserProfilesRecoverHeading": "在以下帳戶找到了一些遊戲存檔", + "UserProfilesRecoverEmptyList": "沒有可恢復的使用者帳戶", + "GraphicsAATooltip": "在遊戲繪圖上套用抗鋸齒", + "GraphicsAALabel": "抗鋸齒:", + "GraphicsScalingFilterLabel": "縮放過濾器:", + "GraphicsScalingFilterTooltip": "啟用畫幀緩衝區縮放", + "GraphicsScalingFilterLevelLabel": "記錄檔等級", + "GraphicsScalingFilterLevelTooltip": "設定縮放過濾器的強度", + "SmaaLow": "低階 SMAA", + "SmaaMedium": "中階 SMAA", + "SmaaHigh": "高階 SMAA", + "SmaaUltra": "超高階 SMAA", + "UserEditorTitle": "編輯使用者", + "UserEditorTitleCreate": "建立使用者", + "SettingsTabNetworkInterface": "網路介面:", + "NetworkInterfaceTooltip": "用於具有 LAN 功能的網路介面", + "NetworkInterfaceDefault": "預設", + "PackagingShaders": "著色器封裝", + "AboutChangelogButton": "在 GitHub 查看更新日誌", + "AboutChangelogButtonTooltipMessage": "在瀏覽器中打開此Ryujinx版本的更新日誌。" +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/Assets/Styles/Styles.xaml b/src/Ryujinx.Ava/Assets/Styles/Styles.xaml new file mode 100644 index 000000000..f7f64be22 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Styles/Styles.xaml @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + +