@@ -744,6 +744,112 @@ enum {
744744 OSWB_MAX_DEPTH = 5 , // up to 5 different classes
745745};
746746
747+ // Codegen for setting an instance variable.
748+ // Preconditions:
749+ // - receiver is in REG0
750+ // - receiver has the same class as CLASS_OF(comptime_receiver)
751+ // - no stack push or pops to ctx since the entry to the codegen of the instruction being compiled
752+ static codegen_status_t
753+ gen_set_ivar (jitstate_t * jit , ctx_t * ctx , const int max_chain_depth , VALUE comptime_receiver , ID ivar_name , insn_opnd_t reg0_opnd , uint8_t * side_exit )
754+ {
755+ VALUE comptime_val_klass = CLASS_OF (comptime_receiver );
756+ const ctx_t starting_context = * ctx ; // make a copy for use with jit_chain_guard
757+
758+ // If the class uses the default allocator, instances should all be T_OBJECT
759+ // NOTE: This assumes nobody changes the allocator of the class after allocation.
760+ // Eventually, we can encode whether an object is T_OBJECT or not
761+ // inside object shapes.
762+ if (rb_get_alloc_func (comptime_val_klass ) != rb_class_allocate_instance ) {
763+ GEN_COUNTER_INC (cb , setivar_not_object );
764+ return YJIT_CANT_COMPILE ;
765+ }
766+ RUBY_ASSERT (BUILTIN_TYPE (comptime_receiver ) == T_OBJECT ); // because we checked the allocator
767+
768+ // ID for the name of the ivar
769+ ID id = ivar_name ;
770+ struct rb_iv_index_tbl_entry * ent ;
771+ struct st_table * iv_index_tbl = ROBJECT_IV_INDEX_TBL (comptime_receiver );
772+
773+ // Bail if this is a heap object, because this needs a write barrier
774+ ADD_COMMENT (cb , "guard value is immediate" );
775+ test (cb , REG1 , imm_opnd (RUBY_IMMEDIATE_MASK ));
776+ jz_ptr (cb , COUNTED_EXIT (side_exit , setivar_val_heapobject ));
777+
778+ // Lookup index for the ivar the instruction loads
779+ if (iv_index_tbl && rb_iv_index_tbl_lookup (iv_index_tbl , id , & ent )) {
780+ uint32_t ivar_index = ent -> index ;
781+
782+ x86opnd_t val_to_write = ctx_stack_pop (ctx , 1 );
783+ mov (cb , REG1 , val_to_write );
784+
785+ x86opnd_t flags_opnd = member_opnd (REG0 , struct RBasic , flags );
786+
787+ // Bail if this object is frozen
788+ ADD_COMMENT (cb , "guard self is not frozen" );
789+ test (cb , flags_opnd , imm_opnd (RUBY_FL_FREEZE ));
790+ jz_ptr (cb , COUNTED_EXIT (side_exit , setivar_frozen ));
791+
792+ // Pop receiver if it's on the temp stack
793+ if (!reg0_opnd .is_self ) {
794+ (void )ctx_stack_pop (ctx , 1 );
795+ }
796+
797+ // Compile time self is embedded and the ivar index lands within the object
798+ if (RB_FL_TEST_RAW (comptime_receiver , ROBJECT_EMBED ) && ivar_index < ROBJECT_EMBED_LEN_MAX ) {
799+ // See ROBJECT_IVPTR() from include/ruby/internal/core/robject.h
800+
801+ // Guard that self is embedded
802+ // TODO: BT and JC is shorter
803+ ADD_COMMENT (cb , "guard embedded setivar" );
804+ test (cb , flags_opnd , imm_opnd (ROBJECT_EMBED ));
805+ jit_chain_guard (JCC_JZ , jit , & starting_context , max_chain_depth , side_exit );
806+
807+ // Load the variable
808+ x86opnd_t ivar_opnd = mem_opnd (64 , REG0 , offsetof(struct RObject , as .ary ) + ivar_index * SIZEOF_VALUE );
809+
810+ mov (cb , ivar_opnd , REG1 );
811+
812+ // Push the ivar on the stack
813+ // For attr_writer we'll need to push the value on the stack
814+ //x86opnd_t out_opnd = ctx_stack_push(ctx, TYPE_UNKNOWN);
815+ }
816+ else {
817+ // Compile time value is *not* embeded.
818+
819+ // Guard that value is *not* embedded
820+ // See ROBJECT_IVPTR() from include/ruby/internal/core/robject.h
821+ ADD_COMMENT (cb , "guard extended setivar" );
822+ x86opnd_t flags_opnd = member_opnd (REG0 , struct RBasic , flags );
823+ test (cb , flags_opnd , imm_opnd (ROBJECT_EMBED ));
824+ jit_chain_guard (JCC_JNZ , jit , & starting_context , max_chain_depth , side_exit );
825+
826+ // check that the extended table is big enough
827+ if (ivar_index >= ROBJECT_EMBED_LEN_MAX + 1 ) {
828+ // Check that the slot is inside the extended table (num_slots > index)
829+ x86opnd_t num_slots = mem_opnd (32 , REG0 , offsetof(struct RObject , as .heap .numiv ));
830+ cmp (cb , num_slots , imm_opnd (ivar_index ));
831+ jle_ptr (cb , COUNTED_EXIT (side_exit , setivar_idx_out_of_range ));
832+ }
833+
834+ // Get a pointer to the extended table
835+ x86opnd_t tbl_opnd = mem_opnd (64 , REG0 , offsetof(struct RObject , as .heap .ivptr ));
836+ mov (cb , REG0 , tbl_opnd );
837+
838+ // Write the ivar to the extended table
839+ x86opnd_t ivar_opnd = mem_opnd (64 , REG0 , sizeof (VALUE ) * ivar_index );
840+ mov (cb , REG1 , val_to_write );
841+ mov (cb , ivar_opnd , REG1 );
842+ }
843+
844+ // Jump to next instruction. This allows guard chains to share the same successor.
845+ jit_jump_to_next_insn (jit , ctx );
846+ return YJIT_END_BLOCK ;
847+ }
848+
849+ GEN_COUNTER_INC (cb , setivar_name_not_mapped );
850+ return YJIT_CANT_COMPILE ;
851+ }
852+
747853// Codegen for getting an instance variable.
748854// Preconditions:
749855// - receiver is in REG0
@@ -866,7 +972,7 @@ gen_getinstancevariable(jitstate_t *jit, ctx_t *ctx)
866972
867973 // Guard that the receiver has the same class as the one from compile time.
868974 mov (cb , REG0 , member_opnd (REG_CFP , rb_control_frame_t , self ));
869- guard_self_is_heap (cb , REG0 , side_exit , ctx );
975+ guard_self_is_heap (cb , REG0 , COUNTED_EXIT ( side_exit , getivar_se_self_not_heap ) , ctx );
870976
871977 jit_guard_known_klass (jit , ctx , comptime_val_klass , OPND_SELF , GETIVAR_MAX_DEPTH , side_exit );
872978
@@ -876,69 +982,27 @@ gen_getinstancevariable(jitstate_t *jit, ctx_t *ctx)
876982static codegen_status_t
877983gen_setinstancevariable (jitstate_t * jit , ctx_t * ctx )
878984{
879- IVC ic = (IVC )jit_get_arg (jit , 1 );
880-
881- // Check that the inline cache has been set, slot index is known
882- if (!ic -> entry ) {
883- return YJIT_CANT_COMPILE ;
985+ // Defer compilation so we can specialize on a runtime `self`
986+ if (!jit_at_current_insn (jit )) {
987+ defer_compilation (jit -> block , jit -> insn_idx , ctx );
988+ return YJIT_END_BLOCK ;
884989 }
885990
886- // If the class uses the default allocator, instances should all be T_OBJECT
887- // NOTE: This assumes nobody changes the allocator of the class after allocation.
888- // Eventually, we can encode whether an object is T_OBJECT or not
889- // inside object shapes.
890- if (rb_get_alloc_func (ic -> entry -> class_value ) != rb_class_allocate_instance ) {
891- return YJIT_CANT_COMPILE ;
892- }
991+ ID ivar_name = (ID )jit_get_arg (jit , 0 );
893992
894- uint32_t ivar_index = ic -> entry -> index ;
993+ VALUE comptime_val = jit_peek_at_self (jit , ctx );
994+ VALUE comptime_val_klass = CLASS_OF (comptime_val );
895995
896- // Create a size-exit to fall back to the interpreter
897- uint8_t * side_exit = yjit_side_exit (jit , ctx );
996+ // Generate a side exit
997+ uint8_t * side_exit = yjit_side_exit (jit , ctx );
898998
899- // Load self from CFP
999+ // Guard that the receiver has the same class as the one from compile time.
9001000 mov (cb , REG0 , member_opnd (REG_CFP , rb_control_frame_t , self ));
1001+ guard_self_is_heap (cb , REG0 , COUNTED_EXIT (side_exit , setivar_se_self_not_heap ), ctx );
9011002
902- guard_self_is_heap (cb , REG0 , side_exit , ctx );
903-
904- // Bail if receiver class is different from compiled time call cache class
905- x86opnd_t klass_opnd = mem_opnd (64 , REG0 , offsetof(struct RBasic , klass ));
906- mov (cb , REG1 , klass_opnd );
907- x86opnd_t serial_opnd = mem_opnd (64 , REG1 , offsetof(struct RClass , class_serial ));
908- cmp (cb , serial_opnd , imm_opnd (ic -> entry -> class_serial ));
909- jne_ptr (cb , side_exit );
910-
911- // Bail if the ivars are not on the extended table
912- // See ROBJECT_IVPTR() from include/ruby/internal/core/robject.h
913- x86opnd_t flags_opnd = member_opnd (REG0 , struct RBasic , flags );
914- test (cb , flags_opnd , imm_opnd (ROBJECT_EMBED ));
915- jnz_ptr (cb , side_exit );
916-
917- // If we can't guarantee that the extended table is big enoughg
918- if (ivar_index >= ROBJECT_EMBED_LEN_MAX + 1 ) {
919- // Check that the slot is inside the extended table (num_slots > index)
920- x86opnd_t num_slots = mem_opnd (32 , REG0 , offsetof(struct RObject , as .heap .numiv ));
921- cmp (cb , num_slots , imm_opnd (ivar_index ));
922- jle_ptr (cb , side_exit );
923- }
924-
925- // Get a pointer to the extended table
926- x86opnd_t tbl_opnd = mem_opnd (64 , REG0 , offsetof(struct RObject , as .heap .ivptr ));
927- mov (cb , REG0 , tbl_opnd );
928-
929- // Pop the value to write from the stack
930- x86opnd_t stack_top = ctx_stack_pop (ctx , 1 );
931- mov (cb , REG1 , stack_top );
932-
933- // Bail if this is a heap object, because this needs a write barrier
934- test (cb , REG1 , imm_opnd (RUBY_IMMEDIATE_MASK ));
935- jz_ptr (cb , side_exit );
936-
937- // Write the ivar to the extended table
938- x86opnd_t ivar_opnd = mem_opnd (64 , REG0 , sizeof (VALUE ) * ivar_index );
939- mov (cb , ivar_opnd , REG1 );
1003+ jit_guard_known_klass (jit , ctx , comptime_val_klass , OPND_SELF , GETIVAR_MAX_DEPTH , side_exit );
9401004
941- return YJIT_KEEP_COMPILING ;
1005+ return gen_set_ivar ( jit , ctx , GETIVAR_MAX_DEPTH , comptime_val , ivar_name , OPND_SELF , side_exit ) ;
9421006}
9431007
9441008static void
0 commit comments