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
13namespace dart {
14
15namespace compiler {
16
17namespace ffi {
18
19// Argument #0 is the function pointer.
20const intptr_t kNativeParamsStartAt = 1;
21
22const intptr_t kNoFpuRegister = -1;
23
24// In Soft FP, floats and doubles get passed in integer registers.
25static 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.
34static 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.
48static 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.
64static 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.
72class 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.
226static 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.
242static 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
266NativeCallingConvention::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
275intptr_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
283AbstractTypePtr 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
292intptr_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