1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "vm/compiler/ffi/native_calling_convention.h" |
6 | |
7 | #include "vm/compiler/ffi/marshaller.h" |
8 | #include "vm/compiler/ffi/native_location.h" |
9 | #include "vm/compiler/ffi/native_type.h" |
10 | #include "vm/log.h" |
11 | #include "vm/symbols.h" |
12 | |
13 | namespace dart { |
14 | |
15 | namespace compiler { |
16 | |
17 | namespace ffi { |
18 | |
19 | // Argument #0 is the function pointer. |
20 | const intptr_t kNativeParamsStartAt = 1; |
21 | |
22 | const intptr_t kNoFpuRegister = -1; |
23 | |
24 | // In Soft FP, floats and doubles get passed in integer registers. |
25 | static bool SoftFpAbi() { |
26 | #if defined(TARGET_ARCH_ARM) |
27 | return !TargetCPUFeatures::hardfp_supported(); |
28 | #else |
29 | return false; |
30 | #endif |
31 | } |
32 | |
33 | // In Soft FP, floats are treated as 4 byte ints, and doubles as 8 byte ints. |
34 | static const NativeType& ConvertIfSoftFp(const NativeType& rep, Zone* zone) { |
35 | if (SoftFpAbi() && rep.IsFloat()) { |
36 | ASSERT(rep.IsFloat()); |
37 | if (rep.SizeInBytes() == 4) { |
38 | return *new (zone) NativeFundamentalType(kInt32); |
39 | } |
40 | if (rep.SizeInBytes() == 8) { |
41 | return *new (zone) NativeFundamentalType(kInt64); |
42 | } |
43 | } |
44 | return rep; |
45 | } |
46 | |
47 | // Representations of the arguments to a C signature function. |
48 | static ZoneGrowableArray<const NativeType*>& ArgumentRepresentations( |
49 | const Function& signature, |
50 | Zone* zone) { |
51 | const intptr_t num_arguments = |
52 | signature.num_fixed_parameters() - kNativeParamsStartAt; |
53 | auto& result = *new ZoneGrowableArray<const NativeType*>(zone, num_arguments); |
54 | for (intptr_t i = 0; i < num_arguments; i++) { |
55 | AbstractType& arg_type = AbstractType::Handle( |
56 | zone, signature.ParameterTypeAt(i + kNativeParamsStartAt)); |
57 | const auto& rep = NativeType::FromAbstractType(arg_type, zone); |
58 | result.Add(&rep); |
59 | } |
60 | return result; |
61 | } |
62 | |
63 | // Representation of the result of a C signature function. |
64 | static NativeType& ResultRepresentation(const Function& signature, Zone* zone) { |
65 | AbstractType& result_type = |
66 | AbstractType::Handle(zone, signature.result_type()); |
67 | return NativeType::FromAbstractType(result_type, zone); |
68 | } |
69 | |
70 | // Represents the state of a stack frame going into a call, between allocations |
71 | // of argument locations. |
72 | class ArgumentAllocator : public ValueObject { |
73 | public: |
74 | explicit ArgumentAllocator(Zone* zone) : zone_(zone) {} |
75 | |
76 | const NativeLocation& AllocateArgument(const NativeType& payload_type) { |
77 | const auto& payload_type_converted = ConvertIfSoftFp(payload_type, zone_); |
78 | if (payload_type_converted.IsFloat()) { |
79 | const auto kind = FpuRegKind(payload_type); |
80 | const intptr_t reg_index = FirstFreeFpuRegisterIndex(kind); |
81 | if (reg_index != kNoFpuRegister) { |
82 | AllocateFpuRegisterAtIndex(kind, reg_index); |
83 | if (CallingConventions::kArgumentIntRegXorFpuReg) { |
84 | cpu_regs_used++; |
85 | } |
86 | return *new (zone_) NativeFpuRegistersLocation( |
87 | payload_type, payload_type, kind, reg_index); |
88 | } else { |
89 | BlockAllFpuRegisters(); |
90 | if (CallingConventions::kArgumentIntRegXorFpuReg) { |
91 | ASSERT(cpu_regs_used == CallingConventions::kNumArgRegs); |
92 | } |
93 | } |
94 | } else { |
95 | ASSERT(payload_type_converted.IsInt()); |
96 | // Some calling conventions require the callee to make the lowest 32 bits |
97 | // in registers non-garbage. |
98 | const auto& container_type = |
99 | CallingConventions::kArgumentRegisterExtension == kExtendedTo4 |
100 | ? payload_type_converted.WidenTo4Bytes(zone_) |
101 | : payload_type_converted; |
102 | if (target::kWordSize == 4 && payload_type.SizeInBytes() == 8) { |
103 | if (CallingConventions::kArgumentRegisterAlignment == |
104 | kAlignedToWordSizeBut8AlignedTo8) { |
105 | cpu_regs_used += cpu_regs_used % 2; |
106 | } |
107 | if (cpu_regs_used + 2 <= CallingConventions::kNumArgRegs) { |
108 | const Register register_1 = AllocateCpuRegister(); |
109 | const Register register_2 = AllocateCpuRegister(); |
110 | return *new (zone_) NativeRegistersLocation( |
111 | payload_type, container_type, register_1, register_2); |
112 | } |
113 | } else { |
114 | ASSERT(payload_type.SizeInBytes() <= target::kWordSize); |
115 | if (cpu_regs_used + 1 <= CallingConventions::kNumArgRegs) { |
116 | return *new (zone_) NativeRegistersLocation( |
117 | payload_type, container_type, AllocateCpuRegister()); |
118 | } |
119 | } |
120 | } |
121 | |
122 | return AllocateStack(payload_type); |
123 | } |
124 | |
125 | private: |
126 | static FpuRegisterKind FpuRegKind(const NativeType& payload_type) { |
127 | #if defined(TARGET_ARCH_ARM) |
128 | return FpuRegisterKindFromSize(payload_type.SizeInBytes()); |
129 | #else |
130 | return kQuadFpuReg; |
131 | #endif |
132 | } |
133 | |
134 | Register AllocateCpuRegister() { |
135 | RELEASE_ASSERT(cpu_regs_used >= 0); // Avoids -Werror=array-bounds in GCC. |
136 | ASSERT(cpu_regs_used < CallingConventions::kNumArgRegs); |
137 | |
138 | const auto result = CallingConventions::ArgumentRegisters[cpu_regs_used]; |
139 | if (CallingConventions::kArgumentIntRegXorFpuReg) { |
140 | AllocateFpuRegisterAtIndex(kQuadFpuReg, cpu_regs_used); |
141 | } |
142 | cpu_regs_used++; |
143 | return result; |
144 | } |
145 | |
146 | const NativeLocation& AllocateStack(const NativeType& payload_type) { |
147 | align_stack(payload_type.AlignmentInBytesStack()); |
148 | const intptr_t size = payload_type.SizeInBytes(); |
149 | // If the stack arguments are not packed, the 32 lowest bits should not |
150 | // contain garbage. |
151 | const auto& container_type = |
152 | CallingConventions::kArgumentStackExtension == kExtendedTo4 |
153 | ? payload_type.WidenTo4Bytes(zone_) |
154 | : payload_type; |
155 | const auto& result = *new (zone_) NativeStackLocation( |
156 | payload_type, container_type, CallingConventions::kStackPointerRegister, |
157 | stack_height_in_bytes); |
158 | stack_height_in_bytes += size; |
159 | return result; |
160 | } |
161 | |
162 | void align_stack(intptr_t alignment) { |
163 | stack_height_in_bytes = Utils::RoundUp(stack_height_in_bytes, alignment); |
164 | } |
165 | |
166 | int NumFpuRegisters(FpuRegisterKind kind) { |
167 | #if defined(TARGET_ARCH_ARM) |
168 | if (SoftFpAbi()) return 0; |
169 | if (kind == kSingleFpuReg) return CallingConventions::kNumSFpuArgRegs; |
170 | if (kind == kDoubleFpuReg) return CallingConventions::kNumDFpuArgRegs; |
171 | #endif // defined(TARGET_ARCH_ARM) |
172 | if (kind == kQuadFpuReg) return CallingConventions::kNumFpuArgRegs; |
173 | UNREACHABLE(); |
174 | } |
175 | |
176 | // If no register is free, returns -1. |
177 | int FirstFreeFpuRegisterIndex(FpuRegisterKind kind) { |
178 | const intptr_t size = SizeFromFpuRegisterKind(kind) / 4; |
179 | ASSERT(size == 1 || size == 2 || size == 4); |
180 | if (fpu_reg_parts_used == -1) return kNoFpuRegister; |
181 | const intptr_t mask = (1 << size) - 1; |
182 | intptr_t index = 0; |
183 | while (index < NumFpuRegisters(kind)) { |
184 | const intptr_t mask_shifted = mask << (index * size); |
185 | if ((fpu_reg_parts_used & mask_shifted) == 0) { |
186 | return index; |
187 | } |
188 | index++; |
189 | } |
190 | return kNoFpuRegister; |
191 | } |
192 | |
193 | void AllocateFpuRegisterAtIndex(FpuRegisterKind kind, int index) { |
194 | const intptr_t size = SizeFromFpuRegisterKind(kind) / 4; |
195 | ASSERT(size == 1 || size == 2 || size == 4); |
196 | const intptr_t mask = (1 << size) - 1; |
197 | const intptr_t mask_shifted = (mask << (index * size)); |
198 | ASSERT((mask_shifted & fpu_reg_parts_used) == 0); |
199 | fpu_reg_parts_used |= mask_shifted; |
200 | } |
201 | |
202 | // > The back-filling continues only so long as no VFP CPRC has been |
203 | // > allocated to a slot on the stack. |
204 | // Procedure Call Standard for the Arm Architecture, Release 2019Q1.1 |
205 | // Chapter 7.1 page 28. https://developer.arm.com/docs/ihi0042/h |
206 | // |
207 | // Irrelevant on Android and iOS, as those are both SoftFP. |
208 | // > For floating-point arguments, the Base Standard variant of the |
209 | // > Procedure Call Standard is used. In this variant, floating-point |
210 | // > (and vector) arguments are passed in general purpose registers |
211 | // > (GPRs) instead of in VFP registers) |
212 | // https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv7FunctionCallingConventions.html#//apple_ref/doc/uid/TP40009022-SW1 |
213 | void BlockAllFpuRegisters() { |
214 | // Set all bits to 1. |
215 | fpu_reg_parts_used = -1; |
216 | } |
217 | |
218 | intptr_t cpu_regs_used = 0; |
219 | // Every bit denotes 32 bits of FPU registers. |
220 | intptr_t fpu_reg_parts_used = 0; |
221 | intptr_t stack_height_in_bytes = 0; |
222 | Zone* zone_; |
223 | }; |
224 | |
225 | // Location for the arguments of a C signature function. |
226 | static NativeLocations& ArgumentLocations( |
227 | const ZoneGrowableArray<const NativeType*>& arg_reps, |
228 | Zone* zone) { |
229 | intptr_t num_arguments = arg_reps.length(); |
230 | auto& result = *new NativeLocations(zone, num_arguments); |
231 | |
232 | // Loop through all arguments and assign a register or a stack location. |
233 | ArgumentAllocator frame_state(zone); |
234 | for (intptr_t i = 0; i < num_arguments; i++) { |
235 | const NativeType& rep = *arg_reps[i]; |
236 | result.Add(&frame_state.AllocateArgument(rep)); |
237 | } |
238 | return result; |
239 | } |
240 | |
241 | // Location for the result of a C signature function. |
242 | static NativeLocation& ResultLocation(const NativeType& payload_type, |
243 | Zone* zone) { |
244 | const auto& payload_type_converted = ConvertIfSoftFp(payload_type, zone); |
245 | const auto& container_type = |
246 | CallingConventions::kReturnRegisterExtension == kExtendedTo4 |
247 | ? payload_type_converted.WidenTo4Bytes(zone) |
248 | : payload_type_converted; |
249 | if (container_type.IsFloat()) { |
250 | return *new (zone) NativeFpuRegistersLocation( |
251 | payload_type, container_type, CallingConventions::kReturnFpuReg); |
252 | } |
253 | |
254 | ASSERT(container_type.IsInt() || container_type.IsVoid()); |
255 | if (container_type.SizeInBytes() == 8 && target::kWordSize == 4) { |
256 | return *new (zone) NativeRegistersLocation( |
257 | payload_type, container_type, CallingConventions::kReturnReg, |
258 | CallingConventions::kSecondReturnReg); |
259 | } |
260 | |
261 | ASSERT(container_type.SizeInBytes() <= target::kWordSize); |
262 | return *new (zone) NativeRegistersLocation(payload_type, container_type, |
263 | CallingConventions::kReturnReg); |
264 | } |
265 | |
266 | NativeCallingConvention::NativeCallingConvention(Zone* zone, |
267 | const Function& c_signature) |
268 | : zone_(ASSERT_NOTNULL(zone)), |
269 | c_signature_(c_signature), |
270 | arg_locs_(ArgumentLocations(ArgumentRepresentations(c_signature_, zone_), |
271 | zone_)), |
272 | result_loc_( |
273 | ResultLocation(ResultRepresentation(c_signature_, zone_), zone_)) {} |
274 | |
275 | intptr_t NativeCallingConvention::num_args() const { |
276 | ASSERT(c_signature_.NumOptionalParameters() == 0); |
277 | ASSERT(c_signature_.NumOptionalPositionalParameters() == 0); |
278 | |
279 | // Subtract the #0 argument, the function pointer. |
280 | return c_signature_.num_fixed_parameters() - kNativeParamsStartAt; |
281 | } |
282 | |
283 | AbstractTypePtr NativeCallingConvention::CType(intptr_t arg_index) const { |
284 | if (arg_index == kResultIndex) { |
285 | return c_signature_.result_type(); |
286 | } |
287 | |
288 | // Skip #0 argument, the function pointer. |
289 | return c_signature_.ParameterTypeAt(arg_index + kNativeParamsStartAt); |
290 | } |
291 | |
292 | intptr_t NativeCallingConvention::StackTopInBytes() const { |
293 | const intptr_t num_arguments = arg_locs_.length(); |
294 | intptr_t max_height_in_bytes = 0; |
295 | for (intptr_t i = 0; i < num_arguments; i++) { |
296 | if (Location(i).IsStack()) { |
297 | const intptr_t height = Location(i).AsStack().offset_in_bytes() + |
298 | Location(i).container_type().SizeInBytes(); |
299 | max_height_in_bytes = Utils::Maximum(height, max_height_in_bytes); |
300 | } |
301 | } |
302 | return Utils::RoundUp(max_height_in_bytes, compiler::target::kWordSize); |
303 | } |
304 | |
305 | } // namespace ffi |
306 | |
307 | } // namespace compiler |
308 | |
309 | } // namespace dart |
310 | |