@@ -230,6 +230,144 @@ bool SignaturesMatch(const FFIFunction& fn,
230230 return true ;
231231}
232232
233+ namespace {
234+
235+ bool IsFastCallEligibleFFIType (ffi_type* type) {
236+ // Accept all numeric types, pointer, and void (void OK as return only).
237+ // Rejects struct types (not yet supported in fast-call path).
238+ return type == &ffi_type_void || type == &ffi_type_sint8 ||
239+ type == &ffi_type_uint8 || type == &ffi_type_sint16 ||
240+ type == &ffi_type_uint16 || type == &ffi_type_sint32 ||
241+ type == &ffi_type_uint32 || type == &ffi_type_sint64 ||
242+ type == &ffi_type_uint64 || type == &ffi_type_float ||
243+ type == &ffi_type_double || type == &ffi_type_pointer;
244+ }
245+
246+ bool IsFunctionTypeName (const std::string& name) {
247+ return name == " function" ;
248+ }
249+
250+ // Check if an FFI type occupies a floating-point register.
251+ bool IsFFITypeFloat (ffi_type* type) {
252+ return type == &ffi_type_float || type == &ffi_type_double;
253+ }
254+
255+ // Check if an FFI type name maps to a kBuffer (kV8Value) argument in the
256+ // fast-call path, which consumes an extra GP register for the helper call.
257+ bool IsBufferTypeName (const std::string& name) {
258+ return name == " buffer" || name == " arraybuffer" ;
259+ }
260+
261+ } // namespace
262+
263+ bool IsFastCallEligible (const FFIFunction& fn, const char ** out_reason) {
264+ static const char * dummy = " " ;
265+ if (out_reason == nullptr ) out_reason = &dummy;
266+
267+ // Check that a platform stub emitter exists for the current ABI.
268+ // Stub emitters cover AArch64 (Linux/macOS/FreeBSD/Windows) and
269+ // x86_64 (SysV: Linux/macOS/FreeBSD, Win64: Windows). Other platforms
270+ // fall back to libffi.
271+ #if !defined(__aarch64__) && !defined(_M_ARM64) && !defined(__x86_64__)
272+ *out_reason = " no platform stub emitter" ;
273+ return false ;
274+ #endif
275+
276+ // Check return type eligibility.
277+ if (!IsFastCallEligibleFFIType (fn.return_type )) {
278+ *out_reason = " unsupported return type" ;
279+ return false ;
280+ }
281+ if (IsFunctionTypeName (fn.return_type_name )) {
282+ *out_reason = " return type is function" ;
283+ return false ;
284+ }
285+
286+ // V8's fast-call lowering caps the C-side arg count. With HasReceiver=kNo
287+ // there's no implicit receiver in the count, so this is the user-arg cap.
288+ // V8's hard limit is 8 args; signatures over that fall back to libffi.
289+ if (fn.args .size () > 8 ) {
290+ *out_reason = " argument count exceeds V8 fast-call cap" ;
291+ return false ;
292+ }
293+
294+ // Per-ABI register caps for arguments that must be passed in registers.
295+ // If an arg can't fit in a register, it goes on the stack — which the
296+ // current trampoline generators don't support.
297+ size_t gp_count = 0 ;
298+ size_t fp_count = 0 ;
299+ bool has_buffer_arg = false ;
300+ for (size_t i = 0 ; i < fn.args .size (); ++i) {
301+ ffi_type* t = fn.args [i];
302+ const std::string& name = fn.arg_type_names [i];
303+
304+ if (!IsFastCallEligibleFFIType (t)) {
305+ *out_reason = " unsupported arg type" ;
306+ return false ;
307+ }
308+ // `void` is fine as a return type but has no register slot, so it cannot
309+ // appear in `args`.
310+ if (t == &ffi_type_void) {
311+ *out_reason = " void cannot be an argument type" ;
312+ return false ;
313+ }
314+ if (IsFunctionTypeName (name)) {
315+ *out_reason = " arg is function" ;
316+ return false ;
317+ }
318+
319+ // `buffer`/`arraybuffer` args arrive as kV8Value in the V8 fast-call
320+ // signature, consuming an extra GP register for the helper call.
321+ if (IsBufferTypeName (name)) {
322+ has_buffer_arg = true ;
323+ }
324+
325+ // Count register classes used by each argument.
326+ if (IsFFITypeFloat (t)) {
327+ fp_count++;
328+ } else {
329+ gp_count++;
330+ }
331+ }
332+
333+ // Platform-specific register pressure limits.
334+ #if defined(__aarch64__) || defined(_M_ARM64)
335+ // AArch64: 8 FP registers (v0-v7) + up to 7 GP registers per trampoline
336+ // constraint (the 8th GP slot is consumed by the helper call for buffer
337+ // args). Buffer args and float args can't coexist — the helper call would
338+ // clobber FP state.
339+ const size_t effective_gp = gp_count + (has_buffer_arg ? 1 : 0 );
340+ if (has_buffer_arg && fp_count != 0 ) {
341+ *out_reason = " buffer and float args cannot coexist on AArch64" ;
342+ return false ;
343+ }
344+ if (effective_gp > 7 || fp_count > 8 ) {
345+ *out_reason = " argument count exceeds AArch64 register limit" ;
346+ return false ;
347+ }
348+ #elif defined(__x86_64__)
349+ #if defined(_WIN32)
350+ // x86_64 Win64: 4 register slots total (rcx, rdx, r8, r9 for GP;
351+ // xmm0-xmm3 for FP). GP + FP share the positional limit — once 4 slots
352+ // are consumed, remaining args go to the stack.
353+ if (gp_count + fp_count > 3 ) {
354+ *out_reason = " argument count exceeds x86_64 Win64 register limit" ;
355+ return false ;
356+ }
357+ #else
358+ // x86_64 SysV: 6 GP registers (rdi, rsi, rdx, rcx, r8, r9) + 8 FP
359+ // registers (xmm0-xmm7).
360+ if (gp_count > 6 || fp_count > 8 ) {
361+ *out_reason = " argument count exceeds x86_64 SysV register limit" ;
362+ return false ;
363+ }
364+ #endif // _WIN32
365+ #endif // __x86_64__
366+
367+ *out_reason = " " ;
368+ return true ;
369+ }
370+
233371bool IsSBEligibleFFIType (ffi_type* type) {
234372 return type == &ffi_type_void || type == &ffi_type_sint8 ||
235373 type == &ffi_type_uint8 || type == &ffi_type_sint16 ||
0 commit comments