aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs
blob: d374c08a5d10216b9abacc49a4657ce31e609dd2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using ARMeilleure.Translation;
using NUnit.Framework;
using Ryujinx.Cpu.Jit;
using Ryujinx.Tests.Memory;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Ryujinx.Tests.Cpu
{
    internal class EnvironmentTests
    {
        private static Translator _translator;

        private void EnsureTranslator()
        {
            // Create a translator, as one is needed to register the signal handler or emit methods.
            _translator ??= new Translator(new JitMemoryAllocator(), new MockMemoryManager(), true);
        }

        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        private float GetDenormal()
        {
            return BitConverter.Int32BitsToSingle(1);
        }

        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        private float GetZero()
        {
            return BitConverter.Int32BitsToSingle(0);
        }

        /// <summary>
        /// This test ensures that managed methods do not reset floating point control flags.
        /// This is used to avoid changing control flags when running methods that don't require it, such as SVC calls, software memory...
        /// </summary>
        [Test]
        public void FpFlagsPInvoke()
        {
            EnsureTranslator();

            // Subnormal results are not flushed to zero by default.
            // This operation should not be allowed to do constant propagation, hence the methods that explicitly disallow inlining.
            Assert.AreNotEqual(GetDenormal() + GetZero(), 0f);

            bool methodCalled = false;
            bool isFz = false;

            var managedMethod = () =>
            {
                // Floating point math should not modify fp flags.
                float test = 2f * 3.5f;

                if (test < 4f)
                {
                    throw new System.Exception("Sanity check.");
                }

                isFz = GetDenormal() + GetZero() == 0f;

                try
                {
                    if (test >= 4f)
                    {
                        throw new System.Exception("Always throws.");
                    }
                }
                catch
                {
                    // Exception handling should not modify fp flags.

                    methodCalled = true;
                }
            };

            var method = TranslatorTestMethods.GenerateFpFlagsPInvokeTest();

            // This method sets flush-to-zero and then calls the managed method.
            // Before and after setting the flags, it ensures subnormal addition works as expected.
            // It returns a positive result if any tests fail, and 0 on success (or if the platform cannot change FP flags)
            int result = method(Marshal.GetFunctionPointerForDelegate(managedMethod));

            // Subnormal results are not flushed to zero by default, which we should have returned to exiting the method.
            Assert.AreNotEqual(GetDenormal() + GetZero(), 0f);

            Assert.True(result == 0);
            Assert.True(methodCalled);
            Assert.True(isFz);
        }
    }
}