1616#include "vm_sync.h"
1717#include "internal/fixnum.h"
1818#include "internal/string.h"
19+ #include "ruby/jit_native.h"
1920
2021enum jit_bindgen_constants {
2122 // Field offsets for the RObject struct
@@ -31,6 +32,9 @@ enum jit_bindgen_constants {
3132 RUBY_OFFSET_EC_INTERRUPT_MASK = offsetof(rb_execution_context_t , interrupt_mask ),
3233 RUBY_OFFSET_EC_THREAD_PTR = offsetof(rb_execution_context_t , thread_ptr ),
3334 RUBY_OFFSET_EC_RACTOR_ID = offsetof(rb_execution_context_t , ractor_id ),
35+
36+ // Field offset for RTypedData.data (used for FFI::Pointer address extraction)
37+ RUBY_OFFSET_RTYPEDDATA_DATA = offsetof(struct RTypedData , data ),
3438};
3539
3640// Manually bound in rust since this is out-of-range of `int`,
@@ -797,3 +801,250 @@ rb_jit_shape_capacity(shape_id_t shape_id)
797801{
798802 return RSHAPE_CAPACITY (shape_id );
799803}
804+
805+ // --- Native call hints for JIT optimization of FFI-style calls ---
806+ //
807+ // Two-level st_table: klass (VALUE) -> st_table(mid (ID) -> hint ptr).
808+ // Populated at runtime by C extensions via rb_jit_hint_native_call().
809+ // Queried by the JIT compiler via rb_jit_find_native_call_hint().
810+
811+ static st_table * jit_native_hints = NULL ;
812+
813+ void
814+ rb_jit_hint_native_call (VALUE klass , ID mid , void * func_ptr ,
815+ int argc , const int * arg_types , int ret_type )
816+ {
817+ if (argc < 0 || argc > RB_JIT_NATIVE_MAX_ARGS ) {
818+ rb_raise (rb_eArgError ,
819+ "rb_jit_hint_native_call: argc %d out of range (0..%d)" ,
820+ argc , RB_JIT_NATIVE_MAX_ARGS );
821+ }
822+
823+ if (!jit_native_hints ) {
824+ jit_native_hints = st_init_numtable ();
825+ }
826+
827+ // Find or create the inner table for this class
828+ st_data_t inner_val ;
829+ st_table * inner ;
830+ if (st_lookup (jit_native_hints , (st_data_t )klass , & inner_val )) {
831+ inner = (st_table * )inner_val ;
832+ }
833+ else {
834+ inner = st_init_numtable ();
835+ st_insert (jit_native_hints , (st_data_t )klass , (st_data_t )inner );
836+ }
837+
838+ rb_jit_native_call_hint_t * hint = xcalloc (1 , sizeof (rb_jit_native_call_hint_t ));
839+ hint -> func_ptr = func_ptr ;
840+ hint -> argc = argc ;
841+ hint -> ret_type = ret_type ;
842+ for (int i = 0 ; i < argc ; i ++ ) {
843+ hint -> arg_types [i ] = arg_types [i ];
844+ }
845+
846+ // Replace any existing hint for the same method
847+ st_data_t old_val ;
848+ if (st_lookup (inner , (st_data_t )mid , & old_val )) {
849+ xfree ((void * )old_val );
850+ }
851+ st_insert (inner , (st_data_t )mid , (st_data_t )hint );
852+ }
853+
854+ const rb_jit_native_call_hint_t *
855+ rb_jit_find_native_call_hint (VALUE klass , ID mid )
856+ {
857+ if (!jit_native_hints ) return NULL ;
858+
859+ st_data_t inner_val ;
860+ if (!st_lookup (jit_native_hints , (st_data_t )klass , & inner_val )) return NULL ;
861+
862+ st_table * inner = (st_table * )inner_val ;
863+ st_data_t hint_val ;
864+ if (!st_lookup (inner , (st_data_t )mid , & hint_val )) return NULL ;
865+
866+ return (const rb_jit_native_call_hint_t * )hint_val ;
867+ }
868+
869+ // --- Typed trampolines for native calls involving floating-point ---
870+ //
871+ // The JIT backend passes all arguments in integer registers, but C functions
872+ // with float/double parameters expect them in FP registers. These trampolines
873+ // bridge the gap: they accept Ruby VALUEs in integer registers, convert to
874+ // native types, call the target function, and box the result back to Ruby.
875+ //
876+ // Each trampoline is named: rb_jit_trampoline_<arg_types>_<ret_type>
877+ // where d = double, f = float, i = int, l = long, v = void.
878+
879+ // double f(double)
880+ VALUE
881+ rb_jit_trampoline_d_d (void * fptr , VALUE a0 )
882+ {
883+ double arg0 = rb_float_value (a0 );
884+ double result = ((double (* )(double ))fptr )(arg0 );
885+ return rb_float_new (result );
886+ }
887+
888+ // double f(double, double)
889+ VALUE
890+ rb_jit_trampoline_dd_d (void * fptr , VALUE a0 , VALUE a1 )
891+ {
892+ double arg0 = rb_float_value (a0 );
893+ double arg1 = rb_float_value (a1 );
894+ double result = ((double (* )(double , double ))fptr )(arg0 , arg1 );
895+ return rb_float_new (result );
896+ }
897+
898+ // double f(int)
899+ VALUE
900+ rb_jit_trampoline_i_d (void * fptr , VALUE a0 )
901+ {
902+ int arg0 = FIX2INT (a0 );
903+ double result = ((double (* )(int ))fptr )(arg0 );
904+ return rb_float_new (result );
905+ }
906+
907+ // double f(int, double)
908+ VALUE
909+ rb_jit_trampoline_id_d (void * fptr , VALUE a0 , VALUE a1 )
910+ {
911+ int arg0 = FIX2INT (a0 );
912+ double arg1 = rb_float_value (a1 );
913+ double result = ((double (* )(int , double ))fptr )(arg0 , arg1 );
914+ return rb_float_new (result );
915+ }
916+
917+ // double f(double, int)
918+ VALUE
919+ rb_jit_trampoline_di_d (void * fptr , VALUE a0 , VALUE a1 )
920+ {
921+ double arg0 = rb_float_value (a0 );
922+ int arg1 = FIX2INT (a1 );
923+ double result = ((double (* )(double , int ))fptr )(arg0 , arg1 );
924+ return rb_float_new (result );
925+ }
926+
927+ // void f(double)
928+ VALUE
929+ rb_jit_trampoline_d_v (void * fptr , VALUE a0 )
930+ {
931+ double arg0 = rb_float_value (a0 );
932+ ((void (* )(double ))fptr )(arg0 );
933+ return Qnil ;
934+ }
935+
936+ // double f(void)
937+ VALUE
938+ rb_jit_trampoline_v_d (void * fptr )
939+ {
940+ double result = ((double (* )(void ))fptr )();
941+ return rb_float_new (result );
942+ }
943+
944+ // --- Generic trampoline for signatures with pointer/string/int args ---
945+ //
946+ // Handles all args that fit in integer registers (no FP). Extracts native
947+ // values from Ruby objects: Fixnum → int/long, String → RSTRING_PTR,
948+ // FFI::Pointer → DATA_PTR address, bool → 0/1.
949+ //
950+ // The hint struct is passed as the first arg so the trampoline knows the
951+ // types. Remaining args are Ruby VALUEs passed through from JIT code.
952+
953+ static uintptr_t
954+ jit_convert_arg (int native_type , VALUE val )
955+ {
956+ switch (native_type ) {
957+ case RB_JIT_NATIVE_INT :
958+ case RB_JIT_NATIVE_UINT :
959+ return (uintptr_t )(unsigned int )FIX2INT (val );
960+ case RB_JIT_NATIVE_LONG :
961+ case RB_JIT_NATIVE_ULONG :
962+ case RB_JIT_NATIVE_INT64 :
963+ case RB_JIT_NATIVE_UINT64 :
964+ return (uintptr_t )FIX2LONG (val );
965+ case RB_JIT_NATIVE_POINTER :
966+ if (FIXNUM_P (val ))
967+ return (uintptr_t )FIX2LONG (val );
968+ if (NIL_P (val ))
969+ return (uintptr_t )0 ;
970+ // T_DATA with address at offset 0 (FFI::Pointer, Fiddle::Pointer)
971+ if (RB_TYPE_P (val , T_DATA ))
972+ return * (uintptr_t * )RTYPEDDATA_DATA (val );
973+ return (uintptr_t )NUM2LONG (rb_funcall (val , rb_intern ("to_i" ), 0 ));
974+ case RB_JIT_NATIVE_STRING :
975+ return (uintptr_t )RSTRING_PTR (val );
976+ case RB_JIT_NATIVE_BOOL :
977+ return RTEST (val ) ? 1 : 0 ;
978+ default :
979+ return 0 ;
980+ }
981+ }
982+
983+ typedef uintptr_t (* generic_fn0_t )(void );
984+ typedef uintptr_t (* generic_fn1_t )(uintptr_t );
985+ typedef uintptr_t (* generic_fn2_t )(uintptr_t , uintptr_t );
986+ typedef uintptr_t (* generic_fn3_t )(uintptr_t , uintptr_t , uintptr_t );
987+ typedef uintptr_t (* generic_fn4_t )(uintptr_t , uintptr_t , uintptr_t , uintptr_t );
988+ typedef uintptr_t (* generic_fn5_t )(uintptr_t , uintptr_t , uintptr_t , uintptr_t , uintptr_t );
989+ typedef uintptr_t (* generic_fn6_t )(uintptr_t , uintptr_t , uintptr_t , uintptr_t , uintptr_t , uintptr_t );
990+
991+ static VALUE
992+ jit_box_result (int ret_type , uintptr_t result )
993+ {
994+ switch (ret_type ) {
995+ case RB_JIT_NATIVE_INT :
996+ return INT2FIX ((int )(intptr_t )result );
997+ case RB_JIT_NATIVE_UINT :
998+ return UINT2NUM ((unsigned int )result );
999+ case RB_JIT_NATIVE_LONG :
1000+ case RB_JIT_NATIVE_INT64 :
1001+ return LONG2FIX ((long )result );
1002+ case RB_JIT_NATIVE_ULONG :
1003+ case RB_JIT_NATIVE_UINT64 :
1004+ return ULONG2NUM ((unsigned long )result );
1005+ case RB_JIT_NATIVE_BOOL :
1006+ return result ? Qtrue : Qfalse ;
1007+ case RB_JIT_NATIVE_VOID :
1008+ default :
1009+ return Qnil ;
1010+ }
1011+ }
1012+
1013+ // Variadic-cfunc-compatible trampoline.
1014+ // Signature matches CCallVariadic convention: func(int argc, VALUE *argv, VALUE recv).
1015+ // Looks up the hint from the current method's CME using the cfp.
1016+ VALUE
1017+ rb_jit_trampoline_generic (int argc , const VALUE * argv , VALUE recv )
1018+ {
1019+ rb_execution_context_t * ec = GET_EC ();
1020+ const rb_callable_method_entry_t * cme = rb_vm_frame_method_entry (ec -> cfp );
1021+ VALUE klass = cme -> defined_class ;
1022+ ID mid = cme -> called_id ;
1023+
1024+ const rb_jit_native_call_hint_t * hint =
1025+ rb_jit_find_native_call_hint (klass , mid );
1026+ if (!hint ) {
1027+ rb_raise (rb_eRuntimeError , "rb_jit_trampoline_generic: no hint found" );
1028+ }
1029+
1030+ uintptr_t a [RB_JIT_NATIVE_MAX_ARGS ];
1031+ void * fp = hint -> func_ptr ;
1032+
1033+ for (int i = 0 ; i < hint -> argc && i < argc ; i ++ ) {
1034+ a [i ] = jit_convert_arg (hint -> arg_types [i ], argv [i ]);
1035+ }
1036+
1037+ uintptr_t result ;
1038+ switch (hint -> argc ) {
1039+ case 0 : result = ((generic_fn0_t )fp )(); break ;
1040+ case 1 : result = ((generic_fn1_t )fp )(a [0 ]); break ;
1041+ case 2 : result = ((generic_fn2_t )fp )(a [0 ], a [1 ]); break ;
1042+ case 3 : result = ((generic_fn3_t )fp )(a [0 ], a [1 ], a [2 ]); break ;
1043+ case 4 : result = ((generic_fn4_t )fp )(a [0 ], a [1 ], a [2 ], a [3 ]); break ;
1044+ case 5 : result = ((generic_fn5_t )fp )(a [0 ], a [1 ], a [2 ], a [3 ], a [4 ]); break ;
1045+ case 6 : result = ((generic_fn6_t )fp )(a [0 ], a [1 ], a [2 ], a [3 ], a [4 ], a [5 ]); break ;
1046+ default : result = 0 ; break ;
1047+ }
1048+
1049+ return jit_box_result (hint -> ret_type , result );
1050+ }
0 commit comments