1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | // |
5 | |
6 | #ifndef __ARGDESTINATION_H__ |
7 | #define __ARGDESTINATION_H__ |
8 | |
9 | // The ArgDestination class represents a destination location of an argument. |
10 | class ArgDestination |
11 | { |
12 | // Base address to which the m_offset is applied to get the actual argument location. |
13 | PTR_VOID m_base; |
14 | // Offset of the argument relative to the m_base. On AMD64 on Unix, it can have a special |
15 | // value that represent a struct that contain both general purpose and floating point fields |
16 | // passed in registers. |
17 | int m_offset; |
18 | // For structs passed in registers, this member points to an ArgLocDesc that contains |
19 | // details on the layout of the struct in general purpose and floating point registers. |
20 | ArgLocDesc* m_argLocDescForStructInRegs; |
21 | |
22 | public: |
23 | |
24 | // Construct the ArgDestination |
25 | ArgDestination(PTR_VOID base, int offset, ArgLocDesc* argLocDescForStructInRegs) |
26 | : m_base(base), |
27 | m_offset(offset), |
28 | m_argLocDescForStructInRegs(argLocDescForStructInRegs) |
29 | { |
30 | LIMITED_METHOD_CONTRACT; |
31 | #if defined(UNIX_AMD64_ABI) |
32 | _ASSERTE((argLocDescForStructInRegs != NULL) || (offset != TransitionBlock::StructInRegsOffset)); |
33 | #elif defined(_TARGET_ARM64_) |
34 | // This assert is not interesting on arm64. argLocDescForStructInRegs could be |
35 | // initialized if the args are being enregistered. |
36 | #else |
37 | _ASSERTE(argLocDescForStructInRegs == NULL); |
38 | #endif |
39 | } |
40 | |
41 | // Get argument destination address for arguments that are not structs passed in registers. |
42 | PTR_VOID GetDestinationAddress() |
43 | { |
44 | LIMITED_METHOD_CONTRACT; |
45 | return dac_cast<PTR_VOID>(dac_cast<TADDR>(m_base) + m_offset); |
46 | } |
47 | |
48 | #if defined(_TARGET_ARM64_) |
49 | #ifndef DACCESS_COMPILE |
50 | |
51 | // Returns true if the ArgDestination represents an HFA struct |
52 | bool IsHFA() |
53 | { |
54 | return m_argLocDescForStructInRegs != NULL; |
55 | } |
56 | |
57 | // Copy struct argument into registers described by the current ArgDestination. |
58 | // Arguments: |
59 | // src = source data of the structure |
60 | // fieldBytes - size of the structure |
61 | void CopyHFAStructToRegister(void *src, int fieldBytes) |
62 | { |
63 | // We are either copying either a float or double HFA and need to |
64 | // enregister each field. |
65 | |
66 | int floatRegCount = m_argLocDescForStructInRegs->m_cFloatReg; |
67 | bool typeFloat = m_argLocDescForStructInRegs->m_isSinglePrecision; |
68 | void* dest = this->GetDestinationAddress(); |
69 | |
70 | if (typeFloat) |
71 | { |
72 | for (int i = 0; i < floatRegCount; ++i) |
73 | { |
74 | // Copy 4 bytes on 8 bytes alignment |
75 | *((UINT64*)dest + i) = *((UINT32*)src + i); |
76 | } |
77 | } |
78 | else |
79 | { |
80 | // We can just do a memcpy. |
81 | memcpyNoGCRefs(dest, src, fieldBytes); |
82 | } |
83 | } |
84 | |
85 | #endif // !DACCESS_COMPILE |
86 | #endif // defined(_TARGET_ARM64_) |
87 | |
88 | #if defined(UNIX_AMD64_ABI) |
89 | |
90 | // Returns true if the ArgDestination represents a struct passed in registers. |
91 | bool IsStructPassedInRegs() |
92 | { |
93 | LIMITED_METHOD_CONTRACT; |
94 | return m_offset == TransitionBlock::StructInRegsOffset; |
95 | } |
96 | |
97 | // Get destination address for floating point fields of a struct passed in registers. |
98 | PTR_VOID GetStructFloatRegDestinationAddress() |
99 | { |
100 | LIMITED_METHOD_CONTRACT; |
101 | _ASSERTE(IsStructPassedInRegs()); |
102 | int offset = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_argLocDescForStructInRegs->m_idxFloatReg * 16; |
103 | return dac_cast<PTR_VOID>(dac_cast<TADDR>(m_base) + offset); |
104 | } |
105 | |
106 | // Get destination address for non-floating point fields of a struct passed in registers. |
107 | PTR_VOID GetStructGenRegDestinationAddress() |
108 | { |
109 | LIMITED_METHOD_CONTRACT; |
110 | _ASSERTE(IsStructPassedInRegs()); |
111 | int offset = TransitionBlock::GetOffsetOfArgumentRegisters() + m_argLocDescForStructInRegs->m_idxGenReg * 8; |
112 | return dac_cast<PTR_VOID>(dac_cast<TADDR>(m_base) + offset); |
113 | } |
114 | |
115 | #ifndef DACCESS_COMPILE |
116 | // Zero struct argument stored in registers described by the current ArgDestination. |
117 | // Arguments: |
118 | // fieldBytes - size of the structure |
119 | void ZeroStructInRegisters(int fieldBytes) |
120 | { |
121 | STATIC_CONTRACT_NOTHROW; |
122 | STATIC_CONTRACT_GC_NOTRIGGER; |
123 | STATIC_CONTRACT_FORBID_FAULT; |
124 | STATIC_CONTRACT_MODE_COOPERATIVE; |
125 | |
126 | // To zero the struct, we create a zero filled array of large enough size and |
127 | // then copy it to the registers. It is implemented this way to keep the complexity |
128 | // of dealing with the eightbyte classification in single function. |
129 | // This function is used rarely and so the overhead of reading the zeros from |
130 | // the stack is negligible. |
131 | long long zeros[CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS] = {}; |
132 | _ASSERTE(sizeof(zeros) >= fieldBytes); |
133 | |
134 | CopyStructToRegisters(zeros, fieldBytes, 0); |
135 | } |
136 | |
137 | // Copy struct argument into registers described by the current ArgDestination. |
138 | // Arguments: |
139 | // src = source data of the structure |
140 | // fieldBytes - size of the structure |
141 | // destOffset - nonzero when copying values into Nullable<T>, it is the offset |
142 | // of the T value inside of the Nullable<T> |
143 | void CopyStructToRegisters(void *src, int fieldBytes, int destOffset) |
144 | { |
145 | STATIC_CONTRACT_NOTHROW; |
146 | STATIC_CONTRACT_GC_NOTRIGGER; |
147 | STATIC_CONTRACT_FORBID_FAULT; |
148 | STATIC_CONTRACT_MODE_COOPERATIVE; |
149 | |
150 | _ASSERTE(IsStructPassedInRegs()); |
151 | |
152 | BYTE* genRegDest = (BYTE*)GetStructGenRegDestinationAddress() + destOffset; |
153 | BYTE* floatRegDest = (BYTE*)GetStructFloatRegDestinationAddress(); |
154 | INDEBUG(int remainingBytes = fieldBytes;) |
155 | |
156 | EEClass* eeClass = m_argLocDescForStructInRegs->m_eeClass; |
157 | _ASSERTE(eeClass != NULL); |
158 | |
159 | // We start at the first eightByte that the destOffset didn't skip completely. |
160 | for (int i = destOffset / 8; i < eeClass->GetNumberEightBytes(); i++) |
161 | { |
162 | int eightByteSize = eeClass->GetEightByteSize(i); |
163 | SystemVClassificationType eightByteClassification = eeClass->GetEightByteClassification(i); |
164 | |
165 | // Adjust the size of the first eightByte by the destOffset |
166 | eightByteSize -= (destOffset & 7); |
167 | destOffset = 0; |
168 | |
169 | _ASSERTE(remainingBytes >= eightByteSize); |
170 | |
171 | if (eightByteClassification == SystemVClassificationTypeSSE) |
172 | { |
173 | if (eightByteSize == 8) |
174 | { |
175 | *(UINT64*)floatRegDest = *(UINT64*)src; |
176 | } |
177 | else |
178 | { |
179 | _ASSERTE(eightByteSize == 4); |
180 | *(UINT32*)floatRegDest = *(UINT32*)src; |
181 | } |
182 | floatRegDest += 16; |
183 | } |
184 | else |
185 | { |
186 | if (eightByteSize == 8) |
187 | { |
188 | _ASSERTE((eightByteClassification == SystemVClassificationTypeInteger) || |
189 | (eightByteClassification == SystemVClassificationTypeIntegerReference) || |
190 | (eightByteClassification == SystemVClassificationTypeIntegerByRef)); |
191 | |
192 | _ASSERTE(IS_ALIGNED((SIZE_T)genRegDest, 8)); |
193 | *(UINT64*)genRegDest = *(UINT64*)src; |
194 | } |
195 | else |
196 | { |
197 | _ASSERTE(eightByteClassification == SystemVClassificationTypeInteger); |
198 | memcpyNoGCRefs(genRegDest, src, eightByteSize); |
199 | } |
200 | |
201 | genRegDest += eightByteSize; |
202 | } |
203 | |
204 | src = (BYTE*)src + eightByteSize; |
205 | INDEBUG(remainingBytes -= eightByteSize;) |
206 | } |
207 | |
208 | _ASSERTE(remainingBytes == 0); |
209 | } |
210 | |
211 | #endif //DACCESS_COMPILE |
212 | |
213 | // Report managed object pointers in the struct in registers |
214 | // Arguments: |
215 | // fn - promotion function to apply to each managed object pointer |
216 | // sc - scan context to pass to the promotion function |
217 | // fieldBytes - size of the structure |
218 | void ReportPointersFromStructInRegisters(promote_func *fn, ScanContext *sc, int fieldBytes) |
219 | { |
220 | LIMITED_METHOD_CONTRACT; |
221 | |
222 | // SPAN-TODO: GC reporting - https://github.com/dotnet/coreclr/issues/8517 |
223 | |
224 | _ASSERTE(IsStructPassedInRegs()); |
225 | |
226 | TADDR genRegDest = dac_cast<TADDR>(GetStructGenRegDestinationAddress()); |
227 | INDEBUG(int remainingBytes = fieldBytes;) |
228 | |
229 | EEClass* eeClass = m_argLocDescForStructInRegs->m_eeClass; |
230 | _ASSERTE(eeClass != NULL); |
231 | |
232 | for (int i = 0; i < eeClass->GetNumberEightBytes(); i++) |
233 | { |
234 | int eightByteSize = eeClass->GetEightByteSize(i); |
235 | SystemVClassificationType eightByteClassification = eeClass->GetEightByteClassification(i); |
236 | |
237 | _ASSERTE(remainingBytes >= eightByteSize); |
238 | |
239 | if (eightByteClassification != SystemVClassificationTypeSSE) |
240 | { |
241 | if ((eightByteClassification == SystemVClassificationTypeIntegerReference) || |
242 | (eightByteClassification == SystemVClassificationTypeIntegerByRef)) |
243 | { |
244 | _ASSERTE(eightByteSize == 8); |
245 | _ASSERTE(IS_ALIGNED((SIZE_T)genRegDest, 8)); |
246 | |
247 | uint32_t flags = eightByteClassification == SystemVClassificationTypeIntegerByRef ? GC_CALL_INTERIOR : 0; |
248 | (*fn)(dac_cast<PTR_PTR_Object>(genRegDest), sc, flags); |
249 | } |
250 | |
251 | genRegDest += eightByteSize; |
252 | } |
253 | |
254 | INDEBUG(remainingBytes -= eightByteSize;) |
255 | } |
256 | |
257 | _ASSERTE(remainingBytes == 0); |
258 | } |
259 | |
260 | #endif // UNIX_AMD64_ABI |
261 | |
262 | }; |
263 | |
264 | #endif // __ARGDESTINATION_H__ |
265 | |