4 # Number of iterations to test
7 # Parse the command-line options
8 OptionParser.new do |opts|
9 opts.on("--num-iters=N") do |n|
14 # Format large numbers with comma separators for readability
15 def format_number(pad, number)
17 i = s.index('.') || s.size
18 s.insert(i -= 3, ',') while i > 3
22 # Wrap an integer to pass as argument
23 # We use this so we can have some object arguments
26 # Force the object to have a random shape
55 # Generate a random argument value, integer or string or object
57 c = ['int', 'string', 'object'].sample()
64 return 'f' * rand(0...100)
68 return IntWrapper.new(rand(0...100))
71 raise "should not get here"
74 # Evaluate the value of an argument with respect to the checksum
76 if arg.kind_of? Integer
80 if arg.kind_of? String
84 if arg.kind_of? Object
88 raise "unknown arg type"
91 # List of parameters/arguments for a method
98 # Sample/generate a random set of parameters for a method
100 # Choose how many positional arguments to use, and how many are optional
102 @opt_parg_idx = rand(num_pargs)
103 @num_opt_pargs = rand(num_pargs + 1 - @opt_parg_idx)
104 @num_pargs_req = num_pargs - @num_opt_pargs
105 @pargs = (0...num_pargs).map do |i|
108 :optional => (i >= @opt_parg_idx && i < @opt_parg_idx + @num_opt_pargs)
112 # Choose how many kwargs to use, and how many are optional
113 num_kwargs = rand(10)
114 @kwargs = (0...num_kwargs).map do |i|
117 :optional => rand() < 0.5
121 # Choose whether to have rest parameters or not
122 @has_rest = @num_opt_pargs == 0 && rand() < 0.5
123 @has_kwrest = rand() < 0.25
125 # Choose whether to have a named block parameter or not
126 @has_block_param = rand() < 0.25
129 # Sample/generate a random set of arguments corresponding to the parameters
131 # Choose how many positional args to pass
132 num_pargs_passed = rand(@num_pargs_req..@pargs.size)
134 # How many optional arguments will be filled
135 opt_pargs_filled = num_pargs_passed - @num_pargs_req
137 @pargs.each_with_index do |parg, i|
139 parg[:default] = rand(100)
142 if !parg[:optional] || i < @opt_parg_idx + opt_pargs_filled
143 parg[:argval] = rand(100)
147 @kwargs.each_with_index do |kwarg, i|
149 kwarg[:default] = rand(100)
152 if !kwarg[:optional] || rand() < 0.5
153 kwarg[:argval] = rand(100)
157 # Randomly pass a block or not
160 @block_arg = rand(100)
164 # Compute the expected checksum of arguments ahead of time
165 def compute_checksum()
168 @pargs.each_with_index do |arg, i|
169 value = (arg.key? :argval)? arg[:argval]:arg[:default]
170 checksum += (i+1) * arg_val(value)
173 @kwargs.each_with_index do |arg, i|
174 value = (arg.key? :argval)? arg[:argval]:arg[:default]
175 checksum += (i+1) * arg_val(value)
180 checksum += arg_val(@block_arg)
183 checksum += arg_val(@block_arg)
189 # Generate code for the method signature and method body
194 if !m_str.end_with?("(")
200 # If this has a default value
202 m_str += " = #{arg[:default]}"
207 if !m_str.end_with?("(")
213 @kwargs.each do |arg|
214 if !m_str.end_with?("(")
218 m_str += "#{arg[:name]}:"
220 # If this has a default value
222 m_str += " #{arg[:default]}"
227 if !m_str.end_with?("(")
234 if !m_str.end_with?("(")
243 # Add some useless locals
244 rand(0...16).times do |i|
245 m_str += "local#{i} = #{i}\n"
248 # Add some useless if statements
249 @pargs.each_with_index do |arg, i|
251 m_str += "if #{arg[:name]} > 4; end\n"
255 m_str += "checksum = 0\n"
257 @pargs.each_with_index do |arg, i|
258 m_str += "checksum += #{i+1} * arg_val(#{arg[:name]})\n"
261 @kwargs.each_with_index do |arg, i|
262 m_str += "checksum += #{i+1} * arg_val(#{arg[:name]})\n"
266 m_str += "if block; r = block.call; checksum += arg_val(r); end\n"
269 m_str += "if block_given?; r = yield; checksum += arg_val(r); end\n"
272 m_str += "raise 'rest is not array' unless rest.kind_of?(Array)\n"
273 m_str += "raise 'rest size not integer' unless rest.size.kind_of?(Integer)\n"
277 m_str += "raise 'kwrest is not a hash' unless kwrest.kind_of?(Hash)\n"
278 m_str += "raise 'kwrest size not integer' unless kwrest.size.kind_of?(Integer)\n"
281 m_str += "checksum\n"
287 # Generate code to call into the method and pass the arguments
291 @pargs.each_with_index do |arg, i|
296 if !c_str.end_with?("(")
300 c_str += "#{arg[:argval]}"
303 @kwargs.each_with_index do |arg, i|
308 if !c_str.end_with?("(")
312 c_str += "#{arg[:name]}: #{arg[:argval]}"
317 # Randomly pass a block or not
319 c_str += " { #{@block_arg} }"
326 iseqs_compiled_start = RubyVM::YJIT.runtime_stats[:compiled_iseq_entry]
327 start_time = Time.now.to_f
329 num_iters.times do |i|
330 puts "Iteration #{i}"
332 lst = ParamList.new()
333 m_str = lst.gen_method_str()
334 c_str = lst.gen_call_str()
335 checksum = lst.compute_checksum()
339 # Define the method on f
342 f.instance_eval(m_str)
343 #puts RubyVM::InstructionSequence.disasm(f.method(:m))
353 raise "return value #{r} doesn't match checksum #{checksum}"
359 # Make sure that YJIT actually compiled the tests we ran
360 # Should be run with --yjit-call-threshold=1
361 iseqs_compiled_end = RubyVM::YJIT.runtime_stats[:compiled_iseq_entry]
362 if iseqs_compiled_end - iseqs_compiled_start < num_iters
363 raise "YJIT did not compile enough ISEQs"
366 puts "Code region size: #{ format_number(0, RubyVM::YJIT.runtime_stats[:code_region_size]) }"
368 end_time = Time.now.to_f
369 itrs_per_sec = num_iters / (end_time - start_time)
370 itrs_per_hour = 3600 * itrs_per_sec
371 puts "#{'%.1f' % itrs_per_sec} iterations/s"
372 puts "#{format_number(0, itrs_per_hour.round)} iterations/hour"