ffi.c (5450B)
1 /* External (host-ABI) call marshaller. 2 * 3 * The engine has already classified arguments into integer-register and 4 * fp-register slots per the call's ABIFuncInfo (sret pointer first, byval as a 5 * pointer, aggregate-in-regs split into chunks). On the supported ABIs (SysV 6 * x64, AAPCS64, RV64 LP64D) integer and fp arguments are assigned from 7 * independent register sequences, so calling through one maximal prototype 8 * `T(u64 x8, <fp> x8)` places the first `nint` integers and `nfp` fp values in 9 * the correct registers regardless of their original interleaving; unused 10 * trailing slots are zero and ignored by the callee. 11 * 12 * Two fp-argument shapes are needed because a 32-bit `float` and a 64-bit 13 * `double` occupy the fp register differently (single vs double precision): the 14 * engine picks `args_fp_is_float` when every fp arg is a 4-byte single, and the 15 * dispatcher selects the `float`-parameter thunk family. A call mixing float 16 * and double fp args within one signature is rejected upstream. 17 * 18 * Returns mirror this: a value comes back in one or two registers, classified 19 * into int/fp parts. Two-register returns dispatch through struct-returning 20 * thunks whose field types steer the ABI to the right return registers; the 21 * caller copies each part's bytes into the aggregate destination. 22 * 23 * The casts deliberately mismatch the callee's real prototype — the classic 24 * libffi-lite trick. That trips clang's -fsanitize=function, so the dispatcher 25 * opts out of that one check (clang only; kit self-host never enables it). */ 26 27 #include <string.h> 28 29 #include "core/core.h" 30 #include "interp/interp.h" 31 32 /* 8 integer + 8 fp register slots, fp as double or as single. */ 33 #define IPARAMS u64, u64, u64, u64, u64, u64, u64, u64 34 #define DPARAMS double, double, double, double, double, double, double, double 35 #define FPARAMS float, float, float, float, float, float, float, float 36 37 #define IVALS(a) \ 38 (a)->iargs[0], (a)->iargs[1], (a)->iargs[2], (a)->iargs[3], (a)->iargs[4], \ 39 (a)->iargs[5], (a)->iargs[6], (a)->iargs[7] 40 #define DVALS(a) \ 41 (a)->fargs[0], (a)->fargs[1], (a)->fargs[2], (a)->fargs[3], (a)->fargs[4], \ 42 (a)->fargs[5], (a)->fargs[6], (a)->fargs[7] 43 #define FVALS(a) \ 44 (a)->fargs_f[0], (a)->fargs_f[1], (a)->fargs_f[2], (a)->fargs_f[3], \ 45 (a)->fargs_f[4], (a)->fargs_f[5], (a)->fargs_f[6], (a)->fargs_f[7] 46 47 /* Two-register return shapes; the field types pick the return-register classes 48 * (e.g. {u64,u64}=RAX,RDX / X0,X1; {u64,double}=RAX,XMM0 / X0,V0; etc). */ 49 typedef struct { 50 u64 a, b; 51 } R_ii; 52 typedef struct { 53 u64 a; 54 double b; 55 } R_id; 56 typedef struct { 57 double a; 58 u64 b; 59 } R_di; 60 typedef struct { 61 double a, b; 62 } R_dd; 63 64 /* double-fp-arg thunks, by return shape */ 65 typedef u64 (*t_i_d)(IPARAMS, DPARAMS); 66 typedef double (*t_d_d)(IPARAMS, DPARAMS); 67 typedef float (*t_f_d)(IPARAMS, DPARAMS); 68 typedef R_ii (*t_ii_d)(IPARAMS, DPARAMS); 69 typedef R_id (*t_id_d)(IPARAMS, DPARAMS); 70 typedef R_di (*t_di_d)(IPARAMS, DPARAMS); 71 typedef R_dd (*t_dd_d)(IPARAMS, DPARAMS); 72 /* float-fp-arg thunks, scalar returns only (mixed signatures are rare) */ 73 typedef u64 (*t_i_f)(IPARAMS, FPARAMS); 74 typedef double (*t_d_f)(IPARAMS, FPARAMS); 75 typedef float (*t_f_f)(IPARAMS, FPARAMS); 76 77 #if defined(__clang__) 78 __attribute__((no_sanitize("function"))) 79 #endif 80 int interp_ffi_invoke(void* fp, const InterpFfiArgs* a, u64 out[2], 81 const char** reason) { 82 int flt; 83 out[0] = 0; 84 out[1] = 0; 85 if (!fp) { 86 *reason = "external call target is null"; 87 return 1; 88 } 89 if (a->nint > 8u || a->nfp > 8u) { 90 *reason = "external call has too many register arguments"; 91 return 1; 92 } 93 flt = a->args_fp_is_float; 94 95 /* void return */ 96 if (a->ret_is_void || a->ret_nparts == 0u) { 97 if (flt) 98 ((t_i_f)fp)(IVALS(a), FVALS(a)); 99 else 100 ((t_i_d)fp)(IVALS(a), DVALS(a)); 101 return 0; 102 } 103 104 /* single-register return */ 105 if (a->ret_nparts == 1u) { 106 if (a->ret_fp[0]) { 107 if (a->ret_size[0] == 4u) { 108 float r = flt ? ((t_f_f)fp)(IVALS(a), FVALS(a)) 109 : ((t_f_d)fp)(IVALS(a), DVALS(a)); 110 u32 b; 111 memcpy(&b, &r, 4); 112 out[0] = b; 113 } else { 114 double r = flt ? ((t_d_f)fp)(IVALS(a), FVALS(a)) 115 : ((t_d_d)fp)(IVALS(a), DVALS(a)); 116 memcpy(&out[0], &r, 8); 117 } 118 } else { 119 out[0] = flt ? ((t_i_f)fp)(IVALS(a), FVALS(a)) 120 : ((t_i_d)fp)(IVALS(a), DVALS(a)); 121 } 122 return 0; 123 } 124 125 /* two-register return. A float fp arg combined with a struct return is rare; 126 * require double fp args here (engine also rejects 4-byte fp return parts). 127 */ 128 if (flt) { 129 *reason = "external call: float args with multi-register return"; 130 return 1; 131 } 132 if (!a->ret_fp[0] && !a->ret_fp[1]) { 133 R_ii r = ((t_ii_d)fp)(IVALS(a), DVALS(a)); 134 out[0] = r.a; 135 out[1] = r.b; 136 } else if (!a->ret_fp[0] && a->ret_fp[1]) { 137 R_id r = ((t_id_d)fp)(IVALS(a), DVALS(a)); 138 out[0] = r.a; 139 memcpy(&out[1], &r.b, 8); 140 } else if (a->ret_fp[0] && !a->ret_fp[1]) { 141 R_di r = ((t_di_d)fp)(IVALS(a), DVALS(a)); 142 memcpy(&out[0], &r.a, 8); 143 out[1] = r.b; 144 } else { 145 R_dd r = ((t_dd_d)fp)(IVALS(a), DVALS(a)); 146 memcpy(&out[0], &r.a, 8); 147 memcpy(&out[1], &r.b, 8); 148 } 149 return 0; 150 }