aboutsummaryrefslogtreecommitdiff
path: root/ChocolArm64/Instructions/InstEmitFlowHelper.cs
blob: a6091a5711d76e4c80a0f0fa370cf4679bb3f641 (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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
using ChocolArm64.State;
using ChocolArm64.Translation;
using System.Reflection;
using System.Reflection.Emit;

namespace ChocolArm64.Instructions
{
    static class InstEmitFlowHelper
    {
        public static void EmitCall(ILEmitterCtx context, long imm)
        {
            if (context.Tier == TranslationTier.Tier0)
            {
                context.EmitStoreState();

                context.TranslateAhead(imm);

                context.EmitLdc_I8(imm);

                context.Emit(OpCodes.Ret);

                return;
            }

            if (!context.TryOptEmitSubroutineCall())
            {
                context.HasSlowCall = true;

                context.EmitStoreState();

                context.TranslateAhead(imm);

                context.EmitLdarg(TranslatedSub.StateArgIdx);

                context.EmitFieldLoad(typeof(CpuThreadState).GetField(nameof(CpuThreadState.CurrentTranslator),
                    BindingFlags.Instance |
                    BindingFlags.NonPublic));

                context.EmitLdarg(TranslatedSub.StateArgIdx);
                context.EmitLdc_I8(imm);
                context.EmitLdc_I4((int)CallType.Call);

                context.EmitPrivateCall(typeof(Translator), nameof(Translator.GetOrTranslateSubroutine));

                context.EmitLdarg(TranslatedSub.StateArgIdx);
                context.EmitLdarg(TranslatedSub.MemoryArgIdx);

                context.EmitCall(typeof(TranslatedSub), nameof(TranslatedSub.Execute));
            }

            EmitContinueOrReturnCheck(context);
        }

        public static void EmitVirtualCall(ILEmitterCtx context)
        {
            EmitVirtualCallOrJump(context, isJump: false);
        }

        public static void EmitVirtualJump(ILEmitterCtx context)
        {
            EmitVirtualCallOrJump(context, isJump: true);
        }

        private static void EmitVirtualCallOrJump(ILEmitterCtx context, bool isJump)
        {
            if (context.Tier == TranslationTier.Tier0)
            {
                context.Emit(OpCodes.Ret);
            }
            else
            {
                context.EmitSttmp();
                context.EmitLdarg(TranslatedSub.StateArgIdx);

                context.EmitFieldLoad(typeof(CpuThreadState).GetField(nameof(CpuThreadState.CurrentTranslator),
                    BindingFlags.Instance |
                    BindingFlags.NonPublic));

                context.EmitLdarg(TranslatedSub.StateArgIdx);
                context.EmitLdtmp();
                context.EmitLdc_I4(isJump
                    ? (int)CallType.VirtualJump
                    : (int)CallType.VirtualCall);

                context.EmitPrivateCall(typeof(Translator), nameof(Translator.GetOrTranslateSubroutine));

                context.EmitLdarg(TranslatedSub.StateArgIdx);
                context.EmitLdarg(TranslatedSub.MemoryArgIdx);

                if (isJump)
                {
                    //The tail prefix allows the JIT to jump to the next function,
                    //while releasing the stack space used by the current one.
                    //This is ideal for BR ARM instructions, which are
                    //basically indirect tail calls.
                    context.Emit(OpCodes.Tailcall);
                }

                MethodInfo mthdInfo = typeof(ArmSubroutine).GetMethod("Invoke");

                context.EmitCall(mthdInfo, isVirtual: true);

                if (!isJump)
                {
                    EmitContinueOrReturnCheck(context);
                }
                else
                {
                    context.Emit(OpCodes.Ret);
                }
            }
        }

        private static void EmitContinueOrReturnCheck(ILEmitterCtx context)
        {
            //Note: The return value of the called method will be placed
            //at the Stack, the return value is always a Int64 with the
            //return address of the function. We check if the address is
            //correct, if it isn't we keep returning until we reach the dispatcher.
            if (context.CurrBlock.Next != null)
            {
                context.Emit(OpCodes.Dup);

                context.EmitLdc_I8(context.CurrOp.Position + 4);

                ILLabel lblContinue = new ILLabel();

                context.Emit(OpCodes.Beq_S, lblContinue);
                context.Emit(OpCodes.Ret);

                context.MarkLabel(lblContinue);

                context.Emit(OpCodes.Pop);

                context.EmitLoadState();
            }
            else
            {
                context.Emit(OpCodes.Ret);
            }
        }
    }
}