crystalizer 0.2.2

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 (126) hide show
  1. data/Changelog +27 -0
  2. data/README +79 -0
  3. data/Rakefile +24 -0
  4. data/TODO +14 -0
  5. data/VERSION +1 -0
  6. data/benchmarks/bench.rb +129 -0
  7. data/benchmarks/concretize_test.rb +26 -0
  8. data/benchmarks/extconf.rb +10 -0
  9. data/benchmarks/tak_rb.rb +7 -0
  10. data/benchmarks/tak_so.rb +7 -0
  11. data/benchmarks/tak_source.rb +16 -0
  12. data/bin/rb2cx +162 -0
  13. data/doc/eval2c.txt +246 -0
  14. data/doc/gen_html.rb +26 -0
  15. data/doc/html_template +10 -0
  16. data/doc/index.txt +169 -0
  17. data/doc/limitations.txt +529 -0
  18. data/doc/optimizations.txt +185 -0
  19. data/doc/rb2cx.txt +130 -0
  20. data/doc/style.css +27 -0
  21. data/lib/concretizer.rb +3 -0
  22. data/lib/ruby2cext/c_function.rb +617 -0
  23. data/lib/ruby2cext/common_node_comp.rb +1412 -0
  24. data/lib/ruby2cext/compiler.rb +311 -0
  25. data/lib/ruby2cext/concretize.rb +269 -0
  26. data/lib/ruby2cext/error.rb +15 -0
  27. data/lib/ruby2cext/eval2c.rb +126 -0
  28. data/lib/ruby2cext/parser.rb +36 -0
  29. data/lib/ruby2cext/plugin.rb +24 -0
  30. data/lib/ruby2cext/plugins/builtin_methods.rb +817 -0
  31. data/lib/ruby2cext/plugins/cache_call.rb +293 -0
  32. data/lib/ruby2cext/plugins/case_optimize.rb +102 -0
  33. data/lib/ruby2cext/plugins/const_cache.rb +36 -0
  34. data/lib/ruby2cext/plugins/direct_self_call.rb +70 -0
  35. data/lib/ruby2cext/plugins/inline_builtin.rb +797 -0
  36. data/lib/ruby2cext/plugins/inline_methods.rb +68 -0
  37. data/lib/ruby2cext/plugins/ivar_cache.rb +147 -0
  38. data/lib/ruby2cext/plugins/require_include.rb +69 -0
  39. data/lib/ruby2cext/plugins/util.rb +154 -0
  40. data/lib/ruby2cext/plugins/warnings.rb +121 -0
  41. data/lib/ruby2cext/scopes.rb +225 -0
  42. data/lib/ruby2cext/str_to_c_strlit.rb +12 -0
  43. data/lib/ruby2cext/tools.rb +80 -0
  44. data/lib/ruby2cext/version.rb +22 -0
  45. data/results +68 -0
  46. data/setup.rb +1585 -0
  47. data/stuff/builtin_methods.rb +69 -0
  48. data/stuff/builtin_methods_test.rb +37 -0
  49. data/test/bootstrap.rb +10 -0
  50. data/test/causes_crash_all_opts.rb +1165 -0
  51. data/test/eval2c/test_eval2c.rb +37 -0
  52. data/test/temp_17.rb +16 -0
  53. data/test/temp_18.rb +8 -0
  54. data/test/temp_19.rb +8 -0
  55. data/test/temp_2.rb +7 -0
  56. data/test/temp_20.rb +5 -0
  57. data/test/temp_21.rb +161 -0
  58. data/test/temp_22.rb +7 -0
  59. data/test/temp_23.rb +7 -0
  60. data/test/temp_24.rb +219 -0
  61. data/test/temp_25.rb +7 -0
  62. data/test/temp_26.rb +11 -0
  63. data/test/temp_27.rb +11 -0
  64. data/test/temp_28.rb +9 -0
  65. data/test/temp_29.rb +9 -0
  66. data/test/temp_3.rb +0 -0
  67. data/test/temp_30.rb +0 -0
  68. data/test/temp_31.rb +10 -0
  69. data/test/temp_32.rb +10 -0
  70. data/test/temp_33.rb +15 -0
  71. data/test/temp_34.rb +15 -0
  72. data/test/temp_35.rb +7 -0
  73. data/test/temp_36.rb +7 -0
  74. data/test/temp_37.rb +10 -0
  75. data/test/temp_38.rb +10 -0
  76. data/test/temp_39.rb +0 -0
  77. data/test/temp_4.rb +7 -0
  78. data/test/temp_40.rb +50 -0
  79. data/test/temp_41.rb +50 -0
  80. data/test/temp_42.rb +8 -0
  81. data/test/temp_43.rb +8 -0
  82. data/test/temp_44.rb +0 -0
  83. data/test/temp_48.rb +7 -0
  84. data/test/temp_49.rb +7 -0
  85. data/test/temp_5.rb +7 -0
  86. data/test/temp_59.rb +7 -0
  87. data/test/temp_6.rb +7 -0
  88. data/test/temp_60.rb +7 -0
  89. data/test/temp_68.rb +239 -0
  90. data/test/temp_7.rb +7 -0
  91. data/test/temp_70.rb +7 -0
  92. data/test/temp_71.rb +7 -0
  93. data/test/temp_72.rb +13 -0
  94. data/test/temp_73.rb +7 -0
  95. data/test/temp_74.rb +7 -0
  96. data/test/temp_76.rb +7 -0
  97. data/test/temp_77.rb +13 -0
  98. data/test/temp_79.rb +7 -0
  99. data/test/temp_8.rb +14 -0
  100. data/test/temp_81.rb +14 -0
  101. data/test/temp_83.rb +0 -0
  102. data/test/temp_84.rb +7 -0
  103. data/test/temp_85.rb +7 -0
  104. data/test/temp_86.rb +14 -0
  105. data/test/temp_87.rb +7 -0
  106. data/test/temp_88.rb +7 -0
  107. data/test/temp_89.rb +7 -0
  108. data/test/temp_9.rb +14 -0
  109. data/test/temp_90.rb +0 -0
  110. data/test/temp_91.rb +7 -0
  111. data/test/temp_92.rb +7 -0
  112. data/test/temp_93.rb +7 -0
  113. data/test/temp_94.rb +7 -0
  114. data/test/temp_95.rb +7 -0
  115. data/test/temp_96.rb +0 -0
  116. data/test/temp_97.rb +0 -0
  117. data/test/temp_98.rb +7 -0
  118. data/test/temp_99.rb +7 -0
  119. data/test/test_concretize.rb +132 -0
  120. data/test/test_concretize_all.rb +15 -0
  121. data/test/test_crystalize_block.rb +73 -0
  122. data/test/test_files/test.rb +615 -0
  123. data/test/test_files/vmode_test.rb +73 -0
  124. data/test/test_files/warn_test.rb +35 -0
  125. data/test/test_syntax.rb +25 -0
  126. metadata +268 -0
@@ -0,0 +1,15 @@
1
+
2
+ module Ruby2CExtension
3
+
4
+ class Ruby2CExtError < StandardError
5
+ class NotSupported < self
6
+ end
7
+
8
+ class Bug < self
9
+ def initialize(msg)
10
+ super("BUG! #{msg}")
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,126 @@
1
+
2
+ require "rubynode"
3
+ require "digest/sha1"
4
+
5
+ module Ruby2CExtension
6
+
7
+ class Eval2C
8
+
9
+ attr_reader :path, :prefix, :plugins, :logger, :force_recompile
10
+
11
+ DEFAULT_PATH = File.join(File.expand_path(ENV["HOME"] || ENV["USERPROFILE"] || ENV["HOMEPATH"] || "."), ".ruby2cext")
12
+
13
+ def initialize(options = {})
14
+ unless (@path = options[:path])
15
+ @path = DEFAULT_PATH
16
+ Dir.mkdir(@path, 0700) unless File.exists?(@path)
17
+ end
18
+ @path = File.expand_path(@path)
19
+ unless File.directory?(@path)
20
+ raise Ruby2CExtError, "#{@path} is no directory"
21
+ end
22
+ unless File.stat(@path).mode & 022 == 0 # no writing for group and others
23
+ warn "Ruby2CExtension::Eval2C warning: #{@path} is insecure"
24
+ end
25
+ @prefix = options[:prefix] || "eval2c"
26
+ @plugins = options[:plugins] || {:optimizations => :all}
27
+ @logger = options[:logger]
28
+ @force_recompile = options[:force_recompile]
29
+ @done = {}
30
+ @digest_extra = Ruby2CExtension::FULL_VERSION_STRING + @plugins.inspect.split(//).sort.join("")
31
+ end
32
+
33
+ private
34
+
35
+ def compile_helper(digest_str)
36
+ name = "#{prefix}_#{Digest::SHA1.hexdigest(digest_str + @digest_extra)}"
37
+ gvar_name = "$__#{name}__"
38
+ dl_filename = File.join(path, "#{name}.#{Compiler::DLEXT}")
39
+ if !File.exists?(dl_filename) || (force_recompile && !@done[name])
40
+ c = Compiler.new(name, logger)
41
+ c.add_plugins(plugins)
42
+ yield c, name, gvar_name
43
+ c_filename = File.join(path, "#{name}.c")
44
+ File.open(c_filename, "w") { |f| f.puts(c.to_c_code) }
45
+ unless Compiler.compile_c_file_to_dllib(c_filename, logger) == dl_filename
46
+ raise Ruby2CExtError::Bug, "unexpected return value from compile_c_file_to_dllib"
47
+ end
48
+ @done[name] = true
49
+ end
50
+ require dl_filename
51
+ eval(gvar_name) # return the proc
52
+ end
53
+
54
+ public
55
+
56
+ def compile_to_proc(code_str)
57
+ compile_helper(code_str) { |compiler, name, gvar_name|
58
+ compiler.add_rb_file("#{gvar_name} = proc { #{code_str} }", name)
59
+ }
60
+ end
61
+
62
+ def module_eval(mod, code_str)
63
+ mod.module_eval(&compile_to_proc(code_str))
64
+ end
65
+ alias :class_eval :module_eval
66
+
67
+ def instance_eval(object, code_str)
68
+ object.instance_eval(&compile_to_proc(code_str))
69
+ end
70
+
71
+ def toplevel_eval(code_str)
72
+ compile_to_proc(code_str).call
73
+ end
74
+
75
+ def compile_methods(mod, *methods)
76
+ methods = methods.map { |m| m.to_sym }.uniq
77
+ defs = methods.map { |m|
78
+ bnode = mod.instance_method(m).body_node
79
+ unless bnode.type == :scope
80
+ raise Ruby2CExtError, "the method #{m} cannot be compiled, only methods defined using def can be compiled"
81
+ end
82
+ [:defn, {:mid => m, :defn => bnode.transform(Compiler::NODE_TRANSFORM_OPTIONS)}]
83
+ }
84
+ node_tree = [:scope, {:next => [:gasgn, {:vid => :$test, :value =>
85
+ [:iter, {
86
+ :var => false,
87
+ :iter => [:fcall, {:args => false, :mid => :proc}],
88
+ :body => [:block, defs]
89
+ }]
90
+ }]}]
91
+ # save current visibility of the methods
92
+ publ_methods = mod.public_instance_methods.map { |m| m.to_sym } & methods
93
+ prot_methods = mod.protected_instance_methods.map { |m| m.to_sym } & methods
94
+ priv_methods = mod.private_instance_methods.map { |m| m.to_sym } & methods
95
+ # compile to test if the methods don't need a cref and to get a string for the hash
96
+ c = Compiler.new("test")
97
+ c.add_toplevel(c.compile_toplevel_function(node_tree))
98
+ test_code = c.to_c_code(nil) # no time_stamp
99
+ # don't allow methods that need a cref, because the compiled version would get a different cref
100
+ if test_code =~ /^static void def_only_once/ # a bit hackish ...
101
+ raise Ruby2CExtError, "the method(s) cannot be compiled, because at least one needs a cref"
102
+ end
103
+ # compile the proc
104
+ def_proc = compile_helper(test_code) { |compiler, name, gvar_name|
105
+ node_tree.last[:next].last[:vid] = gvar_name.to_sym
106
+ compiler.add_toplevel(compiler.compile_toplevel_function(node_tree))
107
+ }
108
+ # try to remove all the methods
109
+ mod.module_eval {
110
+ methods.each { |m|
111
+ remove_method(m) rescue nil
112
+ }
113
+ }
114
+ # add the compiled methods
115
+ mod.module_eval &def_proc
116
+ # restore original visibility
117
+ mod.module_eval {
118
+ public(*publ_methods) unless publ_methods.empty?
119
+ protected(*prot_methods) unless prot_methods.empty?
120
+ private(*priv_methods) unless priv_methods.empty?
121
+ }
122
+ end
123
+
124
+ end
125
+
126
+ end
@@ -0,0 +1,36 @@
1
+
2
+ require "rubynode"
3
+
4
+ module Ruby2CExtension
5
+
6
+ # not really a parser, uses rubynode
7
+ module Parser
8
+ def self.parse_string(str, file_name = "(parse)")
9
+ res = {}
10
+ # for the first parsing use original str, because it doesn't matter
11
+ # for BEGIN stuff and we get better exceptions this way.
12
+ if (tmp = str.parse_begin_to_nodes(file_name, 1))
13
+ res[:begin] = tmp
14
+ end
15
+ # now wrap str in a class scope and strip the class node of
16
+ # afterwards, to get a clean scope in the result. src should
17
+ # not have syntax errors if str didn't.
18
+ src = "class Object\n#{str}\nend"
19
+ begin
20
+ old_verb = $VERBOSE
21
+ # turn warnings of here to avoid the repetition of parse warnings
22
+ $VERBOSE = nil
23
+ if (tmp = src.parse_to_nodes(file_name, 0))
24
+ tmp = tmp.nd_next.nd_body
25
+ if tmp.type == :scope && tmp.nd_next
26
+ res[:tree] = tmp
27
+ end
28
+ end
29
+ ensure
30
+ $VERBOSE = old_verb
31
+ end
32
+ res
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,24 @@
1
+
2
+ module Ruby2CExtension
3
+
4
+ class Plugin
5
+ attr_reader :compiler
6
+
7
+ def initialize(compiler)
8
+ @compiler = compiler
9
+ end
10
+
11
+ # C code returned by this method will be inserted into the final C file
12
+ # between the helpers and the C functions
13
+ def global_c_code
14
+ nil
15
+ end
16
+
17
+ # C code returned by this method will be inserted into the Init_*()
18
+ # function before the calling of the toplevel scopes
19
+ def init_c_code
20
+ nil
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,817 @@
1
+
2
+ module Ruby2CExtension::Plugins
3
+
4
+ class BuiltinMethods < Ruby2CExtension::Plugin
5
+ # for public methods of builtin types with a fixed arity, which don't do anything with blocks
6
+
7
+ SUPPORTED_BUILTINS = [:Array, :Bignum, :FalseClass, :Fixnum, :Float, :Hash, :NilClass, :Regexp, :String, :Symbol, :TrueClass]
8
+
9
+ NO_CLASS_CHECK_BUILTINS = [:FalseClass, :Fixnum, :NilClass, :Symbol, :TrueClass]
10
+
11
+ COMMON_METHODS = [ # all supported builtins use these methods from Kernel
12
+ [:__id__, 0, :Kernel],
13
+ [:class, 0, :Kernel],
14
+ [:clone, 0, :Kernel],
15
+ [:dup, 0, :Kernel],
16
+ [:freeze, 0, :Kernel],
17
+ [:instance_variables, 0, :Kernel],
18
+ [:object_id, 0, :Kernel],
19
+ [:taint, 0, :Kernel],
20
+ [:tainted?, 0, :Kernel],
21
+ [:untaint, 0, :Kernel],
22
+ [:equal?, 1, :Kernel],
23
+ [:instance_of?, 1, :Kernel],
24
+ [:instance_variable_get, 1, :Kernel],
25
+ [:is_a?, 1, :Kernel],
26
+ [:kind_of?, 1, :Kernel],
27
+ [:method, 1, :Kernel],
28
+ [:instance_variable_set, 2, :Kernel],
29
+ ]
30
+
31
+ METHODS = {
32
+ :Array => [
33
+ [:[], 1, nil, nil, -1],
34
+ [:[], 2, nil, nil, -1],
35
+ [:first, 0, nil, nil, -1],
36
+ [:first, 1, nil, nil, -1],
37
+ [:insert, 2, nil, nil, -1],
38
+ [:insert, 3, nil, nil, -1],
39
+ [:join, 0, nil, nil, -1],
40
+ [:join, 1, nil, nil, -1],
41
+ [:last, 0, nil, nil, -1],
42
+ [:last, 1, nil, nil, -1],
43
+ [:push, 1, nil, nil, -1],
44
+ [:slice, 1, nil, nil, -1],
45
+ [:slice, 2, nil, nil, -1],
46
+ [:slice!, 1, nil, nil, -1],
47
+ [:slice!, 2, nil, nil, -1],
48
+ [:unshift, 1, nil, nil, -1],
49
+ [:values_at, 1, nil, nil, -1],
50
+ [:values_at, 2, nil, nil, -1],
51
+ [:values_at, 3, nil, nil, -1],
52
+ [:values_at, 4, nil, nil, -1],
53
+ [:clear, 0],
54
+ [:compact, 0],
55
+ [:compact!, 0],
56
+ [:empty?, 0],
57
+ [:flatten, 0],
58
+ [:flatten!, 0],
59
+ [:frozen?, 0],
60
+ [:hash, 0],
61
+ [:inspect, 0],
62
+ [:length, 0],
63
+ [:nitems, 0],
64
+ [:pop, 0],
65
+ [:reverse, 0],
66
+ [:reverse!, 0],
67
+ [:shift, 0],
68
+ [:size, 0],
69
+ [:to_a, 0],
70
+ [:to_ary, 0],
71
+ [:to_s, 0],
72
+ [:transpose, 0],
73
+ [:uniq, 0],
74
+ [:uniq!, 0],
75
+ [:&, 1],
76
+ [:|, 1],
77
+ [:*, 1],
78
+ [:+, 1],
79
+ [:-, 1],
80
+ [:<<, 1],
81
+ [:<=>, 1],
82
+ [:==, 1],
83
+ [:assoc, 1],
84
+ [:at, 1],
85
+ [:concat, 1],
86
+ [:delete_at, 1],
87
+ [:eql?, 1],
88
+ [:include?, 1],
89
+ [:index, 1],
90
+ [:rassoc, 1],
91
+ [:replace, 1],
92
+ [:rindex, 1],
93
+ [:entries, 0, :Enumerable],
94
+ [:member?, 1, :Enumerable],
95
+ [:nil?, 0, :Kernel],
96
+ [:===, 1, :Kernel],
97
+ [:=~, 1, :Kernel],
98
+ ],
99
+ :Bignum => [
100
+ [:to_s, 0, nil, nil, -1],
101
+ [:to_s, 1, nil, nil, -1],
102
+ [:-@, 0],
103
+ [:abs, 0],
104
+ [:hash, 0],
105
+ [:size, 0],
106
+ [:to_f, 0],
107
+ [:~, 0],
108
+ [:%, 1, nil, [:Fixnum, :Bignum]],
109
+ [:&, 1],
110
+ [:*, 1, nil, [:Fixnum, :Bignum, :Float]],
111
+ [:**, 1, nil, [:Fixnum, :Bignum, :Float]],
112
+ [:+, 1, nil, [:Fixnum, :Bignum, :Float]],
113
+ [:-, 1, nil, [:Fixnum, :Bignum, :Float]],
114
+ [:/, 1, nil, [:Fixnum, :Bignum, :Float]],
115
+ [:<<, 1],
116
+ [:<=>, 1, nil, [:Fixnum, :Bignum, :Float]],
117
+ [:==, 1],
118
+ [:>>, 1],
119
+ [:[], 1],
120
+ [:^, 1],
121
+ [:coerce, 1],
122
+ [:div, 1, nil, [:Fixnum, :Bignum, :Float]],
123
+ [:divmod, 1, nil, [:Fixnum, :Bignum]],
124
+ [:eql?, 1],
125
+ [:modulo, 1, nil, [:Fixnum, :Bignum]],
126
+ [:quo, 1, nil, [:Fixnum, :Bignum, :Float]],
127
+ [:remainder, 1, nil, [:Fixnum, :Bignum]],
128
+ [:|, 1],
129
+ [:<, 1, :Comparable],
130
+ [:<=, 1, :Comparable],
131
+ [:>, 1, :Comparable],
132
+ [:>=, 1, :Comparable],
133
+ [:between?, 2, :Comparable],
134
+ [:ceil, 0, :Integer],
135
+ [:chr, 0, :Integer],
136
+ [:floor, 0, :Integer],
137
+ [:integer?, 0, :Integer],
138
+ [:next, 0, :Integer],
139
+ [:round, 0, :Integer],
140
+ [:succ, 0, :Integer],
141
+ [:to_i, 0, :Integer],
142
+ [:to_int, 0, :Integer],
143
+ [:truncate, 0, :Integer],
144
+ [:+@, 0, :Numeric],
145
+ [:nonzero?, 0, :Numeric],
146
+ [:zero?, 0, :Numeric],
147
+ [:frozen?, 0, :Kernel],
148
+ [:inspect, 0, :Kernel],
149
+ [:nil?, 0, :Kernel],
150
+ [:to_a, 0, :Kernel],
151
+ [:===, 1, :Kernel],
152
+ [:=~, 1, :Kernel],
153
+ ],
154
+ :FalseClass => [
155
+ [:to_s, 0],
156
+ [:&, 1],
157
+ [:^, 1],
158
+ [:|, 1],
159
+ [:frozen?, 0, :Kernel],
160
+ [:hash, 0, :Kernel],
161
+ [:inspect, 0, :Kernel],
162
+ [:nil?, 0, :Kernel],
163
+ [:to_a, 0, :Kernel],
164
+ [:==, 1, :Kernel],
165
+ [:===, 1, :Kernel],
166
+ [:=~, 1, :Kernel],
167
+ [:eql?, 1, :Kernel],
168
+ ],
169
+ :Fixnum => [
170
+ [:to_s, 0, nil, nil, -1],
171
+ [:to_s, 1, nil, nil, -1],
172
+ [:-@, 0],
173
+ [:abs, 0],
174
+ [:id2name, 0],
175
+ [:size, 0],
176
+ [:to_sym, 0],
177
+ [:to_f, 0],
178
+ [:zero?, 0],
179
+ [:~, 0],
180
+ [:+, 1, nil, [:Fixnum, :Float]],
181
+ [:-, 1, nil, [:Fixnum, :Float]],
182
+ [:*, 1, nil, [:Fixnum, :Float]],
183
+ [:**, 1, nil, [:Fixnum, :Float]],
184
+ [:/, 1, nil, [:Fixnum]],
185
+ [:div, 1, nil, [:Fixnum]],
186
+ [:%, 1, nil, [:Fixnum]],
187
+ [:modulo, 1, nil, [:Fixnum]],
188
+ [:divmod, 1, nil, [:Fixnum]],
189
+ [:quo, 1, nil, [:Fixnum]],
190
+ [:<=>, 1, nil, [:Fixnum]],
191
+ [:>, 1, nil, [:Fixnum]],
192
+ [:>=, 1, nil, [:Fixnum]],
193
+ [:<, 1, nil, [:Fixnum]],
194
+ [:<=, 1, nil, [:Fixnum]],
195
+ [:==, 1],
196
+ [:&, 1],
197
+ [:|, 1],
198
+ [:^, 1],
199
+ [:[], 1],
200
+ [:<<, 1],
201
+ [:>>, 1],
202
+ [:between?, 2, :Comparable],
203
+ [:+@, 0, :Numeric],
204
+ [:nonzero?, 0, :Numeric],
205
+ [:coerce, 1, :Numeric],
206
+ [:eql?, 1, :Numeric],
207
+ [:remainder, 1, :Numeric],
208
+ [:ceil, 0, :Integer],
209
+ [:chr, 0, :Integer],
210
+ [:floor, 0, :Integer],
211
+ [:integer?, 0, :Integer],
212
+ [:next, 0, :Integer],
213
+ [:round, 0, :Integer],
214
+ [:succ, 0, :Integer],
215
+ [:to_i, 0, :Integer],
216
+ [:to_int, 0, :Integer],
217
+ [:truncate, 0, :Integer],
218
+ [:frozen?, 0, :Kernel],
219
+ [:hash, 0, :Kernel],
220
+ [:inspect, 0, :Kernel],
221
+ [:nil?, 0, :Kernel],
222
+ [:to_a, 0, :Kernel],
223
+ [:===, 1, :Kernel],
224
+ [:=~, 1, :Kernel],
225
+ ],
226
+ :Float => [
227
+ [:-@, 0],
228
+ [:to_s, 0],
229
+ [:hash, 0],
230
+ [:to_f, 0],
231
+ [:abs, 0],
232
+ [:zero?, 0],
233
+ [:to_i, 0],
234
+ [:to_int, 0],
235
+ [:floor, 0],
236
+ [:ceil, 0],
237
+ [:round, 0],
238
+ [:truncate, 0],
239
+ [:nan?, 0],
240
+ [:infinite?, 0],
241
+ [:finite?, 0],
242
+ [:coerce, 1],
243
+ [:eql?, 1],
244
+ [:==, 1],
245
+ [:+, 1, nil, [:Fixnum, :Bignum, :Float]],
246
+ [:-, 1, nil, [:Fixnum, :Bignum, :Float]],
247
+ [:*, 1, nil, [:Fixnum, :Bignum, :Float]],
248
+ [:/, 1, nil, [:Fixnum, :Bignum, :Float]],
249
+ [:%, 1, nil, [:Fixnum, :Bignum, :Float]],
250
+ [:modulo, 1, nil, [:Fixnum, :Bignum, :Float]],
251
+ [:divmod, 1, nil, [:Fixnum, :Bignum, :Float]],
252
+ [:**, 1, nil, [:Fixnum, :Bignum, :Float]],
253
+ [:<=>, 1, nil, [:Fixnum, :Bignum, :Float]],
254
+ [:>, 1, nil, [:Fixnum, :Bignum, :Float]],
255
+ [:>=, 1, nil, [:Fixnum, :Bignum, :Float]],
256
+ [:<, 1, nil, [:Fixnum, :Bignum, :Float]],
257
+ [:<=, 1, nil, [:Fixnum, :Bignum, :Float]],
258
+ [:between?, 2, :Comparable],
259
+ [:+@, 0, :Numeric],
260
+ [:integer?, 0, :Numeric],
261
+ [:nonzero?, 0, :Numeric],
262
+ [:div, 1, :Numeric],
263
+ [:quo, 1, :Numeric],
264
+ [:remainder, 1, :Numeric],
265
+ [:frozen?, 0, :Kernel],
266
+ [:inspect, 0, :Kernel],
267
+ [:nil?, 0, :Kernel],
268
+ [:to_a, 0, :Kernel],
269
+ [:===, 1, :Kernel],
270
+ [:=~, 1, :Kernel],
271
+ ],
272
+ :Hash => [
273
+ [:default, 0, nil, nil, -1],
274
+ [:default, 1, nil, nil, -1],
275
+ [:values_at, 1, nil, nil, -1],
276
+ [:values_at, 2, nil, nil, -1],
277
+ [:values_at, 3, nil, nil, -1],
278
+ [:values_at, 4, nil, nil, -1],
279
+ [:clear, 0],
280
+ [:default_proc, 0],
281
+ [:empty?, 0],
282
+ [:inspect, 0],
283
+ [:invert, 0],
284
+ [:keys, 0],
285
+ [:length, 0],
286
+ [:rehash, 0],
287
+ [:shift, 0],
288
+ [:size, 0],
289
+ [:to_a, 0],
290
+ [:to_hash, 0],
291
+ [:to_s, 0],
292
+ [:values, 0],
293
+ [:==, 1],
294
+ [:[], 1],
295
+ [:default=, 1],
296
+ [:delete, 1],
297
+ [:has_key?, 1],
298
+ [:has_value?, 1],
299
+ [:include?, 1],
300
+ [:index, 1],
301
+ [:key?, 1],
302
+ [:member?, 1],
303
+ [:replace, 1],
304
+ [:value?, 1],
305
+ [:store, 2],
306
+ [:entries, 0, :Enumerable],
307
+ [:frozen?, 0, :Kernel],
308
+ [:hash, 0, :Kernel],
309
+ [:nil?, 0, :Kernel],
310
+ [:===, 1, :Kernel],
311
+ [:=~, 1, :Kernel],
312
+ [:eql?, 1, :Kernel],
313
+ ],
314
+ :NilClass => [
315
+ [:inspect, 0],
316
+ [:nil?, 0],
317
+ [:to_a, 0],
318
+ [:to_f, 0],
319
+ [:to_i, 0],
320
+ [:to_s, 0],
321
+ [:&, 1],
322
+ [:^, 1],
323
+ [:|, 1],
324
+ [:frozen?, 0, :Kernel],
325
+ [:hash, 0, :Kernel],
326
+ [:==, 1, :Kernel],
327
+ [:===, 1, :Kernel],
328
+ [:=~, 1, :Kernel],
329
+ [:eql?, 1, :Kernel],
330
+ ],
331
+ :Regexp => [
332
+ [:casefold?, 0],
333
+ [:hash, 0],
334
+ [:inspect, 0],
335
+ [:kcode, 0],
336
+ [:options, 0],
337
+ [:source, 0],
338
+ [:to_s, 0],
339
+ [:~, 0],
340
+ [:==, 1],
341
+ [:===, 1],
342
+ [:=~, 1],
343
+ [:eql?, 1],
344
+ [:match, 1],
345
+ [:frozen?, 0, :Kernel],
346
+ [:nil?, 0, :Kernel],
347
+ [:to_a, 0, :Kernel],
348
+ ],
349
+ :String => [
350
+ [:[], 1, nil, nil, -1],
351
+ [:[], 2, nil, nil, -1],
352
+ [:center, 1, nil, nil, -1],
353
+ [:center, 2, nil, nil, -1],
354
+ [:chomp, 1, nil, nil, -1],
355
+ [:chomp, 2, nil, nil, -1],
356
+ [:chomp!, 1, nil, nil, -1],
357
+ [:chomp!, 2, nil, nil, -1],
358
+ [:count, 1, nil, nil, -1],
359
+ [:count, 2, nil, nil, -1],
360
+ [:count, 3, nil, nil, -1],
361
+ [:delete, 1, nil, nil, -1],
362
+ [:delete, 2, nil, nil, -1],
363
+ [:delete, 3, nil, nil, -1],
364
+ [:delete!, 1, nil, nil, -1],
365
+ [:delete!, 2, nil, nil, -1],
366
+ [:delete!, 3, nil, nil, -1],
367
+ [:index, 1, nil, nil, -1],
368
+ [:index, 2, nil, nil, -1],
369
+ [:ljust, 1, nil, nil, -1],
370
+ [:ljust, 2, nil, nil, -1],
371
+ [:rindex, 1, nil, nil, -1],
372
+ [:rindex, 2, nil, nil, -1],
373
+ [:rjust, 1, nil, nil, -1],
374
+ [:rjust, 2, nil, nil, -1],
375
+ [:slice, 1, nil, nil, -1],
376
+ [:slice, 2, nil, nil, -1],
377
+ [:slice!, 1, nil, nil, -1],
378
+ [:slice!, 2, nil, nil, -1],
379
+ [:split, 0, nil, nil, -1],
380
+ [:split, 1, nil, nil, -1],
381
+ [:split, 2, nil, nil, -1],
382
+ [:squeeze, 1, nil, nil, -1],
383
+ [:squeeze, 2, nil, nil, -1],
384
+ [:squeeze, 3, nil, nil, -1],
385
+ [:squeeze!, 1, nil, nil, -1],
386
+ [:squeeze!, 2, nil, nil, -1],
387
+ [:squeeze!, 3, nil, nil, -1],
388
+ [:to_i, 0, nil, nil, -1],
389
+ [:to_i, 1, nil, nil, -1],
390
+ [:capitalize, 0],
391
+ [:capitalize!, 0],
392
+ [:chop, 0],
393
+ [:chop!, 0],
394
+ [:downcase, 0],
395
+ [:downcase!, 0],
396
+ [:dump, 0],
397
+ [:empty?, 0],
398
+ [:hash, 0],
399
+ [:hex, 0],
400
+ [:inspect, 0],
401
+ [:intern, 0],
402
+ [:length, 0],
403
+ [:lstrip, 0],
404
+ [:lstrip!, 0],
405
+ [:next, 0],
406
+ [:next!, 0],
407
+ [:oct, 0],
408
+ [:reverse, 0],
409
+ [:reverse!, 0],
410
+ [:rstrip, 0],
411
+ [:rstrip!, 0],
412
+ [:size, 0],
413
+ [:strip, 0],
414
+ [:strip!, 0],
415
+ [:succ, 0],
416
+ [:succ!, 0],
417
+ [:swapcase, 0],
418
+ [:swapcase!, 0],
419
+ [:to_f, 0],
420
+ [:to_s, 0],
421
+ [:to_str, 0],
422
+ [:to_sym, 0],
423
+ [:upcase, 0],
424
+ [:upcase!, 0],
425
+ [:%, 1],
426
+ [:*, 1],
427
+ [:+, 1],
428
+ [:<<, 1],
429
+ [:<=>, 1],
430
+ [:==, 1],
431
+ [:=~, 1],
432
+ [:casecmp, 1],
433
+ [:concat, 1],
434
+ [:crypt, 1],
435
+ [:eql?, 1],
436
+ [:include?, 1],
437
+ [:match, 1],
438
+ [:replace, 1],
439
+ [:insert, 2],
440
+ [:tr, 2],
441
+ [:tr!, 2],
442
+ [:tr_s, 2],
443
+ [:tr_s!, 2],
444
+ [:<, 1, :Comparable],
445
+ [:<=, 1, :Comparable],
446
+ [:>, 1, :Comparable],
447
+ [:>=, 1, :Comparable],
448
+ [:between?, 2, :Comparable],
449
+ [:entries, 0, :Enumerable],
450
+ [:to_a, 0, :Enumerable],
451
+ [:member?, 1, :Enumerable],
452
+ [:frozen?, 0, :Kernel],
453
+ [:nil?, 0, :Kernel],
454
+ [:===, 1, :Kernel],
455
+ ],
456
+ :Symbol => [
457
+ [:id2name, 0],
458
+ [:inspect, 0],
459
+ [:to_i, 0],
460
+ [:to_int, 0],
461
+ [:to_s, 0],
462
+ [:to_sym, 0],
463
+ [:===, 1],
464
+ [:frozen?, 0, :Kernel],
465
+ [:hash, 0, :Kernel],
466
+ [:nil?, 0, :Kernel],
467
+ [:to_a, 0, :Kernel],
468
+ [:==, 1, :Kernel],
469
+ [:=~, 1, :Kernel],
470
+ [:eql?, 1, :Kernel],
471
+ ],
472
+ :TrueClass => [
473
+ [:to_s, 0],
474
+ [:&, 1],
475
+ [:^, 1],
476
+ [:|, 1],
477
+ [:frozen?, 0, :Kernel],
478
+ [:hash, 0, :Kernel],
479
+ [:inspect, 0, :Kernel],
480
+ [:nil?, 0, :Kernel],
481
+ [:to_a, 0, :Kernel],
482
+ [:==, 1, :Kernel],
483
+ [:===, 1, :Kernel],
484
+ [:=~, 1, :Kernel],
485
+ [:eql?, 1, :Kernel],
486
+ ],
487
+ }
488
+
489
+ METHOD_NAME_MAPPINGS = Hash.new { |h, k|
490
+ case k.to_s
491
+ when /\A\w+\z/
492
+ h[k] = "builtinoptmeth_#{k}"
493
+ when /\A\w+\?\z/
494
+ h[k] = "builtinoptmeth_#{k.to_s[0..-2]}__pred"
495
+ when /\A\w+!\z/
496
+ h[k] = "builtinoptmeth_#{k.to_s[0..-2]}__bang"
497
+ when /\A\w+=\z/
498
+ h[k] = "builtinoptmeth_#{k.to_s[0..-2]}__assign"
499
+ else
500
+ raise Ruby2CExtension::Ruby2CExtError::Bug, "unexpected method name: #{k.inspect}"
501
+ end
502
+ }
503
+ METHOD_NAME_MAPPINGS.merge!({
504
+ :+@ => "builtinoptop_uplus",
505
+ :-@ => "builtinoptop_uminus",
506
+ :+ => "builtinoptop_plus",
507
+ :- => "builtinoptop_minus",
508
+ :* => "builtinoptop_mul",
509
+ :/ => "builtinoptop_div",
510
+ :** => "builtinoptop_pow",
511
+ :% => "builtinoptop_mod",
512
+ :~ => "builtinoptop_rev",
513
+ :== => "builtinoptop_equal",
514
+ :=== => "builtinoptop_eqq",
515
+ :=~ => "builtinoptop_match",
516
+ :<=> => "builtinoptop_cmp",
517
+ :> => "builtinoptop_gt",
518
+ :>= => "builtinoptop_ge",
519
+ :< => "builtinoptop_lt",
520
+ :<= => "builtinoptop_le",
521
+ :& => "builtinoptop_and",
522
+ :| => "builtinoptop_or",
523
+ :^ => "builtinoptop_xor",
524
+ :[] => "builtinoptop_aref",
525
+ :<< => "builtinoptop_lshift",
526
+ :>> => "builtinoptop_rshift",
527
+ })
528
+
529
+ BUILTIN_TYPE_MAP = Hash.new { |h, k|
530
+ h[k] = "T_#{k}".upcase
531
+ }
532
+ BUILTIN_TYPE_MAP.merge!({
533
+ :NilClass => "T_NIL",
534
+ :TrueClass => "T_TRUE",
535
+ :FalseClass => "T_FALSE",
536
+ })
537
+
538
+ BUILTIN_C_VAR_MAP = Hash.new { |h, k|
539
+ h[k] = "rb_#{Module.const_get(k).instance_of?(Module) ? "m" : "c"}#{k}"
540
+ }
541
+
542
+ attr_reader :methods
543
+
544
+ def initialize(compiler, builtins)
545
+ super(compiler)
546
+ builtins = SUPPORTED_BUILTINS & builtins # "sort" and unique
547
+ @methods = {} # [meth_sym, arity] => # [[type, impl. class/mod, types of first arg or nil, real arity], ...]
548
+ @function_names = {} # [meth_sym, arity] => name # initialized on first use
549
+ @typed_function_infos = {} # [meth_sym, arity, recv_type] => [fun_ptr, real_arity, first_arg_types] # initialized on first use
550
+ @fallback_functions = {} # [meth_sym, real_arity] => name
551
+ @functions_code = [] # source code of the replacement functions
552
+ @fallback_functions_code = [] # source code of the fallback functions
553
+ @method_tbl_size = 0
554
+ @init_code = []
555
+ builtins.each { |builtin|
556
+ (METHODS[builtin] + COMMON_METHODS).each { |arr|
557
+ (@methods[arr[0, 2]] ||= []) << [builtin, arr[2] || builtin, arr[3], arr[4] || arr[1]]
558
+ }
559
+ }
560
+ compiler.add_preprocessor(:call) { |cfun, node|
561
+ handle_call(cfun, node.last, node)
562
+ }
563
+ end
564
+
565
+ def deduce_type(node)
566
+ if Array === node
567
+ case node.first
568
+ when :lit
569
+ node.last[:lit].class.name.to_sym
570
+ when :nil
571
+ :NilClass
572
+ when :false
573
+ :FalseClass
574
+ when :true
575
+ :TrueClass
576
+ when :str, :dstr
577
+ :String
578
+ when :dsym
579
+ :Symbol
580
+ when :array, :zarray
581
+ :Array
582
+ when :hash
583
+ :Hash
584
+ when :dregx, :dregx_once
585
+ :Regexp
586
+ else
587
+ nil
588
+ end
589
+ else
590
+ nil
591
+ end
592
+ end
593
+
594
+ def do_init_lookup(method, method_info, fallback_fun = nil)
595
+ tbl_idx = @method_tbl_size
596
+ @method_tbl_size += 1
597
+ var = "builtinopt_method_tbl[#{tbl_idx}]"
598
+ lookup_args = [BUILTIN_C_VAR_MAP[method_info[0]], BUILTIN_C_VAR_MAP[method_info[1]], compiler.sym(method), method_info[3]]
599
+ @init_code << "#{var} = builtinopt_method_lookup(#{lookup_args.join(", ")});"
600
+ if fallback_fun
601
+ @init_code << "if (!(#{var})) #{var} = #{fallback_fun};"
602
+ end
603
+ var
604
+ end
605
+
606
+ def get_function(method, arity)
607
+ ma = [method, arity]
608
+ if (name = @function_names[ma])
609
+ name
610
+ elsif (meth_list = methods[ma])
611
+ name = @function_names[ma] = "#{METHOD_NAME_MAPPINGS[method]}__#{arity}"
612
+ code = []
613
+ code << "static VALUE #{name}(VALUE recv#{arity > 0 ? ", VALUE *argv" : ""}) {"
614
+ code << "switch(TYPE(recv)) {"
615
+ meth_list.each { |m|
616
+ fun_ptr = do_init_lookup(method, m)
617
+ code << "case #{BUILTIN_TYPE_MAP[m[0]]}:"
618
+ check = fun_ptr.dup
619
+ unless NO_CLASS_CHECK_BUILTINS.include?(m[0])
620
+ check << " && RBASIC(recv)->klass == #{BUILTIN_C_VAR_MAP[m[0]]}"
621
+ end
622
+ call =
623
+ if m[3] == -1
624
+ "(*(#{fun_ptr}))(#{arity}, #{arity > 0 ? "argv" : "NULL"}, recv)"
625
+ else
626
+ args = (0...arity).map { |j| "argv[#{j}]" }.join(", ")
627
+ args = ", " + args unless args.empty?
628
+ "(*(#{fun_ptr}))(recv#{args})"
629
+ end
630
+ if (first_arg_types = m[2])
631
+ if arity != 1
632
+ raise Ruby2CExtension::Ruby2CExtError::Bug, "arity must be 1 for arg type check"
633
+ end
634
+ code << "switch(TYPE(argv[0])) {"
635
+ first_arg_types.each { |o| code << "case #{BUILTIN_TYPE_MAP[o]}:" }
636
+ code << "if (#{check}) return #{call};"
637
+ code << "}"
638
+ else
639
+ code << "if (#{check}) return #{call};"
640
+ end
641
+ code << "break;"
642
+ }
643
+ code << "}"
644
+ code << "return rb_funcall3(recv, #{compiler.sym(method)}, #{arity}, #{arity > 0 ? "argv" : "NULL"});"
645
+ code << "}"
646
+ @functions_code << code.join("\n")
647
+ name
648
+ else
649
+ nil
650
+ end
651
+ end
652
+
653
+ def generate_fallback_function(method, real_arity)
654
+ ma = [method, real_arity]
655
+ if (name = @fallback_functions[ma])
656
+ name
657
+ else
658
+ name = @fallback_functions[ma] = "#{METHOD_NAME_MAPPINGS[method]}__#{real_arity >= 0 ? real_arity : "V"}__fallback"
659
+ code = []
660
+ if real_arity == -1
661
+ code << "static VALUE #{name}(int argc, VALUE *argv, VALUE recv) {"
662
+ code << "return rb_funcall3(recv, #{compiler.sym(method)}, argc, argv);"
663
+ code << "}"
664
+ else
665
+ args = (["VALUE recv"] + (0...real_arity).map { |j| "VALUE arg_#{j}" }).join(", ")
666
+ code << "static VALUE #{name}(#{args}) {"
667
+ if real_arity > 1
668
+ code << "VALUE argv[#{real_arity}];"
669
+ (0...real_arity).each { |j|
670
+ code << "argv[#{j}] = arg_#{j};"
671
+ }
672
+ end
673
+ argv =
674
+ case real_arity
675
+ when 0: "NULL"
676
+ when 1: "&arg_0"
677
+ else "argv"
678
+ end
679
+ code << "return rb_funcall3(recv, #{compiler.sym(method)}, #{real_arity}, #{argv});"
680
+ code << "}"
681
+ end
682
+ @fallback_functions_code << code.join("\n")
683
+ name
684
+ end
685
+ end
686
+
687
+ def get_typed_function_info(method, arity, recv_type)
688
+ mat = [method, arity, recv_type]
689
+ if (infos = @typed_function_infos[mat])
690
+ infos
691
+ elsif (meth_list = methods[[method, arity]])
692
+ if (m = meth_list.find { |arr| arr.first == recv_type })
693
+ @typed_function_infos[mat] = [
694
+ do_init_lookup(method, m, generate_fallback_function(method, m[3])),
695
+ m[3], m[2]
696
+ ]
697
+ else
698
+ nil # this call is not optimizable
699
+ end
700
+ else
701
+ nil
702
+ end
703
+ end
704
+
705
+ def handle_call(cfun, hash, node)
706
+ args = []
707
+ if hash[:args]
708
+ if hash[:args].first == :array
709
+ args = hash[:args].last
710
+ else
711
+ return node
712
+ end
713
+ end
714
+ if (recv_type = deduce_type(hash[:recv]))
715
+ fun_ptr, real_arity, first_arg_types = *get_typed_function_info(hash[:mid], args.size, recv_type)
716
+ if fun_ptr
717
+ cfun.instance_eval {
718
+ recv = comp(hash[:recv])
719
+ if args.empty?
720
+ "(*(#{fun_ptr}))(#{real_arity == -1 ? "0, NULL, " : ""}#{recv})"
721
+ else
722
+ arity = args.size
723
+ c_scope_res {
724
+ l "VALUE recv = #{recv};"
725
+ build_c_arr(args, "argv")
726
+ call_args =
727
+ if real_arity == -1
728
+ "#{arity}, #{arity > 0 ? "argv" : "NULL"}, recv"
729
+ else
730
+ "recv, " + (0...arity).map { |j| "argv[#{j}]" }.join(", ")
731
+ end
732
+ if first_arg_types
733
+ l "switch(TYPE(argv[0])) {"
734
+ first_arg_types.each { |o|
735
+ l "case #{BUILTIN_TYPE_MAP[o]}:"
736
+ }
737
+ assign_res("(*(#{fun_ptr}))(#{call_args})")
738
+ l "break;"
739
+ l "default:"
740
+ assign_res("rb_funcall3(recv, #{compiler.sym(hash[:mid])}, 1, argv)")
741
+ l "}"
742
+ "res"
743
+ else
744
+ "(*(#{fun_ptr}))(#{call_args})"
745
+ end
746
+ }
747
+ end
748
+ }
749
+ else
750
+ node
751
+ end
752
+ elsif (fun = get_function(hash[:mid], args.size))
753
+ cfun.instance_eval {
754
+ recv = comp(hash[:recv])
755
+ if args.empty?
756
+ "#{fun}(#{recv})"
757
+ else
758
+ c_scope_res {
759
+ l "VALUE recv = #{recv};"
760
+ build_c_arr(args, "argv")
761
+ "#{fun}(recv, argv)"
762
+ }
763
+ end
764
+ }
765
+ else
766
+ node
767
+ end
768
+ end
769
+
770
+ METHOD_LOOKUP_CODE = %{
771
+ static BUILTINOPT_FP builtinopt_method_lookup(VALUE klass, VALUE origin, ID mid, long arity) {
772
+ NODE *body;
773
+ while (klass != origin) {
774
+ if (TYPE(klass) == T_ICLASS && RBASIC(klass)->klass == origin) break;
775
+ if (st_lookup(RCLASS(klass)->m_tbl, mid, (st_data_t *)&body)) return NULL;
776
+ klass = RCLASS(klass)->super;
777
+ if (!klass) return NULL;
778
+ }
779
+ if (st_lookup(RCLASS(klass)->m_tbl, mid, (st_data_t *)&body)) {
780
+ body = body->nd_body;
781
+ if (nd_type(body) == NODE_FBODY) body = body->nd_head;
782
+ if (nd_type(body) == NODE_CFUNC && body->nd_argc == arity) {
783
+ return body->nd_cfnc;
784
+ }
785
+ }
786
+ return NULL;
787
+ }
788
+ }
789
+
790
+ def global_c_code
791
+ unless @init_code.empty?
792
+ res = []
793
+ res << "typedef VALUE (*BUILTINOPT_FP)(ANYARGS);"
794
+ res << "static BUILTINOPT_FP builtinopt_method_tbl[#{@method_tbl_size}];"
795
+ res << METHOD_LOOKUP_CODE
796
+
797
+ res.concat(@fallback_functions_code)
798
+
799
+ res << "static void init_builtinopt() {"
800
+ res.concat(@init_code)
801
+ res << "}"
802
+
803
+ res.concat(@functions_code)
804
+
805
+ res.join("\n")
806
+ end
807
+ end
808
+
809
+ def init_c_code
810
+ unless @init_code.empty?
811
+ "init_builtinopt();"
812
+ end
813
+ end
814
+
815
+ end
816
+
817
+ end