@@ -826,7 +826,100 @@ def foo
826
826
end
827
827
828
828
def test_code_gc
829
- assert_compiles ( <<~'RUBY' , exits : :any , result : :ok )
829
+ assert_compiles ( code_gc_helpers + <<~'RUBY' , exits : :any , result : :ok )
830
+ return :not_paged unless add_pages(100) # prepare freeable pages
831
+ code_gc # first code GC
832
+ return :not_compiled1 unless compiles { nil } # should be JITable again
833
+
834
+ code_gc # second code GC
835
+ return :not_compiled2 unless compiles { nil } # should be JITable again
836
+
837
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
838
+ return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 2
839
+
840
+ :ok
841
+ RUBY
842
+ end
843
+
844
+ def test_on_stack_code_gc_call
845
+ assert_compiles ( code_gc_helpers + <<~'RUBY' , exits : :any , result : :ok )
846
+ fiber = Fiber.new {
847
+ # Loop to call the same basic block again after Fiber.yield
848
+ while true
849
+ Fiber.yield(nil.to_i)
850
+ end
851
+ }
852
+
853
+ return :not_paged1 unless add_pages(400) # go to a page without initial ocb code
854
+ return :broken_resume1 if fiber.resume != 0 # JIT the fiber
855
+ code_gc # first code GC, which should not free the fiber page
856
+ return :broken_resume2 if fiber.resume != 0 # The code should be still callable
857
+
858
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
859
+ return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 1
860
+
861
+ :ok
862
+ RUBY
863
+ end
864
+
865
+ def test_on_stack_code_gc_twice
866
+ assert_compiles ( code_gc_helpers + <<~'RUBY' , exits : :any , result : :ok )
867
+ fiber = Fiber.new {
868
+ # Loop to call the same basic block again after Fiber.yield
869
+ while Fiber.yield(nil.to_i); end
870
+ }
871
+
872
+ return :not_paged1 unless add_pages(400) # go to a page without initial ocb code
873
+ return :broken_resume1 if fiber.resume(true) != 0 # JIT the fiber
874
+ code_gc # first code GC, which should not free the fiber page
875
+
876
+ return :not_paged2 unless add_pages(300) # add some stuff to be freed
877
+ # Not calling fiber.resume here to test the case that the YJIT payload loses some
878
+ # information at the previous code GC. The payload should still be there, and
879
+ # thus we could know the fiber ISEQ is still on stack on this second code GC.
880
+ code_gc # second code GC, which should still not free the fiber page
881
+
882
+ return :not_paged3 unless add_pages(200) # attempt to overwrite the fiber page (it shouldn't)
883
+ return :broken_resume2 if fiber.resume(true) != 0 # The fiber code should be still fine
884
+
885
+ return :broken_resume3 if fiber.resume(false) != nil # terminate the fiber
886
+ code_gc # third code GC, freeing a page that used to be on stack
887
+
888
+ return :not_paged4 unless add_pages(100) # check everything still works
889
+
890
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
891
+ return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 3
892
+
893
+ :ok
894
+ RUBY
895
+ end
896
+
897
+ def test_code_gc_with_many_iseqs
898
+ assert_compiles ( code_gc_helpers + <<~'RUBY' , exits : :any , result : :ok , mem_size : 1 )
899
+ fiber = Fiber.new {
900
+ # Loop to call the same basic block again after Fiber.yield
901
+ while true
902
+ Fiber.yield(nil.to_i)
903
+ end
904
+ }
905
+
906
+ return :not_paged1 unless add_pages(500) # use some pages
907
+ return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well
908
+
909
+ return :not_gc if add_pages(2000) # use a whole lot of pages to run out of 1MiB
910
+ return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable
911
+
912
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
913
+ return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count == 0
914
+
915
+ :ok
916
+ RUBY
917
+ end
918
+
919
+ private
920
+
921
+ def code_gc_helpers
922
+ <<~'RUBY'
830
923
def compiles(&block)
831
924
failures = RubyVM::YJIT.runtime_stats[:compilation_failure]
832
925
block.call
@@ -835,37 +928,23 @@ def compiles(&block)
835
928
836
929
def add_pages(num_jits)
837
930
pages = RubyVM::YJIT.runtime_stats[:compiled_page_count]
838
- 100 .times { return false unless eval('compiles { nil.to_i }') }
931
+ num_jits .times { return false unless eval('compiles { nil.to_i }') }
839
932
pages.nil? || pages < RubyVM::YJIT.runtime_stats[:compiled_page_count]
840
933
end
841
934
842
935
def code_gc
843
936
RubyVM::YJIT.simulate_oom! # bump write_pos
844
937
eval('proc { nil }.call') # trigger code GC
845
938
end
846
-
847
- return :not_paged unless add_pages(100) # prepare freeable pages
848
- code_gc # first code GC
849
- return :not_compiled1 unless compiles { nil } # should be JITable again
850
-
851
- code_gc # second code GC
852
- return :not_compiled2 unless compiles { nil } # should be JITable again
853
-
854
- code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
855
- return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 2
856
-
857
- :ok
858
939
RUBY
859
940
end
860
941
861
- private
862
-
863
942
def assert_no_exits ( script )
864
943
assert_compiles ( script )
865
944
end
866
945
867
946
ANY = Object . new
868
- def assert_compiles ( test_script , insns : [ ] , call_threshold : 1 , stdout : nil , exits : { } , result : ANY , frozen_string_literal : nil )
947
+ def assert_compiles ( test_script , insns : [ ] , call_threshold : 1 , stdout : nil , exits : { } , result : ANY , frozen_string_literal : nil , mem_size : nil )
869
948
reset_stats = <<~RUBY
870
949
RubyVM::YJIT.runtime_stats
871
950
RubyVM::YJIT.reset_stats!
@@ -899,7 +978,7 @@ def collect_insns(iseq)
899
978
#{ write_results }
900
979
RUBY
901
980
902
- status , out , err , stats = eval_with_jit ( script , call_threshold : call_threshold )
981
+ status , out , err , stats = eval_with_jit ( script , call_threshold :, mem_size : )
903
982
904
983
assert status . success? , "exited with status #{ status . to_i } , stderr:\n #{ err } "
905
984
@@ -953,12 +1032,13 @@ def script_shell_encode(s)
953
1032
s . chars . map { |c | c . ascii_only? ? c : "\\ u%x" % c . codepoints [ 0 ] } . join
954
1033
end
955
1034
956
- def eval_with_jit ( script , call_threshold : 1 , timeout : 1000 )
1035
+ def eval_with_jit ( script , call_threshold : 1 , timeout : 1000 , mem_size : nil )
957
1036
args = [
958
1037
"--disable-gems" ,
959
1038
"--yjit-call-threshold=#{ call_threshold } " ,
960
1039
"--yjit-stats"
961
1040
]
1041
+ args << "--yjit-exec-mem-size=#{ mem_size } " if mem_size
962
1042
args << "-e" << script_shell_encode ( script )
963
1043
stats_r , stats_w = IO . pipe
964
1044
out , err , status = EnvUtil . invoke_ruby ( args ,
0 commit comments