crystalizer 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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