9
9
#include "yjit_core.h"
10
10
#include "yjit_codegen.h"
11
11
12
+ // For exiting from YJIT frame from branch_stub_hit().
13
+ // Filled by gen_code_for_exit_from_stub().
14
+ static uint8_t * code_for_exit_from_stub = NULL ;
15
+
12
16
/*
13
17
Get an operand for the adjusted stack pointer address
14
18
*/
@@ -597,6 +601,52 @@ add_block_version(blockid_t blockid, block_t *block)
597
601
#endif
598
602
}
599
603
604
+ static ptrdiff_t
605
+ branch_code_size (const branch_t * branch )
606
+ {
607
+ return branch -> end_addr - branch -> start_addr ;
608
+ }
609
+
610
+ // Generate code for a branch, possibly rewriting and changing the size of it
611
+ static void
612
+ regenerate_branch (codeblock_t * cb , branch_t * branch )
613
+ {
614
+ if (branch -> start_addr < cb_get_ptr (cb , yjit_codepage_frozen_bytes )) {
615
+ // Generating this branch would modify frozen bytes. Do nothing.
616
+ return ;
617
+ }
618
+
619
+ const uint32_t old_write_pos = cb -> write_pos ;
620
+ const bool branch_terminates_block = branch -> end_addr == branch -> block -> end_addr ;
621
+
622
+ RUBY_ASSERT (branch -> dst_addrs [0 ] != NULL );
623
+
624
+ cb_set_write_ptr (cb , branch -> start_addr );
625
+ branch -> gen_fn (cb , branch -> dst_addrs [0 ], branch -> dst_addrs [1 ], branch -> shape );
626
+ branch -> end_addr = cb_get_write_ptr (cb );
627
+
628
+ if (branch_terminates_block ) {
629
+ // Adjust block size
630
+ branch -> block -> end_addr = branch -> end_addr ;
631
+ }
632
+
633
+ // cb->write_pos is both a write cursor and a marker for the end of
634
+ // everything written out so far. Leave cb->write_pos at the end of the
635
+ // block before returning. This function only ever bump or retain the end
636
+ // of block marker since that's what the majority of callers want. When the
637
+ // branch sits at the very end of the codeblock and it shrinks after
638
+ // regeneration, it's up to the caller to drop bytes off the end to
639
+ // not leave a gap and implement branch->shape.
640
+ if (old_write_pos > cb -> write_pos ) {
641
+ // We rewound cb->write_pos to generate the branch, now restore it.
642
+ cb_set_pos (cb , old_write_pos );
643
+ }
644
+ else {
645
+ // The branch sits at the end of cb and consumed some memory.
646
+ // Keep cb->write_pos.
647
+ }
648
+ }
649
+
600
650
// Create a new outgoing branch entry for a block
601
651
static branch_t *
602
652
make_branch_entry (block_t * block , const ctx_t * src_ctx , branchgen_fn gen_fn )
@@ -777,13 +827,15 @@ gen_entry_point(const rb_iseq_t *iseq, uint32_t insn_idx, rb_execution_context_t
777
827
static uint8_t *
778
828
branch_stub_hit (branch_t * branch , const uint32_t target_idx , rb_execution_context_t * ec )
779
829
{
780
- uint8_t * dst_addr ;
830
+ uint8_t * dst_addr = NULL ;
781
831
782
832
// Stop other ractors since we are going to patch machine code.
783
833
// This is how the GC does it.
784
834
RB_VM_LOCK_ENTER ();
785
835
rb_vm_barrier ();
786
836
837
+ const ptrdiff_t branch_size_on_entry = branch_code_size (branch );
838
+
787
839
RUBY_ASSERT (branch != NULL );
788
840
RUBY_ASSERT (target_idx < 2 );
789
841
blockid_t target = branch -> targets [target_idx ];
@@ -794,18 +846,13 @@ branch_stub_hit(branch_t *branch, const uint32_t target_idx, rb_execution_contex
794
846
if (branch -> blocks [target_idx ]) {
795
847
dst_addr = branch -> dst_addrs [target_idx ];
796
848
}
797
- else
798
- {
799
- //fprintf(stderr, "\nstub hit, branch: %p, target idx: %d\n", branch, target_idx);
800
- //fprintf(stderr, "blockid.iseq=%p, blockid.idx=%d\n", target.iseq, target.idx);
801
- //fprintf(stderr, "chain_depth=%d\n", target_ctx->chain_depth);
802
-
849
+ else {
803
850
// :stub-sp-flush:
804
851
// Generated code do stack operations without modifying cfp->sp, while the
805
852
// cfp->sp tells the GC what values on the stack to root. Generated code
806
853
// generally takes care of updating cfp->sp when it calls runtime routines that
807
- // could trigger GC, but for the case of branch stubs, it's inconvenient. So
808
- // we do it here.
854
+ // could trigger GC, but it's inconvenient to do it before calling this function.
855
+ // So we do it here instead .
809
856
VALUE * const original_interp_sp = ec -> cfp -> sp ;
810
857
ec -> cfp -> sp += target_ctx -> sp_offset ;
811
858
@@ -818,52 +865,74 @@ branch_stub_hit(branch_t *branch, const uint32_t target_idx, rb_execution_contex
818
865
819
866
// If this block hasn't yet been compiled
820
867
if (!p_block ) {
868
+ const uint8_t branch_old_shape = branch -> shape ;
869
+ bool branch_modified = false;
870
+
821
871
// If the new block can be generated right after the branch (at cb->write_pos)
822
- if (cb_get_write_ptr (cb ) == branch -> end_addr && branch -> start_addr >= cb_get_ptr ( cb , yjit_codepage_frozen_bytes ) ) {
872
+ if (cb_get_write_ptr (cb ) == branch -> end_addr ) {
823
873
// This branch should be terminating its block
824
874
RUBY_ASSERT (branch -> end_addr == branch -> block -> end_addr );
825
875
826
876
// Change the branch shape to indicate the target block will be placed next
827
877
branch -> shape = (uint8_t )target_idx ;
828
878
829
879
// Rewrite the branch with the new, potentially more compact shape
830
- cb_set_write_ptr (cb , branch -> start_addr );
831
- branch -> gen_fn (cb , branch -> dst_addrs [0 ], branch -> dst_addrs [1 ], branch -> shape );
832
- RUBY_ASSERT (cb_get_write_ptr (cb ) <= branch -> end_addr && "can't enlarge branches" );
833
- branch -> end_addr = cb_get_write_ptr (cb );
834
- branch -> block -> end_addr = cb_get_write_ptr (cb );
880
+ regenerate_branch (cb , branch );
881
+ branch_modified = true;
882
+
883
+ // Ensure that the branch terminates the codeblock just like
884
+ // before entering this if block. This drops bytes off the end
885
+ // in case we shrank the branch when regenerating.
886
+ cb_set_write_ptr (cb , branch -> end_addr );
835
887
}
836
888
837
889
// Compile the new block version
838
890
p_block = gen_block_version (target , target_ctx , ec );
839
- RUBY_ASSERT (p_block );
840
- RUBY_ASSERT (!(branch -> shape == (uint8_t )target_idx && p_block -> start_addr != branch -> end_addr ));
891
+
892
+ if (!p_block && branch_modified ) {
893
+ // We couldn't generate a new block for the branch, but we modified the branch.
894
+ // Restore the branch by regenerating it.
895
+ branch -> shape = branch_old_shape ;
896
+ regenerate_branch (cb , branch );
897
+ }
841
898
}
842
899
843
- // Add this branch to the list of incoming branches for the target
844
- rb_darray_append (& p_block -> incoming , branch );
900
+ if (p_block ) {
901
+ // Branch shape should reflect layout
902
+ RUBY_ASSERT (!(branch -> shape == (uint8_t )target_idx && p_block -> start_addr != branch -> end_addr ));
845
903
846
- // Update the branch target address
847
- dst_addr = p_block -> start_addr ;
848
- branch -> dst_addrs [target_idx ] = dst_addr ;
904
+ // Add this branch to the list of incoming branches for the target
905
+ rb_darray_append (& p_block -> incoming , branch );
849
906
850
- // Rewrite the branch with the new jump target address
851
- if (branch -> start_addr >= cb_get_ptr (cb , yjit_codepage_frozen_bytes )) {
852
- RUBY_ASSERT (branch -> dst_addrs [0 ] != NULL );
853
- uint32_t cur_pos = cb -> write_pos ;
854
- cb_set_write_ptr (cb , branch -> start_addr );
855
- branch -> gen_fn (cb , branch -> dst_addrs [0 ], branch -> dst_addrs [1 ], branch -> shape );
856
- RUBY_ASSERT (cb_get_write_ptr (cb ) == branch -> end_addr && "branch can't change size" );
857
- cb_set_pos (cb , cur_pos );
858
- }
907
+ // Update the branch target address
908
+ dst_addr = p_block -> start_addr ;
909
+ branch -> dst_addrs [target_idx ] = dst_addr ;
859
910
860
- // Mark this branch target as patched (no longer a stub)
861
- branch -> blocks [target_idx ] = p_block ;
911
+ // Mark this branch target as patched (no longer a stub)
912
+ branch -> blocks [target_idx ] = p_block ;
862
913
863
- // Restore interpreter sp, since the code hitting the stub expects the original.
864
- ec -> cfp -> sp = original_interp_sp ;
914
+ // Rewrite the branch with the new jump target address
915
+ regenerate_branch (cb , branch );
916
+
917
+ // Restore interpreter sp, since the code hitting the stub expects the original.
918
+ ec -> cfp -> sp = original_interp_sp ;
919
+ }
920
+ else {
921
+ // Failed to service the stub by generating a new block so now we
922
+ // need to exit to the interpreter at the stubbed location. We are
923
+ // intentionally *not* restoring original_interp_sp. At the time of
924
+ // writing, reconstructing interpreter state only involves setting
925
+ // cfp->sp and cfp->pc. We set both before trying to generate the
926
+ // block. All there is left to do to exit is to pop the native
927
+ // frame. We do that in code_for_exit_from_stub.
928
+ dst_addr = code_for_exit_from_stub ;
929
+ }
865
930
}
866
931
932
+ const ptrdiff_t new_branch_size = branch_code_size (branch );
933
+ RUBY_ASSERT_ALWAYS (new_branch_size >= 0 );
934
+ RUBY_ASSERT_ALWAYS (new_branch_size <= branch_size_on_entry && "branch stubs should not enlarge branches" );
935
+
867
936
RB_VM_LOCK_LEAVE ();
868
937
869
938
// Return a pointer to the compiled block version
@@ -942,8 +1011,7 @@ gen_branch(
942
1011
943
1012
// Call the branch generation function
944
1013
branch -> start_addr = cb_get_write_ptr (cb );
945
- gen_fn (cb , branch -> dst_addrs [0 ], branch -> dst_addrs [1 ], SHAPE_DEFAULT );
946
- branch -> end_addr = cb_get_write_ptr (cb );
1014
+ regenerate_branch (cb , branch );
947
1015
}
948
1016
949
1017
static void
@@ -1191,13 +1259,7 @@ invalidate_block_version(block_t *block)
1191
1259
}
1192
1260
1193
1261
// Rewrite the branch with the new jump target address
1194
- RUBY_ASSERT (branch -> dst_addrs [0 ] != NULL );
1195
- uint32_t cur_pos = cb -> write_pos ;
1196
- cb_set_write_ptr (cb , branch -> start_addr );
1197
- branch -> gen_fn (cb , branch -> dst_addrs [0 ], branch -> dst_addrs [1 ], branch -> shape );
1198
- branch -> end_addr = cb_get_write_ptr (cb );
1199
- branch -> block -> end_addr = cb_get_write_ptr (cb );
1200
- cb_set_pos (cb , cur_pos );
1262
+ regenerate_branch (cb , branch );
1201
1263
1202
1264
if (target_next && branch -> end_addr > block -> end_addr ) {
1203
1265
fprintf (stderr , "branch_block_idx=%u block_idx=%u over=%ld block_size=%ld\n" ,
@@ -1243,5 +1305,5 @@ invalidate_block_version(block_t *block)
1243
1305
static void
1244
1306
yjit_init_core (void )
1245
1307
{
1246
- // Nothing yet
1308
+ gen_code_for_exit_from_stub ();
1247
1309
}
0 commit comments