ruby/bootstraptest/test_insns.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

485 строки
14 KiB
Ruby
Исходник Обычный вид История

# C0 coverage of each instructions
# :NOTE: This is for development purpose; never consider this file as
# ISeq compilation specification.
begin
# This library brings some additional coverage.
# Not mandatory.
require 'rbconfig/sizeof'
rescue LoadError
# OK, just skip
else
if defined? RbConfig::LIMITS
$FIXNUM_MAX = RbConfig::LIMITS["FIXNUM_MAX"]
$FIXNUM_MIN = RbConfig::LIMITS["FIXNUM_MIN"]
end
end
fsl = { frozen_string_literal: true } # used later
tests = [
# insn , expression to generate such insn
[ 'nop', %q{ raise rescue true }, ],
[ 'setlocal *, 0', %q{ x = true }, ],
[ 'setlocal *, 1', %q{ x = nil; -> { x = true }.call }, ],
[ 'setlocal', %q{ x = nil; -> { -> { x = true }.() }.() }, ],
[ 'getlocal *, 0', %q{ x = true; x }, ],
[ 'getlocal *, 1', %q{ x = true; -> { x }.call }, ],
[ 'getlocal', %q{ x = true; -> { -> { x }.() }.() }, ],
[ 'setblockparam', <<-'},', ], # {
def m&b
b = # here
proc { true }
end
m { false }.call
},
[ 'getblockparam', <<-'},', ], # {
def m&b
b # here
end
m { true }.call
},
[ 'getblockparamproxy', <<-'},', ], # {
def m&b
b # here
.call
end
m { true }
},
[ 'setspecial', %q{ true if true..true }, ],
[ 'getspecial', %q{ $&.nil? }, ],
[ 'getspecial', %q{ $`.nil? }, ],
[ 'getspecial', %q{ $'.nil? }, ],
[ 'getspecial', %q{ $+.nil? }, ],
[ 'getspecial', %q{ $1.nil? }, ],
[ 'getspecial', %q{ $128.nil? }, ],
[ 'getglobal', %q{ String === $0 }, ],
[ 'getglobal', %q{ $_.nil? }, ],
[ 'setglobal', %q{ $0 = "true" }, ],
[ 'setinstancevariable', %q{ @x = true }, ],
[ 'getinstancevariable', %q{ @x = true; @x }, ],
[ 'setclassvariable', %q{ class A; @@x = true; end }, ],
[ 'getclassvariable', %q{ class A; @@x = true; @@x end }, ],
[ 'setconstant', %q{ X = true }, ],
[ 'setconstant', %q{ Object::X = true }, ],
[ 'getconstant', %q{ X = true; X }, ],
[ 'getconstant', %q{ X = true; Object::X }, ],
[ 'getinlinecache / setinlinecache', %q{ def x; X; end; X = true; x; x; x }, ],
[ 'putnil', %q{ $~ == nil }, ],
[ 'putself', %q{ $~ != self }, ],
[ 'putobject INT2FIX(0)', %q{ $~ != 0 }, ],
[ 'putobject INT2FIX(1)', %q{ $~ != 1 }, ],
[ 'putobject', %q{ $~ != -1 }, ],
[ 'putobject', %q{ $~ != /x/ }, ],
[ 'putobject', %q{ $~ != :x }, ],
[ 'putobject', %q{ $~ != (1..2) }, ],
[ 'putobject', %q{ $~ != true }, ],
[ 'putobject', %q{ /(?<x>x)/ =~ "x"; x == "x" }, ],
[ 'putspecialobject', %q{ {//=>true}[//] }, ],
[ 'putstring', %q{ "true" }, ],
[ 'tostring / concatstrings', %q{ "#{true}" }, ],
[ 'toregexp', %q{ /#{true}/ =~ "true" && $~ }, ],
[ 'intern', %q{ :"#{true}" }, ],
[ 'newarray', %q{ ["true"][0] }, ],
Add pushtoarraykwsplat instruction to avoid unnecessary array allocation This is designed to replace the newarraykwsplat instruction, which is no longer used in the parse.y compiler after this commit. This avoids an unnecessary array allocation in the case where ARGSCAT is followed by LIST with keyword: ```ruby a = [] kw = {} [*a, 1, **kw] ``` Previous Instructions: ``` 0000 newarray 0 ( 1)[Li] 0002 setlocal_WC_0 a@0 0004 newhash 0 ( 2)[Li] 0006 setlocal_WC_0 kw@1 0008 getlocal_WC_0 a@0 ( 3)[Li] 0010 splatarray true 0012 putobject_INT2FIX_1_ 0013 putspecialobject 1 0015 newhash 0 0017 getlocal_WC_0 kw@1 0019 opt_send_without_block <calldata!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE> 0021 newarraykwsplat 2 0023 concattoarray 0024 leave ``` New Instructions: ``` 0000 newarray 0 ( 1)[Li] 0002 setlocal_WC_0 a@0 0004 newhash 0 ( 2)[Li] 0006 setlocal_WC_0 kw@1 0008 getlocal_WC_0 a@0 ( 3)[Li] 0010 splatarray true 0012 putobject_INT2FIX_1_ 0013 pushtoarray 1 0015 putspecialobject 1 0017 newhash 0 0019 getlocal_WC_0 kw@1 0021 opt_send_without_block <calldata!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE> 0023 pushtoarraykwsplat 0024 leave ``` pushtoarraykwsplat is designed to be simpler than newarraykwsplat. It does not take a variable number of arguments from the stack, it pops the top of the stack, and appends it to the second from the top, unless the top of the stack is an empty hash. During this work, I found the ARGSPUSH followed by HASH with keyword did not compile correctly, as it pushed the generated hash to the array even if the hash was empty. This fixes the behavior, to use pushtoarraykwsplat instead of pushtoarray in that case: ```ruby a = [] kw = {} [*a, **kw] [{}] # Before [] # After ``` This does not remove the newarraykwsplat instruction, as it is still referenced in the prism compiler (which should be updated similar to this), YJIT (only in the bindings, it does not appear to be implemented), and RJIT (in a couple comments). After those are updated, the newarraykwsplat instruction should be removed.
2024-01-30 21:31:27 +03:00
[ 'pushtoarraykwsplat', %q{ [**{x:'true'}][0][:x] }, ],
[ 'duparray', %q{ [ true ][0] }, ],
[ 'expandarray', %q{ y = [ true, false, nil ]; x, = y; x }, ],
[ 'expandarray', %q{ y = [ true, false, nil ]; x, *z = y; x }, ],
[ 'expandarray', %q{ y = [ true, false, nil ]; x, *z, w = y; x }, ],
[ 'splatarray', %q{ x, = *(y = true), false; x }, ],
[ 'concatarray', %q{ ["t", "r", *x = "u", "e"].join }, ],
[ 'concatarray', <<-'},', ], # {
class X; def to_a; ['u']; end; end
['t', 'r', *X.new, 'e'].join
},
[ 'concatarray', <<-'},', ], # {
r = false
t = [true, nil]
q, w, e = r, *t # here
w
},
[ 'newhash', %q{ x = {}; x[x] = true }, ],
[ 'newhash', %q{ x = true; { x => x }[x] }, ],
[ 'newhashfromarray', %q{ { a: true }[:a] }, ],
[ 'newrange', %q{ x = 1; [*(0..x)][0] == 0 }, ],
[ 'newrange', %q{ x = 1; [*(0...x)][0] == 0 }, ],
[ 'pop', %q{ def x; true; end; x }, ],
[ 'dup', %q{ x = y = true; x }, ],
[ 'dupn', %q{ Object::X ||= true }, ],
[ 'reverse', %q{ q, (w, e), r = 1, [2, 3], 4; e == 3 }, ],
[ 'swap', %q{ !!defined?([[]]) }, ],
[ 'swap', <<-'},', ], # {
x = [[false, true]]
for i, j in x # here
;
end
j
},
[ 'topn', %q{ x, y = [], 0; x[*y], = [true, false]; x[0] }, ],
[ 'setn', %q{ x, y = [], 0; x[*y] = true ; x[0] }, ],
[ 'adjuststack', %q{ x = [true]; x[0] ||= nil; x[0] }, ],
[ 'defined', %q{ !defined?(x) }, ],
[ 'checkkeyword', %q{ def x x:rand;x end; x x: true }, ],
[ 'checktype', %q{ x = true; "#{x}" }, ],
[ 'checkmatch', <<-'},', ], # {
x = y = true
case x
when false
y = false
when true # here
y = nil
end
y == nil
},
[ 'checkmatch', <<-'},', ], # {
x, y = true, [false]
case x
when *y # here
z = false
else
z = true
end
z
},
[ 'checkmatch', <<-'},', ], # {
x = false
begin
raise
rescue # here
x = true
end
x
},
[ 'defineclass', %q{ module X; true end }, ],
[ 'defineclass', %q{ X = Module.new; module X; true end }, ],
[ 'defineclass', %q{ class X; true end }, ],
[ 'defineclass', %q{ X = Class.new; class X; true end }, ],
[ 'defineclass', %q{ X = Class.new; class Y < X; true end }, ],
[ 'defineclass', %q{ X = Class.new; class << X; true end }, ],
[ 'defineclass', <<-'},', ], # {
X = Class.new
Y = Class.new(X)
class Y < X
true
end
},
[ 'opt_send_without_block', %q{ true.to_s }, ],
[ 'send', %q{ true.tap {|i| i.to_s } }, ],
[ 'leave', %q{ def x; true; end; x }, ],
[ 'invokesuper', <<-'},', ], # {
class X < String
def empty?
super # here
end
end
X.new.empty?
},
[ 'invokeblock', <<-'},', ], # {
def x
return yield self # here
end
x do
true
end
},
[ 'opt_str_freeze', %q{ 'true'.freeze }, ],
[ 'opt_nil_p', %q{ nil.nil? }, ],
2019-09-05 09:02:07 +03:00
[ 'opt_nil_p', %q{ !Object.nil? }, ],
[ 'opt_nil_p', %q{ Class.new{def nil?; true end}.new.nil? }, ],
[ 'opt_str_uminus', %q{ -'true' }, ],
[ 'opt_str_freeze', <<-'},', ], # {
class String
def freeze
true
end
end
'true'.freeze
},
[ 'opt_newarray_send', %q{ ![ ].hash.nil? }, ],
[ 'opt_newarray_send', %q{ [ ].max.nil? }, ],
[ 'opt_newarray_send', %q{ [1, x = 2, 3].max == 3 }, ],
[ 'opt_newarray_send', <<-'},', ], # {
class Array
def max
true
end
end
[1, x = 2, 3].max
},
[ 'opt_newarray_send', %q{ [ ].min.nil? }, ],
[ 'opt_newarray_send', %q{ [3, x = 2, 1].min == 1 }, ],
[ 'opt_newarray_send', <<-'},', ], # {
class Array
def min
true
end
end
[3, x = 2, 1].min
},
[ 'opt_newarray_send', %q{ v = 1.23; [v, v*2].pack("E*").unpack("E*") == [v, v*2] }, ],
[ 'opt_newarray_send', %q{ v = 4.56; b = +"x"; [v, v*2].pack("E*", buffer: b); b[1..].unpack("E*") == [v, v*2] }, ],
[ 'opt_newarray_send', <<-'},', ], # {
v = 7.89;
b = +"x";
class Array
alias _pack pack
def pack(s, buffer: nil, prefix: "y")
buffer ||= +"b"
buffer << prefix
_pack(s, buffer: buffer)
end
end
tests = []
ret = [v].pack("E*", prefix: "z")
tests << (ret[0..1] == "bz")
tests << (ret[2..].unpack("E*") == [v])
ret = [v].pack("E*")
tests << (ret[0..1] == "by")
tests << (ret[2..].unpack("E*") == [v])
[v, v*2, v*3].pack("E*", buffer: b)
tests << (b[0..1] == "xy")
tests << (b[2..].unpack("E*") == [v, v*2, v*3])
class Array
def pack(_fmt, buffer:) = buffer
end
b = nil
tests << [v].pack("E*", buffer: b).nil?
class Array
def pack(_fmt, **kw) = kw.empty?
end
tests << [v].pack("E*") == true
tests.all? or puts tests
},
[ 'throw', %q{ false.tap { break true } }, ],
[ 'branchif', %q{ x = nil; x ||= true }, ],
[ 'branchif', %q{ x = true; x ||= nil; x }, ],
[ 'branchunless', %q{ x = 1; x &&= true }, ],
[ 'branchunless', %q{ x = nil; x &&= true; x.nil? }, ],
[ 'branchnil', %q{ x = true; x&.to_s }, ],
[ 'branchnil', %q{ x = nil; (x&.to_s).nil? }, ],
[ 'jump', <<-'},', ], # {
y = 1
x = if y == 0 then nil elsif y == 1 then true else nil end
x
},
[ 'jump', <<-'},', ], # {
# ultra complicated situation: this ||= assignment only generates
# 15 instructions, not including the class definition.
class X; attr_accessor :x; end
x = X.new
x&.x ||= true # here
},
[ 'once', %q{ /#{true}/o =~ "true" && $~ }, ],
[ 'once', <<-'},', ], # {
def once expr
return /#{expr}/o # here
end
x = once(true); x = once(false); x = once(nil);
x =~ "true" && $~
},
[ 'once', <<-'},', ], # {
# recursive once
def once n
return %r/#{
if n == 0
true
else
once(n-1) # here
end
}/ox
end
x = once(128); x = once(7); x = once(16);
x =~ "true" && $~
},
[ 'once', <<-'},', ], # {
# inter-thread lockup situation
def once n
return Thread.start n do |m|
Thread.pass
next %r/#{
sleep m # here
true
}/ox
end
end
x = once(1); y = once(0.1); z = y.value
z =~ "true" && $~
},
[ 'opt_case_dispatch', %q{ case 0 when 1.1 then false else true end }, ],
[ 'opt_case_dispatch', %q{ case 1.0 when 1.1 then false else true end }, ],
[ 'opt_plus', %q{ 1 + 1 == 2 }, ],
if defined? $FIXNUM_MAX then
[ 'opt_plus', %Q{ #{ $FIXNUM_MAX } + 1 == #{ $FIXNUM_MAX + 1 } }, ]
end,
[ 'opt_plus', %q{ 1.0 + 1.0 == 2.0 }, ],
[ 'opt_plus', %q{ x = +0.0.next_float; x + x >= x }, ],
[ 'opt_plus', %q{ 't' + 'rue' }, ],
[ 'opt_plus', %q{ ( ['t'] + ['r', ['u', ['e'], ], ] ).join }, ],
[ 'opt_plus', %q{ Time.at(1) + 1 == Time.at(2) }, ],
[ 'opt_minus', %q{ 1 - 1 == 0 }, ],
if defined? $FIXNUM_MIN then
[ 'opt_minus', %Q{ #{ $FIXNUM_MIN } - 1 == #{ $FIXNUM_MIN - 1 } }, ]
end,
[ 'opt_minus', %q{ 1.0 - 1.0 == 0.0 }, ],
[ 'opt_minus', %q{ x = -0.0.prev_float; x - x == 0.0 }, ],
[ 'opt_minus', %q{ ( [false, true] - [false] )[0] }, ],
[ 'opt_mult', %q{ 1 * 1 == 1 }, ],
[ 'opt_mult', %q{ 1.0 * 1.0 == 1.0 }, ],
[ 'opt_mult', %q{ x = +0.0.next_float; x * x <= x }, ],
[ 'opt_mult', %q{ ( "ruet" * 3 )[7,4] }, ],
[ 'opt_div', %q{ 1 / 1 == 1 }, ],
[ 'opt_div', %q{ 1.0 / 1.0 == 1.0 }, ],
[ 'opt_div', %q{ x = +0.0.next_float; x / x >= x }, ],
[ 'opt_div', %q{ x = 1/2r; x / x == 1 }, ],
[ 'opt_mod', %q{ 1 % 1 == 0 }, ],
[ 'opt_mod', %q{ 1.0 % 1.0 == 0.0 }, ],
[ 'opt_mod', %q{ x = +0.0.next_float; x % x == 0.0 }, ],
[ 'opt_mod', %q{ '%s' % [ true ] }, ],
[ 'opt_eq', %q{ 1 == 1 }, ],
[ 'opt_eq', <<-'},', ], # {
class X; def == other; true; end; end
X.new == true
},
[ 'opt_neq', %q{ 1 != 0 }, ],
[ 'opt_neq', <<-'},', ], # {
class X; def != other; true; end; end
X.new != true
},
[ 'opt_lt', %q{ -1 < 0 }, ],
[ 'opt_lt', %q{ -1.0 < 0.0 }, ],
[ 'opt_lt', %q{ -0.0.prev_float < 0.0 }, ],
[ 'opt_lt', %q{ ?a < ?z }, ],
[ 'opt_le', %q{ -1 <= 0 }, ],
[ 'opt_le', %q{ -1.0 <= 0.0 }, ],
[ 'opt_le', %q{ -0.0.prev_float <= 0.0 }, ],
[ 'opt_le', %q{ ?a <= ?z }, ],
[ 'opt_gt', %q{ 1 > 0 }, ],
[ 'opt_gt', %q{ 1.0 > 0.0 }, ],
[ 'opt_gt', %q{ +0.0.next_float > 0.0 }, ],
[ 'opt_gt', %q{ ?z > ?a }, ],
[ 'opt_ge', %q{ 1 >= 0 }, ],
[ 'opt_ge', %q{ 1.0 >= 0.0 }, ],
[ 'opt_ge', %q{ +0.0.next_float >= 0.0 }, ],
[ 'opt_ge', %q{ ?z >= ?a }, ],
[ 'opt_ltlt', %q{ +'' << 'true' }, ],
[ 'opt_ltlt', %q{ ([] << 'true').join }, ],
[ 'opt_ltlt', %q{ (1 << 31) == 2147483648 }, ],
[ 'opt_aref', %q{ ['true'][0] }, ],
[ 'opt_aref', %q{ { 0 => 'true'}[0] }, ],
[ 'opt_aref', %q{ 'true'[0] == ?t }, ],
[ 'opt_aset', %q{ [][0] = true }, ],
[ 'opt_aset', %q{ {}[0] = true }, ],
[ 'opt_aset', %q{ x = +'frue'; x[0] = 't'; x }, ],
[ 'opt_aset', <<-'},', ], # {
# opt_aref / opt_aset mixup situation
class X; def x; {}; end; end
x = X.new
x&.x[true] ||= true # here
},
[ 'opt_aref_with', %q{ { 'true' => true }['true'] }, ],
[ 'opt_aref_with', %q{ Struct.new(:nil).new['nil'].nil? }, ],
[ 'opt_aset_with', %q{ {}['true'] = true }, ],
[ 'opt_aset_with', %q{ Struct.new(:true).new['true'] = true }, ],
[ 'opt_length', %q{ 'true' .length == 4 }, ],
[ 'opt_length', %q{ :true .length == 4 }, ],
[ 'opt_length', %q{ [ 'true' ] .length == 1 }, ],
[ 'opt_length', %q{ { 'true' => 1 }.length == 1 }, ],
[ 'opt_size', %q{ 'true' .size == 4 }, ],
[ 'opt_size', %q{ 1.size >= 4 }, ],
[ 'opt_size', %q{ [ 'true' ] .size == 1 }, ],
[ 'opt_size', %q{ { 'true' => 1 }.size == 1 }, ],
[ 'opt_empty_p', %q{ ''.empty? }, ],
[ 'opt_empty_p', %q{ [].empty? }, ],
[ 'opt_empty_p', %q{ {}.empty? }, ],
2021-06-28 17:01:53 +03:00
[ 'opt_empty_p', %q{ Thread::Queue.new.empty? }, ],
[ 'opt_succ', %q{ 1.succ == 2 }, ],
if defined? $FIXNUM_MAX then
[ 'opt_succ',%Q{ #{ $FIXNUM_MAX }.succ == #{ $FIXNUM_MAX + 1 } }, ]
end,
[ 'opt_succ', %q{ '1'.succ == '2' }, ],
[ 'opt_not', %q{ ! false }, ],
[ 'opt_neq', <<-'},', ], # {
class X; def !; true; end; end
! X.new
},
2019-09-02 08:33:29 +03:00
[ 'opt_regexpmatch2', %q{ /true/ =~ 'true' && $~ }, ],
[ 'opt_regexpmatch2', <<-'},', ], # {
class Regexp; def =~ other; true; end; end
/true/ =~ 'true'
},
[ 'opt_regexpmatch2', %q{ 'true' =~ /true/ && $~ }, ],
[ 'opt_regexpmatch2', <<-'},', ], # {
class String; def =~ other; true; end; end
'true' =~ /true/
},
]
# normal path
tests.compact.each do |(insn, expr, *a)|
if a.last.is_a?(Hash)
a = a.dup
kw = a.pop
assert_equal 'true', expr, insn, *a, **kw
else
assert_equal 'true', expr, insn, *a
end
end
# with trace
tests.compact.each {|(insn, expr, *a)|
progn = "set_trace_func(proc{})\n" + expr
if a.last.is_a?(Hash)
a = a.dup
kw = a.pop
assert_equal 'true', progn, 'trace_' + insn, *a, **kw
else
assert_equal 'true', progn, 'trace_' + insn, *a
end
}
assert_normal_exit("#{<<-"begin;"}\n#{<<-'end;'}")
begin;
RubyVM::InstructionSequence.compile("", debug_level: 5)
end;