typeprof 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +26 -0
  3. data/.gitignore +7 -0
  4. data/.gitmodules +6 -0
  5. data/Gemfile +12 -0
  6. data/Gemfile.lock +41 -0
  7. data/README.md +53 -0
  8. data/Rakefile +10 -0
  9. data/doc/doc.ja.md +415 -0
  10. data/doc/doc.md +429 -0
  11. data/doc/ppl2019.pdf +0 -0
  12. data/exe/typeprof +5 -0
  13. data/lib/typeprof.rb +13 -0
  14. data/lib/typeprof/analyzer.rb +1911 -0
  15. data/lib/typeprof/builtin.rb +554 -0
  16. data/lib/typeprof/cli.rb +110 -0
  17. data/lib/typeprof/container-type.rb +626 -0
  18. data/lib/typeprof/export.rb +203 -0
  19. data/lib/typeprof/import.rb +546 -0
  20. data/lib/typeprof/insns-def.rb +61 -0
  21. data/lib/typeprof/iseq.rb +387 -0
  22. data/lib/typeprof/method.rb +267 -0
  23. data/lib/typeprof/type.rb +1092 -0
  24. data/lib/typeprof/utils.rb +209 -0
  25. data/run.sh +3 -0
  26. data/smoke/alias.rb +30 -0
  27. data/smoke/alias2.rb +19 -0
  28. data/smoke/any-cbase.rb +5 -0
  29. data/smoke/any1.rb +15 -0
  30. data/smoke/any2.rb +17 -0
  31. data/smoke/arguments.rb +16 -0
  32. data/smoke/array-each.rb +14 -0
  33. data/smoke/array-each2.rb +15 -0
  34. data/smoke/array-each3.rb +15 -0
  35. data/smoke/array-ltlt.rb +13 -0
  36. data/smoke/array-ltlt2.rb +16 -0
  37. data/smoke/array-map.rb +11 -0
  38. data/smoke/array-map2.rb +10 -0
  39. data/smoke/array-map3.rb +22 -0
  40. data/smoke/array-mul.rb +17 -0
  41. data/smoke/array-plus1.rb +10 -0
  42. data/smoke/array-plus2.rb +15 -0
  43. data/smoke/array-pop.rb +11 -0
  44. data/smoke/array-replace.rb +12 -0
  45. data/smoke/array-s-aref.rb +11 -0
  46. data/smoke/array1.rb +26 -0
  47. data/smoke/array10.rb +14 -0
  48. data/smoke/array11.rb +13 -0
  49. data/smoke/array12.rb +24 -0
  50. data/smoke/array13.rb +30 -0
  51. data/smoke/array14.rb +13 -0
  52. data/smoke/array2.rb +27 -0
  53. data/smoke/array3.rb +25 -0
  54. data/smoke/array4.rb +14 -0
  55. data/smoke/array5.rb +13 -0
  56. data/smoke/array6.rb +14 -0
  57. data/smoke/array7.rb +13 -0
  58. data/smoke/array8.rb +13 -0
  59. data/smoke/array9.rb +12 -0
  60. data/smoke/attr.rb +28 -0
  61. data/smoke/backtrace.rb +32 -0
  62. data/smoke/block1.rb +22 -0
  63. data/smoke/block10.rb +14 -0
  64. data/smoke/block11.rb +39 -0
  65. data/smoke/block12.rb +22 -0
  66. data/smoke/block2.rb +14 -0
  67. data/smoke/block3.rb +38 -0
  68. data/smoke/block4.rb +18 -0
  69. data/smoke/block5.rb +18 -0
  70. data/smoke/block6.rb +20 -0
  71. data/smoke/block7.rb +20 -0
  72. data/smoke/block8.rb +27 -0
  73. data/smoke/block9.rb +12 -0
  74. data/smoke/blown.rb +12 -0
  75. data/smoke/break1.rb +18 -0
  76. data/smoke/break2.rb +15 -0
  77. data/smoke/case.rb +16 -0
  78. data/smoke/case2.rb +17 -0
  79. data/smoke/class.rb +5 -0
  80. data/smoke/class_instance_var.rb +9 -0
  81. data/smoke/class_method.rb +25 -0
  82. data/smoke/class_method2.rb +21 -0
  83. data/smoke/class_method3.rb +27 -0
  84. data/smoke/constant1.rb +38 -0
  85. data/smoke/constant2.rb +33 -0
  86. data/smoke/constant3.rb +9 -0
  87. data/smoke/constant4.rb +11 -0
  88. data/smoke/context-sensitive1.rb +12 -0
  89. data/smoke/cvar.rb +28 -0
  90. data/smoke/cvar2.rb +17 -0
  91. data/smoke/demo.rb +80 -0
  92. data/smoke/demo1.rb +16 -0
  93. data/smoke/demo10.rb +20 -0
  94. data/smoke/demo11.rb +11 -0
  95. data/smoke/demo2.rb +14 -0
  96. data/smoke/demo3.rb +16 -0
  97. data/smoke/demo4.rb +27 -0
  98. data/smoke/demo5.rb +13 -0
  99. data/smoke/demo6.rb +21 -0
  100. data/smoke/demo7.rb +14 -0
  101. data/smoke/demo8.rb +18 -0
  102. data/smoke/demo9.rb +18 -0
  103. data/smoke/dummy-execution1.rb +14 -0
  104. data/smoke/dummy-execution2.rb +16 -0
  105. data/smoke/ensure1.rb +20 -0
  106. data/smoke/enumerator.rb +15 -0
  107. data/smoke/expandarray1.rb +22 -0
  108. data/smoke/expandarray2.rb +23 -0
  109. data/smoke/fib.rb +28 -0
  110. data/smoke/flow1.rb +16 -0
  111. data/smoke/flow2.rb +14 -0
  112. data/smoke/flow3.rb +14 -0
  113. data/smoke/flow4.rb +5 -0
  114. data/smoke/flow5.rb +19 -0
  115. data/smoke/flow6.rb +19 -0
  116. data/smoke/flow7.rb +26 -0
  117. data/smoke/for.rb +9 -0
  118. data/smoke/freeze.rb +11 -0
  119. data/smoke/function.rb +16 -0
  120. data/smoke/gvar.rb +13 -0
  121. data/smoke/hash-fetch.rb +27 -0
  122. data/smoke/hash1.rb +18 -0
  123. data/smoke/hash2.rb +12 -0
  124. data/smoke/hash3.rb +13 -0
  125. data/smoke/hash4.rb +10 -0
  126. data/smoke/hash5.rb +14 -0
  127. data/smoke/inheritance.rb +34 -0
  128. data/smoke/inheritance2.rb +29 -0
  129. data/smoke/initialize.rb +26 -0
  130. data/smoke/instance_eval.rb +18 -0
  131. data/smoke/int_times.rb +14 -0
  132. data/smoke/integer.rb +10 -0
  133. data/smoke/ivar.rb +29 -0
  134. data/smoke/ivar2.rb +30 -0
  135. data/smoke/kernel-class.rb +12 -0
  136. data/smoke/keyword1.rb +11 -0
  137. data/smoke/keyword2.rb +11 -0
  138. data/smoke/keyword3.rb +12 -0
  139. data/smoke/keyword4.rb +11 -0
  140. data/smoke/keyword5.rb +15 -0
  141. data/smoke/kwsplat1.rb +42 -0
  142. data/smoke/kwsplat2.rb +12 -0
  143. data/smoke/manual-rbs.rb +27 -0
  144. data/smoke/manual-rbs.rbs +3 -0
  145. data/smoke/manual-rbs2.rb +20 -0
  146. data/smoke/manual-rbs2.rbs +8 -0
  147. data/smoke/masgn1.rb +13 -0
  148. data/smoke/masgn2.rb +17 -0
  149. data/smoke/masgn3.rb +12 -0
  150. data/smoke/method_in_branch.rb +22 -0
  151. data/smoke/module1.rb +29 -0
  152. data/smoke/module2.rb +28 -0
  153. data/smoke/module3.rb +33 -0
  154. data/smoke/module4.rb +29 -0
  155. data/smoke/module_function1.rb +28 -0
  156. data/smoke/module_function2.rb +28 -0
  157. data/smoke/multiple-include.rb +14 -0
  158. data/smoke/multiple-superclass.rb +16 -0
  159. data/smoke/next1.rb +20 -0
  160. data/smoke/next2.rb +16 -0
  161. data/smoke/object-send1.rb +22 -0
  162. data/smoke/once.rb +12 -0
  163. data/smoke/optional1.rb +13 -0
  164. data/smoke/optional2.rb +15 -0
  165. data/smoke/parameterizedd-self.rb +18 -0
  166. data/smoke/pathname1.rb +13 -0
  167. data/smoke/pathname2.rb +13 -0
  168. data/smoke/printf.rb +20 -0
  169. data/smoke/proc.rb +19 -0
  170. data/smoke/proc2.rb +16 -0
  171. data/smoke/proc3.rb +14 -0
  172. data/smoke/proc4.rb +11 -0
  173. data/smoke/range.rb +13 -0
  174. data/smoke/redo1.rb +21 -0
  175. data/smoke/redo2.rb +22 -0
  176. data/smoke/req-keyword.rb +12 -0
  177. data/smoke/rescue1.rb +20 -0
  178. data/smoke/rescue2.rb +22 -0
  179. data/smoke/respond_to.rb +22 -0
  180. data/smoke/rest-farg.rb +10 -0
  181. data/smoke/rest1.rb +25 -0
  182. data/smoke/rest2.rb +30 -0
  183. data/smoke/rest3.rb +36 -0
  184. data/smoke/rest4.rb +18 -0
  185. data/smoke/rest5.rb +10 -0
  186. data/smoke/rest6.rb +11 -0
  187. data/smoke/retry1.rb +20 -0
  188. data/smoke/return.rb +13 -0
  189. data/smoke/reveal.rb +13 -0
  190. data/smoke/singleton_class.rb +8 -0
  191. data/smoke/singleton_method.rb +9 -0
  192. data/smoke/step.rb +17 -0
  193. data/smoke/string-split.rb +11 -0
  194. data/smoke/struct.rb +9 -0
  195. data/smoke/struct2.rb +24 -0
  196. data/smoke/super1.rb +50 -0
  197. data/smoke/super2.rb +16 -0
  198. data/smoke/super3.rb +19 -0
  199. data/smoke/svar1.rb +12 -0
  200. data/smoke/tap1.rb +17 -0
  201. data/smoke/toplevel.rb +12 -0
  202. data/smoke/two-map.rb +17 -0
  203. data/smoke/type_var.rb +10 -0
  204. data/smoke/typed_method.rb +15 -0
  205. data/smoke/union-recv.rb +29 -0
  206. data/smoke/variadic1.rb.notyet +5 -0
  207. data/smoke/wrong-extend.rb +25 -0
  208. data/smoke/wrong-include.rb +26 -0
  209. data/smoke/wrong-rbs.rb +15 -0
  210. data/smoke/wrong-rbs.rbs +7 -0
  211. data/testbed/ao.rb +297 -0
  212. data/testbed/diff-lcs-entrypoint.rb +4 -0
  213. data/testbed/goodcheck-Gemfile.lock +51 -0
  214. data/tools/coverage.rb +14 -0
  215. data/tools/setup-insns-def.rb +30 -0
  216. data/tools/stackprof-wrapper.rb +10 -0
  217. data/typeprof.gemspec +24 -0
  218. metadata +262 -0
@@ -0,0 +1,61 @@
1
+ TypeProf::INSN_TABLE = {:nop=>[],
2
+ :getlocal=>["lindex_t", "rb_num_t"],
3
+ :setlocal=>["lindex_t", "rb_num_t"],
4
+ :getblockparam=>["lindex_t", "rb_num_t"],
5
+ :setblockparam=>["lindex_t", "rb_num_t"],
6
+ :getblockparamproxy=>["lindex_t", "rb_num_t"],
7
+ :getspecial=>["rb_num_t", "rb_num_t"],
8
+ :setspecial=>["rb_num_t"],
9
+ :getinstancevariable=>["ID", "IVC"],
10
+ :setinstancevariable=>["ID", "IVC"],
11
+ :getclassvariable=>["ID"],
12
+ :setclassvariable=>["ID"],
13
+ :getconstant=>["ID"],
14
+ :setconstant=>["ID"],
15
+ :getglobal=>["GENTRY"],
16
+ :setglobal=>["GENTRY"],
17
+ :putnil=>[],
18
+ :putself=>[],
19
+ :putobject=>["VALUE"],
20
+ :putspecialobject=>["rb_num_t"],
21
+ :putstring=>["VALUE"],
22
+ :concatstrings=>["rb_num_t"],
23
+ :tostring=>[],
24
+ :freezestring=>["VALUE"],
25
+ :toregexp=>["rb_num_t", "rb_num_t"],
26
+ :intern=>[],
27
+ :newarray=>["rb_num_t"],
28
+ :newarraykwsplat=>["rb_num_t"],
29
+ :duparray=>["VALUE"],
30
+ :duphash=>["VALUE"],
31
+ :expandarray=>["rb_num_t", "rb_num_t"],
32
+ :concatarray=>[],
33
+ :splatarray=>["VALUE"],
34
+ :newhash=>["rb_num_t"],
35
+ :newrange=>["rb_num_t"],
36
+ :pop=>[],
37
+ :dup=>[],
38
+ :dupn=>["rb_num_t"],
39
+ :swap=>[],
40
+ :reverse=>["rb_num_t"],
41
+ :topn=>["rb_num_t"],
42
+ :setn=>["rb_num_t"],
43
+ :adjuststack=>["rb_num_t"],
44
+ :defined=>["rb_num_t", "VALUE", "VALUE"],
45
+ :checkmatch=>["rb_num_t"],
46
+ :checkkeyword=>["lindex_t", "lindex_t"],
47
+ :checktype=>["rb_num_t"],
48
+ :defineclass=>["ID", "ISEQ", "rb_num_t"],
49
+ :definemethod=>["ID", "ISEQ"],
50
+ :definesmethod=>["ID", "ISEQ"],
51
+ :send=>["CALL_DATA", "ISEQ"],
52
+ :invokesuper=>["CALL_DATA", "ISEQ"],
53
+ :invokeblock=>["CALL_DATA"],
54
+ :leave=>[],
55
+ :throw=>["rb_num_t"],
56
+ :jump=>["OFFSET"],
57
+ :branchif=>["OFFSET"],
58
+ :branchunless=>["OFFSET"],
59
+ :branchnil=>["OFFSET"],
60
+ :once=>["ISEQ", "ISE"],
61
+ :invokebuiltin=>["RB_BUILTIN"]}
@@ -0,0 +1,387 @@
1
+ module TypeProf
2
+ class ISeq
3
+ include Utils::StructuralEquality
4
+
5
+ def self.compile(file)
6
+ opt = RubyVM::InstructionSequence.compile_option
7
+ opt[:inline_const_cache] = false
8
+ opt[:peephole_optimization] = false
9
+ opt[:specialized_instruction] = false
10
+ opt[:operands_unification] = false
11
+ opt[:coverage_enabled] = false
12
+ new(RubyVM::InstructionSequence.compile_file(file, **opt).to_a)
13
+ end
14
+
15
+ def self.compile_str(str, path = nil)
16
+ opt = RubyVM::InstructionSequence.compile_option
17
+ opt[:inline_const_cache] = false
18
+ opt[:peephole_optimization] = false
19
+ opt[:specialized_instruction] = false
20
+ opt[:operands_unification] = false
21
+ opt[:coverage_enabled] = false
22
+ new(RubyVM::InstructionSequence.compile(str, path, **opt).to_a)
23
+ end
24
+
25
+ FRESH_ID = [0]
26
+
27
+ def initialize(iseq)
28
+ @id = FRESH_ID[0]
29
+ FRESH_ID[0] += 1
30
+
31
+ _magic, _major_version, _minor_version, _format_type, _misc,
32
+ @name, @path, @absolute_path, @start_lineno, @type,
33
+ @locals, @fargs_format, catch_table, insns = *iseq
34
+
35
+ @insns = []
36
+ @linenos = []
37
+
38
+ labels = setup_iseq(insns)
39
+
40
+ # checkmatch->branch
41
+ # send->branch
42
+
43
+ @catch_table = []
44
+ catch_table.map do |type, iseq, first, last, cont, stack_depth|
45
+ iseq = iseq ? ISeq.new(iseq) : nil
46
+ entry = [type, iseq, labels[cont], stack_depth]
47
+ labels[first].upto(labels[last]) do |i|
48
+ @catch_table[i] ||= []
49
+ @catch_table[i] << entry
50
+ end
51
+ end
52
+
53
+ merge_branches
54
+
55
+ analyze_stack
56
+ end
57
+
58
+ def <=>(other)
59
+ @id <=> other.id
60
+ end
61
+
62
+ def setup_iseq(insns)
63
+ i = 0
64
+ labels = {}
65
+ insns.each do |e|
66
+ if e.is_a?(Symbol) && e.to_s.start_with?("label")
67
+ labels[e] = i
68
+ elsif e.is_a?(Array)
69
+ i += 1
70
+ end
71
+ end
72
+
73
+ lineno = 0
74
+ insns.each do |e|
75
+ case e
76
+ when Integer # lineno
77
+ lineno = e
78
+ when Symbol # label or trace
79
+ nil
80
+ when Array
81
+ insn, *operands = e
82
+ operands = INSN_TABLE[insn].zip(operands).map do |type, operand|
83
+ case type
84
+ when "ISEQ"
85
+ operand && ISeq.new(operand)
86
+ when "lindex_t", "rb_num_t", "VALUE", "ID", "GENTRY", "CALL_DATA"
87
+ operand
88
+ when "OFFSET"
89
+ labels[operand] || raise("unknown label: #{ operand }")
90
+ when "IVC", "ISE"
91
+ raise unless operand.is_a?(Integer)
92
+ :_cache_operand
93
+ else
94
+ raise "unknown operand type: #{ type }"
95
+ end
96
+ end
97
+
98
+ @insns << [insn, operands]
99
+ @linenos << lineno
100
+ else
101
+ raise "unknown iseq entry: #{ e }"
102
+ end
103
+ end
104
+
105
+ @fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
106
+
107
+ labels
108
+ end
109
+
110
+ def merge_branches
111
+ @insns.size.times do |i|
112
+ insn, operands = @insns[i]
113
+ case insn
114
+ when :branchif
115
+ @insns[i] = [:branch, [:if] + operands]
116
+ when :branchunless
117
+ @insns[i] = [:branch, [:unless] + operands]
118
+ when :branchnil
119
+ @insns[i] = [:branch, [:nil] + operands]
120
+ end
121
+ end
122
+ end
123
+
124
+ def source_location(pc)
125
+ "#{ @path }:#{ @linenos[pc] }"
126
+ end
127
+
128
+ attr_reader :name, :path, :abolute_path, :start_lineno, :type, :locals, :fargs_format, :catch_table, :insns, :linenos
129
+ attr_reader :id
130
+
131
+ def pretty_print(q)
132
+ q.text "ISeq["
133
+ q.group do
134
+ q.nest(1) do
135
+ q.breakable ""
136
+ q.text "@type= #{ @type }"
137
+ q.breakable ", "
138
+ q.text "@name= #{ @name }"
139
+ q.breakable ", "
140
+ q.text "@path= #{ @path }"
141
+ q.breakable ", "
142
+ q.text "@absolute_path= #{ @absolute_path }"
143
+ q.breakable ", "
144
+ q.text "@start_lineno= #{ @start_lineno }"
145
+ q.breakable ", "
146
+ q.text "@fargs_format= #{ @fargs_format.inspect }"
147
+ q.breakable ", "
148
+ q.text "@insns="
149
+ q.group(2) do
150
+ @insns.each_with_index do |(insn, *operands), i|
151
+ q.breakable
152
+ q.group(2, "#{ i }: #{ insn.to_s }", "") do
153
+ q.pp operands
154
+ end
155
+ end
156
+ end
157
+ end
158
+ q.breakable
159
+ end
160
+ q.text "]"
161
+ end
162
+
163
+ def analyze_stack
164
+ # gather branch targets
165
+ # TODO: catch_table should be also considered
166
+ branch_targets = {}
167
+ @insns.each do |insn, operands|
168
+ case insn
169
+ when :branch
170
+ branch_targets[operands[1]] = true
171
+ when :jump
172
+ branch_targets[operands[0]] = true
173
+ end
174
+ end
175
+
176
+ # find a pattern: getlocal, ..., send (is_a?, respond_to?), branch
177
+ send_branch_list = []
178
+ (@insns.size - 1).times do |i|
179
+ insn, operands = @insns[i]
180
+ if insn == :getlocal && operands[1] == 0
181
+ j = i + 1
182
+ sp = 1
183
+ while @insns[j]
184
+ sp = check_send_branch(sp, j)
185
+ if sp == :match
186
+ send_branch_list << [i, j]
187
+ break
188
+ end
189
+ break if !sp
190
+ j += 1
191
+ end
192
+ end
193
+ end
194
+ send_branch_list.each do |i, j|
195
+ next if (i + 1 .. j).any? {|i| branch_targets[i] }
196
+ _insn, getlocal_operands = @insns[i]
197
+ _insn, send_operands = @insns[j]
198
+ _insn, branch_operands = @insns[j + 1]
199
+ @insns[j] = [:nop]
200
+ @insns[j + 1] = [:send_branch, [getlocal_operands, send_operands, branch_operands]]
201
+ end
202
+
203
+ # find a pattern: getlocal, branch
204
+ (@insns.size - 1).times do |i|
205
+ next if branch_targets[i + 1]
206
+ insn0, getlocal_operands = @insns[i]
207
+ insn1, branch_operands = @insns[i + 1]
208
+ if [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0 && insn1 == :branch
209
+ @insns[i ] = [:nop]
210
+ @insns[i + 1] = [:getlocal_branch, [getlocal_operands, branch_operands]]
211
+ end
212
+ end
213
+
214
+ # flow-sensitive analysis for `case var; when A; when B; when C; end`
215
+ # find a pattern: getlocal, (dup, putobject(true), getconstant(class name), checkmatch, branch)*
216
+ case_branch_list = []
217
+ (@insns.size - 1).times do |i|
218
+ insn0, getlocal_operands = @insns[i]
219
+ next unless [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0
220
+ nops = [i]
221
+ new_insns = []
222
+ j = i + 1
223
+ while true
224
+ case @insns[j]
225
+ when [:dup, []]
226
+ break unless @insns[j + 1] == [:putnil, []]
227
+ break unless @insns[j + 2] == [:putobject, [true]]
228
+ break unless @insns[j + 3][0] == :getconstant # TODO: support A::B::C
229
+ break unless @insns[j + 4] == [:checkmatch, [2]]
230
+ break unless @insns[j + 5][0] == :branch
231
+ target_pc = @insns[j + 5][1][1]
232
+ break unless @insns[target_pc] == [:pop, []]
233
+ nops << j << (j + 4) << target_pc
234
+ new_insns << [j + 5, [:getlocal_checkmatch_branch, [getlocal_operands, @insns[j + 5][1]]]]
235
+ j += 6
236
+ when [:pop, []]
237
+ nops << j
238
+ case_branch_list << [nops, new_insns]
239
+ break
240
+ else
241
+ break
242
+ end
243
+ end
244
+ end
245
+ case_branch_list.each do |nops, new_insns|
246
+ nops.each {|i| @insns[i] = [:nop, []] }
247
+ new_insns.each {|i, insn| @insns[i] = insn }
248
+ end
249
+ end
250
+
251
+ def check_send_branch(sp, j)
252
+ insn, operands = @insns[j]
253
+
254
+ case insn
255
+ when :putspecialobject, :putnil, :putobject, :duparray, :putstring,
256
+ :putself
257
+ sp += 1
258
+ when :newarray, :newarraykwsplat, :newhash, :concatstrings
259
+ len, = operands
260
+ sp =- len
261
+ return nil if sp <= 0
262
+ sp += 1
263
+ when :newhashfromarray
264
+ raise NotImplementedError, "newhashfromarray"
265
+ when :newrange, :tostring
266
+ sp -= 2
267
+ return nil if sp <= 0
268
+ sp += 1
269
+ when :freezestring
270
+ # XXX: should leverage this information?
271
+ when :toregexp
272
+ _regexp_opt, len = operands
273
+ sp -= len
274
+ return nil if sp <= 0
275
+ sp += 1
276
+ when :intern
277
+ sp -= 1
278
+ return nil if sp <= 0
279
+ sp += 1
280
+ when :definemethod, :definesmethod
281
+ when :defineclass
282
+ sp -= 2
283
+ when :send, :invokesuper
284
+ opt, = operands
285
+ _flags = opt[:flag]
286
+ _mid = opt[:mid]
287
+ kw_arg = opt[:kw_arg]
288
+ argc = opt[:orig_argc]
289
+ argc += 1 # receiver
290
+ argc += kw_arg.size if kw_arg
291
+ sp -= argc
292
+ return :match if insn == :send && sp == 0 && @insns[j + 1][0] == :branch
293
+ sp += 1
294
+ when :invokeblock
295
+ opt, = operands
296
+ sp -= opt[:orig_argc]
297
+ return nil if sp <= 0
298
+ sp += 1
299
+ when :invokebuiltin
300
+ raise NotImplementedError
301
+ when :leave, :throw
302
+ return
303
+ when :once
304
+ return # not implemented
305
+ when :branch, :jump
306
+ return # not implemented
307
+ when :setinstancevariable, :setclassvariable, :setglobal
308
+ sp -= 1
309
+ when :setlocal, :setblockparam
310
+ return # conservative
311
+ when :getinstancevariable, :getclassvariable, :getglobal,
312
+ :getlocal, :getblockparam, :getblockparamproxy
313
+ sp += 1
314
+ when :getconstant
315
+ sp -= 2
316
+ return nil if sp <= 0
317
+ sp += 1
318
+ when :setconstant
319
+ sp -= 2
320
+ when :getspecial
321
+ sp += 1
322
+ when :setspecial
323
+ # flip-flop
324
+ raise NotImplementedError, "setspecial"
325
+ when :dup
326
+ sp += 1
327
+ when :duphash
328
+ sp += 1
329
+ when :dupn
330
+ n, = operands
331
+ sp += n
332
+ when :pop
333
+ sp -= 1
334
+ when :swap
335
+ sp -= 2
336
+ return nil if sp <= 0
337
+ sp += 2
338
+ when :reverse
339
+ n, = operands
340
+ sp -= n
341
+ return nil if sp <= 0
342
+ sp += n
343
+ when :defined
344
+ sp -= 1
345
+ return nil if sp <= 0
346
+ sp += 1
347
+ when :checkmatch
348
+ sp -= 2
349
+ return nil if sp <= 0
350
+ sp += 1
351
+ when :checkkeyword
352
+ sp += 1
353
+ when :adjuststack
354
+ n, = operands
355
+ sp -= n
356
+ when :nop
357
+ when :setn
358
+ return nil # not implemented
359
+ when :topn
360
+ sp += 1
361
+ when :splatarray
362
+ sp -= 1
363
+ return nil if sp <= 0
364
+ sp += 1
365
+ when :expandarray
366
+ num, flag = operands
367
+ splat = flag & 1 == 1
368
+ sp -= 1
369
+ return nil if sp <= 0
370
+ sp += num + (splat ? 1 : 0)
371
+ when :concatarray
372
+ sp -= 2
373
+ return nil if sp <= 0
374
+ sp += 1
375
+ when :checktype
376
+ sp -= 1
377
+ return nil if sp <= 0
378
+ sp += 1
379
+ else
380
+ raise "Unknown insn: #{ insn }"
381
+ end
382
+
383
+ return nil if sp <= 0
384
+ sp
385
+ end
386
+ end
387
+ end
@@ -0,0 +1,267 @@
1
+ module TypeProf
2
+ class MethodDef
3
+ include Utils::StructuralEquality
4
+ end
5
+
6
+ class ISeqMethodDef < MethodDef
7
+ def initialize(iseq, cref)
8
+ @iseq = iseq
9
+ raise if iseq.nil?
10
+ @cref = cref
11
+ end
12
+
13
+ def do_send(recv, mid, aargs, caller_ep, caller_env, scratch, &ctn)
14
+ recv = scratch.globalize_type(recv, caller_env, caller_ep)
15
+ aargs = scratch.globalize_type(aargs, caller_env, caller_ep)
16
+
17
+ aargs.each_formal_arguments(@iseq.fargs_format) do |fargs, start_pc|
18
+ if fargs.is_a?(String)
19
+ scratch.error(caller_ep, fargs)
20
+ ctn[Type.any, caller_ep, caller_env]
21
+ next
22
+ end
23
+
24
+ callee_ep = do_send_core(fargs, start_pc, recv, mid, scratch)
25
+
26
+ scratch.add_iseq_method_call!(self, callee_ep.ctx)
27
+ scratch.add_callsite!(callee_ep.ctx, fargs, caller_ep, caller_env, &ctn)
28
+ end
29
+ end
30
+
31
+ def do_send_core(fargs, start_pc, recv, mid, scratch)
32
+ lead_num = @iseq.fargs_format[:lead_num] || 0
33
+ post_start = @iseq.fargs_format[:post_start]
34
+ rest_start = @iseq.fargs_format[:rest_start]
35
+ kw_start = @iseq.fargs_format[:kwbits]
36
+ kw_start -= @iseq.fargs_format[:keyword].size if kw_start
37
+ block_start = @iseq.fargs_format[:block_start]
38
+
39
+ # XXX: need to check .rbs fargs and .rb fargs
40
+
41
+ ctx = Context.new(@iseq, @cref, mid) # XXX: to support opts, rest, etc
42
+ callee_ep = ExecutionPoint.new(ctx, start_pc, nil)
43
+
44
+ locals = [Type.nil] * @iseq.locals.size
45
+ nenv = Env.new(StaticEnv.new(recv, fargs.blk_ty, false), locals, [], Utils::HashWrapper.new({}))
46
+ alloc_site = AllocationSite.new(callee_ep)
47
+ idx = 0
48
+ fargs.lead_tys.each_with_index do |ty, i|
49
+ alloc_site2 = alloc_site.add_id(idx += 1)
50
+ # nenv is top-level, so it is okay to call Type#localize directly
51
+ nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
52
+ nenv = nenv.local_update(i, ty)
53
+ end
54
+ if fargs.opt_tys
55
+ fargs.opt_tys.each_with_index do |ty, i|
56
+ alloc_site2 = alloc_site.add_id(idx += 1)
57
+ nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
58
+ nenv = nenv.local_update(lead_num + i, ty)
59
+ end
60
+ end
61
+ if fargs.rest_ty
62
+ alloc_site2 = alloc_site.add_id(idx += 1)
63
+ ty = Type::Array.new(Type::Array::Elements.new([], fargs.rest_ty), Type::Instance.new(Type::Builtin[:ary]))
64
+ nenv, rest_ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
65
+ nenv = nenv.local_update(rest_start, rest_ty)
66
+ end
67
+ if fargs.post_tys
68
+ fargs.post_tys.each_with_index do |ty, i|
69
+ alloc_site2 = alloc_site.add_id(idx += 1)
70
+ nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
71
+ nenv = nenv.local_update(post_start + i, ty)
72
+ end
73
+ end
74
+ if fargs.kw_tys
75
+ fargs.kw_tys.each_with_index do |(_, _, ty), i|
76
+ alloc_site2 = alloc_site.add_id(idx += 1)
77
+ nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
78
+ nenv = nenv.local_update(kw_start + i, ty)
79
+ end
80
+ end
81
+ # kwrest
82
+ nenv = nenv.local_update(block_start, fargs.blk_ty) if block_start
83
+
84
+ scratch.merge_env(callee_ep, nenv)
85
+
86
+ callee_ep
87
+ end
88
+
89
+ def do_check_send_core(fargs, recv, mid, ep, scratch)
90
+ lead_num = @iseq.fargs_format[:lead_num] || 0
91
+ post_num = @iseq.fargs_format[:post_num] || 0
92
+ rest_start = @iseq.fargs_format[:rest_start]
93
+ opt = @iseq.fargs_format[:opt] || [0]
94
+
95
+ # TODO: check keywords
96
+ if rest_start
97
+ # almost ok
98
+ else
99
+ if fargs.lead_tys.size + fargs.post_tys.size < lead_num + post_num
100
+ scratch.error(ep, "RBS says that the arity may be %d, but the method definition requires at least %d arguments" % [fargs.lead_tys.size + fargs.post_tys.size, lead_num + post_num])
101
+ return
102
+ end
103
+ if fargs.lead_tys.size + fargs.opt_tys.size + fargs.post_tys.size > lead_num + opt.size - 1 + post_num
104
+ scratch.error(ep, "RBS says that the arity may be %d, but the method definition requires at most %d arguments" % [fargs.lead_tys.size + fargs.opt_tys.size + fargs.post_tys.size, lead_num + opt.size - 1 + post_num])
105
+ return
106
+ end
107
+ end
108
+ do_send_core(fargs, 0, recv, mid, scratch)
109
+ end
110
+ end
111
+
112
+ class AttrMethodDef < MethodDef
113
+ def initialize(ivar, kind)
114
+ @ivar = ivar
115
+ @kind = kind # :reader | :writer
116
+ end
117
+
118
+ attr_reader :ivar, :kind
119
+
120
+ def do_send(recv, mid, aargs, caller_ep, caller_env, scratch, &ctn)
121
+ case @kind
122
+ when :reader
123
+ if aargs.lead_tys.size == 0
124
+ scratch.get_instance_variable(recv, @ivar, caller_ep, caller_env) do |ty, nenv|
125
+ ctn[ty, caller_ep, nenv]
126
+ end
127
+ else
128
+ ctn[Type.any, caller_ep, caller_env]
129
+ end
130
+ when :writer
131
+ if aargs.lead_tys.size == 1
132
+ ty = aargs.lead_tys[0]
133
+ scratch.set_instance_variable(recv, @ivar, ty, caller_ep, caller_env)
134
+ ctn[ty, caller_ep, caller_env]
135
+ else
136
+ ctn[Type.any, caller_ep, caller_env]
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ class TypedMethodDef < MethodDef
143
+ def initialize(sigs, rbs_source) # sigs: Array<[FormalArguments, (return)Type]>
144
+ @sigs = sigs
145
+ @rbs_source = rbs_source
146
+ end
147
+
148
+ attr_reader :rbs_source
149
+
150
+ def do_send(recv_orig, mid, aargs, caller_ep, caller_env, scratch, &ctn)
151
+ recv = scratch.globalize_type(recv_orig, caller_env, caller_ep)
152
+ found = false
153
+ aargs = scratch.globalize_type(aargs, caller_env, caller_ep)
154
+ @sigs.each do |fargs, ret_ty|
155
+ ncaller_env = caller_env
156
+ # XXX: need to interpret args more correctly
157
+ #pp [mid, aargs, fargs]
158
+ # XXX: support self type in fargs
159
+ subst = { Type::Var.new(:self) => recv }
160
+ next unless aargs.consistent_with_formal_arguments?(fargs, subst)
161
+ if recv.is_a?(Type::Array) && recv_orig.is_a?(Type::LocalArray)
162
+ tyvar_elem = Type::Var.new(:Elem)
163
+ if subst[tyvar_elem]
164
+ ty = subst[tyvar_elem]
165
+ alloc_site = AllocationSite.new(caller_ep).add_id(self)
166
+ ncaller_env, ty = scratch.localize_type(ty, ncaller_env, caller_ep)
167
+ ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id) do |elems|
168
+ elems.update(nil, ty)
169
+ end
170
+ end
171
+ subst.merge!({ tyvar_elem => recv.elems.squash })
172
+ elsif recv.is_a?(Type::Hash) && recv_orig.is_a?(Type::LocalHash)
173
+ tyvar_k = Type::Var.new(:K)
174
+ tyvar_v = Type::Var.new(:V)
175
+ # XXX: need to support destructive operation
176
+ k_ty, v_ty = recv.elems.squash
177
+ # XXX: need to heuristically replace ret type Hash[K, V] with self, instead of conversative type?
178
+ subst.merge!({ tyvar_k => k_ty, tyvar_v => v_ty })
179
+ end
180
+ ret_ty = ret_ty.substitute(subst, Config.options[:type_depth_limit])
181
+ found = true
182
+ if aargs.blk_ty.is_a?(Type::ISeqProc)
183
+ dummy_ctx = TypedContext.new(caller_ep, mid)
184
+ dummy_ep = ExecutionPoint.new(dummy_ctx, -1, caller_ep)
185
+ dummy_env = Env.new(StaticEnv.new(recv, fargs.blk_ty, false), [], [], Utils::HashWrapper.new({}))
186
+ if fargs.blk_ty.is_a?(Type::TypedProc)
187
+ scratch.add_callsite!(dummy_ctx, nil, caller_ep, ncaller_env, &ctn)
188
+ nfargs = fargs.blk_ty.fargs
189
+ alloc_site = AllocationSite.new(caller_ep).add_id(self)
190
+ nfargs = nfargs.map.with_index do |nfarg, i|
191
+ if recv.is_a?(Type::Array)
192
+ tyvar_elem = Type::Var.new(:Elem)
193
+ nfarg = nfarg.substitute(subst.merge({ tyvar_elem => recv.elems.squash }), Config.options[:type_depth_limit])
194
+ else
195
+ nfarg = nfarg.substitute(subst, Config.options[:type_depth_limit])
196
+ end
197
+ nfarg = nfarg.remove_type_vars
198
+ alloc_site2 = alloc_site.add_id(i)
199
+ dummy_env, nfarg = scratch.localize_type(nfarg, dummy_env, dummy_ep, alloc_site2)
200
+ nfarg
201
+ end
202
+ naargs = ActualArguments.new(nfargs, nil, nil, Type.nil) # XXX: support block to block?
203
+ scratch.do_invoke_block(false, aargs.blk_ty, naargs, dummy_ep, dummy_env) do |blk_ret_ty, _ep, _env|
204
+ subst2 = {}
205
+ if blk_ret_ty.consistent?(fargs.blk_ty.ret_ty, subst2)
206
+ if recv.is_a?(Type::Array) && recv_orig.is_a?(Type::LocalArray)
207
+ tyvar_elem = Type::Var.new(:Elem)
208
+ if subst2[tyvar_elem]
209
+ ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id) do |elems|
210
+ elems.update(nil, subst2[tyvar_elem])
211
+ end
212
+ scratch.merge_return_env(caller_ep) {|env| env ? env.merge(ncaller_env) : ncaller_env }
213
+ end
214
+ ret_ty = ret_ty.substitute(subst2, Config.options[:type_depth_limit])
215
+ else
216
+ ret_ty = ret_ty.substitute(subst2, Config.options[:type_depth_limit])
217
+ end
218
+ else
219
+ # raise "???"
220
+ # XXX: need warning
221
+ ret_ty = Type.any
222
+ end
223
+ ret_ty = ret_ty.remove_type_vars
224
+ # XXX: check the return type from the block
225
+ # sig.blk_ty.ret_ty.eql?(_ret_ty) ???
226
+ scratch.add_return_type!(dummy_ctx, ret_ty)
227
+ end
228
+ # scratch.add_return_type!(dummy_ctx, ret_ty) ?
229
+ # This makes `def foo; 1.times { return "str" }; end` return Integer|String
230
+ else
231
+ # XXX: a block is passed to a method that does not accept block.
232
+ # Should we call the passed block with any arguments?
233
+ ret_ty = ret_ty.remove_type_vars
234
+ ctn[ret_ty, caller_ep, ncaller_env]
235
+ end
236
+ else
237
+ ret_ty = ret_ty.remove_type_vars
238
+ ctn[ret_ty, caller_ep, ncaller_env]
239
+ end
240
+ end
241
+
242
+ unless found
243
+ scratch.error(caller_ep, "failed to resolve overload: #{ recv.screen_name(scratch) }##{ mid }")
244
+ ctn[Type.any, caller_ep, caller_env]
245
+ end
246
+ end
247
+
248
+ def do_match_iseq_mdef(iseq_mdef, recv, mid, env, ep, scratch)
249
+ recv = scratch.globalize_type(recv, env, ep)
250
+ @sigs.each do |fargs, _ret_ty|
251
+ iseq_mdef.do_check_send_core(fargs, recv, mid, ep, scratch)
252
+ end
253
+ end
254
+ end
255
+
256
+ class CustomMethodDef < MethodDef
257
+ def initialize(impl)
258
+ @impl = impl
259
+ end
260
+
261
+ def do_send(recv, mid, aargs, caller_ep, caller_env, scratch, &ctn)
262
+ # XXX: ctn?
263
+ scratch.merge_return_env(caller_ep) {|env| env ? env.merge(caller_env) : caller_env } # for Kernel#lambda
264
+ @impl[recv, mid, aargs, caller_ep, caller_env, scratch, &ctn]
265
+ end
266
+ end
267
+ end