ruby2cext 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+
2
+ require "ruby2cext/plugin"
3
+ require "ruby2cext/tools"
4
+
5
+ module Ruby2CExtension::Plugins
6
+
7
+ class CaseOptimize < Ruby2CExtension::Plugin
8
+
9
+ include Ruby2CExtension::Tools::EnsureNodeTypeMixin
10
+
11
+ def initialize(compiler)
12
+ super
13
+ compiler.add_preprocessor(:case) { |cfun, node|
14
+ handle_case(cfun, node.last) || node
15
+ }
16
+ end
17
+
18
+ def fixnum?(node)
19
+ Array === node && node.first == :lit && Fixnum === node.last[:lit]
20
+ end
21
+
22
+ def fixed_immediate?(node)
23
+ # checks if the node is optimizable and if yes returns the equivalent C value
24
+ if fixnum?(node)
25
+ "LONG2FIX(#{node.last[:lit].inspect})"
26
+ elsif Array === node
27
+ case node.first
28
+ when :nil
29
+ "Qnil"
30
+ when :true
31
+ "Qtrue"
32
+ when :false
33
+ "Qfalse"
34
+ else
35
+ false
36
+ end
37
+ else
38
+ false
39
+ end
40
+ end
41
+
42
+ def handle_case(cfun, hash)
43
+ cur_when = hash[:body]
44
+ fallback_whens = []
45
+ opt_cases = []
46
+ while cur_when.first == :when
47
+ ensure_node_type(head = cur_when.last[:head], :array)
48
+ cases = head.last.map { |wn| fixed_immediate?(wn) }
49
+ break unless cases.all?
50
+ case_c_code = cases.map { |c| "case #{c}:" }.join("\n")
51
+ fixnum_whens = head.last.select { |wn| fixnum?(wn) }
52
+ unless fixnum_whens.empty?
53
+ goto_label = compiler.un("case_opt_label")
54
+ case_c_code << "\n#{goto_label}:"
55
+ fallback_whens << [:when, {
56
+ :body => "Qnil;\ngoto #{goto_label}", # TODO: evil, depends on impl. details of comp_case/handle_when
57
+ :head => [:array, fixnum_whens]
58
+ }]
59
+ end
60
+ opt_cases << [case_c_code, cur_when.last[:body]]
61
+ cur_when = cur_when.last[:next] || [:nil, {}]
62
+ end
63
+ return nil if opt_cases.empty? # nothing to optimize
64
+ rest = cur_when
65
+ if rest.first == :when # some whens are left construct a complete new case node
66
+ rest = [:case, {:head => "case_opt_val", :body => rest}]
67
+ end
68
+ cfun.instance_eval {
69
+ c_scope_res {
70
+ l "VALUE case_opt_val;"
71
+ l "case_opt_val = #{comp(hash[:head])};"
72
+ l "switch (case_opt_val) {"
73
+ opt_cases.each { |(case_code, body_node)|
74
+ l case_code
75
+ assign_res(comp(body_node))
76
+ l "break;"
77
+ }
78
+ l "default:"
79
+ if fallback_whens.empty?
80
+ assign_res(comp(rest))
81
+ else
82
+ # link the fallback_whens
83
+ fallback_whens.each_with_index { |wn, i|
84
+ fallback_whens[i - 1].last[:next] = wn if i > 0
85
+ }
86
+ fallback_whens.last.last[:next] = "Qundef"
87
+ c_if("!FIXNUM_P(case_opt_val)") {
88
+ assign_res(comp_case({:head => "case_opt_val", :body => fallback_whens.first}))
89
+ }
90
+ c_else {
91
+ assign_res("Qundef")
92
+ }
93
+ c_if("res == Qundef") {
94
+ assign_res(comp(rest))
95
+ }
96
+ end
97
+ l "}"
98
+ "res"
99
+ }
100
+ }
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,38 @@
1
+
2
+ require "ruby2cext/plugin"
3
+
4
+ module Ruby2CExtension::Plugins
5
+
6
+ class ConstCache < Ruby2CExtension::Plugin
7
+ def initialize(compiler)
8
+ super
9
+ compiler.add_preprocessor(:const) { |cfun, node|
10
+ cfun.instance_eval {
11
+ c_static_once {
12
+ comp_const(node.last)
13
+ }
14
+ }
15
+ }
16
+ compiler.add_preprocessor(:colon2) { |cfun, node|
17
+ mid = node.last[:mid]
18
+ if mid.to_s[0,1].downcase != mid.to_s[0,1] # then it is a constant
19
+ cfun.instance_eval {
20
+ c_static_once {
21
+ comp_colon2(node.last)
22
+ }
23
+ }
24
+ else
25
+ node
26
+ end
27
+ }
28
+ compiler.add_preprocessor(:colon3) { |cfun, node|
29
+ cfun.instance_eval {
30
+ c_static_once {
31
+ comp_colon3(node.last)
32
+ }
33
+ }
34
+ }
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,69 @@
1
+
2
+ require "ruby2cext/plugin"
3
+
4
+ module Ruby2CExtension::Plugins
5
+
6
+ class InlineMethods < Ruby2CExtension::Plugin
7
+
8
+ METHODS = {
9
+ [:nil?, 0] => proc { |cfun, recv, args|
10
+ "(Qnil == (#{recv}) ? Qtrue : Qfalse)"
11
+ },
12
+ [:equal?, 1] => proc { |cfun, recv, args|
13
+ cfun.instance_eval {
14
+ c_scope_res {
15
+ l "VALUE recv = #{recv};"
16
+ "(recv == (#{comp(args[0])}) ? Qtrue : Qfalse)"
17
+ }
18
+ }
19
+ },
20
+ :__send__ => proc { |cfun, recv, args|
21
+ unless args
22
+ raise Ruby2CExtension::Ruby2CExtError, "inlining #__send__ without arguments is not allowed"
23
+ end
24
+ cfun.instance_eval {
25
+ add_helper <<-EOC
26
+ static void inline_method_send_no_method_name() {
27
+ rb_raise(rb_eArgError, "no method name given");
28
+ }
29
+ EOC
30
+ c_scope_res {
31
+ l "VALUE recv = #{recv};"
32
+ build_args(args)
33
+ l "if (argc == 0) inline_method_send_no_method_name();"
34
+ "rb_funcall2(recv, rb_to_id(argv[0]), argc - 1, (&(argv[0])) + 1)"
35
+ }
36
+ }
37
+ },
38
+ }
39
+
40
+ def initialize(compiler)
41
+ super
42
+ self_node = [:self, {}]
43
+ compiler.add_preprocessor(:vcall) { |cfun, node|
44
+ handle(cfun, node.last[:mid], self_node, false) || node
45
+ }
46
+ compiler.add_preprocessor(:fcall) { |cfun, node|
47
+ handle(cfun, node.last[:mid], self_node, node.last[:args]) || node
48
+ }
49
+ compiler.add_preprocessor(:call) { |cfun, node|
50
+ handle(cfun, node.last[:mid], node.last[:recv], node.last[:args]) || node
51
+ }
52
+ end
53
+
54
+ def handle(cfun, method, recv, args)
55
+ if (pr = METHODS[method])
56
+ pr.call(cfun, cfun.comp(recv), args)
57
+ else
58
+ args ||= [:array, []]
59
+ if args.first == :array && (pr = METHODS[[method, args.last.size]])
60
+ pr.call(cfun, cfun.comp(recv), args.last)
61
+ else
62
+ nil
63
+ end
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,71 @@
1
+
2
+ require "ruby2cext/plugin"
3
+
4
+ module Ruby2CExtension::Plugins
5
+
6
+ class RequireInclude < Ruby2CExtension::Plugin
7
+
8
+ attr_reader :include_paths
9
+
10
+ def initialize(compiler, include_paths, ignore_files = nil)
11
+ super(compiler)
12
+ @include_paths = include_paths
13
+ done = {}
14
+ if ignore_files
15
+ ignore_files.each { |file|
16
+ done[File.expand_path(file)] = true
17
+ }
18
+ end
19
+ compiler.add_preprocessor(:fcall) { |cfun, node|
20
+ hash = node.last
21
+ if hash[:mid] == :require &&
22
+ (args = hash[:args]) &&
23
+ args.first == :array &&
24
+ (args = args.last).size == 1 &&
25
+ Array === args[0] &&
26
+ args[0].first == :str &&
27
+ (file = search_file(args[0].last[:lit]))
28
+ unless done[File.expand_path(file)]
29
+ done[File.expand_path(file)] = true
30
+ cfun.compiler.log "including require'd file: #{file}"
31
+ cfun.instance_eval {
32
+ add_helper <<-EOC
33
+ static NODE * find_top_cref(NODE *cref) {
34
+ while (cref && cref->nd_next) cref = cref->nd_next;
35
+ return cref;
36
+ }
37
+ EOC
38
+ c_scope {
39
+ l "NODE *top_cref = find_top_cref(#{get_cref});"
40
+ l "static int done = 0;"
41
+ c_if("!done") {
42
+ l "done = 1;"
43
+ compiler.rb_file_to_toplevel_functions(IO.read(file), file).each { |tlfn|
44
+ l "#{tlfn}(org_ruby_top_self, top_cref);"
45
+ }
46
+ }
47
+ }
48
+ }
49
+ end
50
+ "Qtrue"
51
+ else
52
+ node
53
+ end
54
+ }
55
+ end
56
+
57
+ def search_file(req_str)
58
+ req_str = req_str.dup
59
+ unless req_str =~ /\.rb\z/
60
+ req_str << ".rb"
61
+ end
62
+ res = false
63
+ include_paths.map { |path|
64
+ File.join(path, req_str)
65
+ }.find { |file|
66
+ File.exists? file
67
+ }
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,123 @@
1
+
2
+ require "ruby2cext/plugin"
3
+
4
+ module Ruby2CExtension::Plugins
5
+
6
+ # TODO: Module.nesting, Module.constants, Kernel#autoload and Kernel#autoload?
7
+
8
+ class Warnings < Ruby2CExtension::Plugin
9
+
10
+ CALL_TYPES = [:vcall, :fcall, :call]
11
+
12
+ exp = "might not behave as expected"
13
+ exp2 = "will not behave as expected"
14
+ vis_exp = "visibility of the defined %s might not be as expected"
15
+
16
+ VCALL_WARNINGS = {
17
+ :binding => exp2,
18
+ :local_variables => exp2,
19
+ :callcc => exp,
20
+ }
21
+
22
+ FCALL_WARNINGS = {
23
+ :set_trace_func => exp,
24
+ :eval => exp,
25
+ :define_method => vis_exp % "method",
26
+ :attr => vis_exp % "attribute",
27
+ :attr_reader => vis_exp % "attribute(s)",
28
+ :attr_writer => vis_exp % "attribute(s)",
29
+ :attr_accessor => vis_exp % "attribute(s)",
30
+ :instance_eval => exp,
31
+ :module_eval => exp,
32
+ :class_eval => exp,
33
+ }
34
+
35
+ CALL_WARNINGS = {
36
+ :arity => "will return -1 for all methods defined in compiled Ruby code",
37
+ :instance_eval => exp,
38
+ :module_eval => exp,
39
+ :class_eval => exp,
40
+ }
41
+
42
+ BLOCK_PASS_WARNINGS = {
43
+ :define_method => true,
44
+ :instance_eval => true,
45
+ :module_eval => true,
46
+ :class_eval => true,
47
+ }
48
+
49
+ NO_WARNING_WITH_ITER = {
50
+ :instance_eval => true,
51
+ :module_eval => true,
52
+ :class_eval => true,
53
+ }
54
+
55
+ def initialize(compiler)
56
+ super
57
+ CALL_TYPES.each { |ct|
58
+ ct_sym = "check_#{ct}".to_sym
59
+ compiler.add_preprocessor(ct) { |cfun, node|
60
+ send(ct_sym, node.last)
61
+ node
62
+ }
63
+ }
64
+ [:iter, :block_pass].each { |it|
65
+ it_sym = "check_#{it}".to_sym
66
+ compiler.add_preprocessor(it) { |cfun, node|
67
+ if node.last[:iter] && CALL_TYPES.include?(node.last[:iter].first)
68
+ send(it_sym, node.last[:iter])
69
+ end
70
+ node
71
+ }
72
+ }
73
+ end
74
+
75
+ def warn(str, node_hash = {})
76
+ lstr = ""
77
+ if (n = node_hash[:node])
78
+ lstr << "#{n.file}:#{n.line}: "
79
+ end
80
+ lstr << "warning: " << str
81
+ compiler.log(lstr, true)
82
+ end
83
+
84
+ def check_vcall(hash)
85
+ if (m = VCALL_WARNINGS[hash[:mid]])
86
+ warn("#{hash[:mid]}: #{m}", hash)
87
+ end
88
+ end
89
+
90
+ def check_fcall(hash)
91
+ if (m = FCALL_WARNINGS[hash[:mid]])
92
+ warn("#{hash[:mid]}: #{m}", hash)
93
+ else
94
+ unless hash[:args]
95
+ check_vcall(hash)
96
+ end
97
+ end
98
+ end
99
+
100
+ def check_call(hash)
101
+ if (m = CALL_WARNINGS[hash[:mid]])
102
+ warn("#{hash[:mid]}: #{m}", hash)
103
+ end
104
+ end
105
+
106
+ def check_iter(iter_node)
107
+ mid = iter_node.last[:mid]
108
+ unless NO_WARNING_WITH_ITER[mid]
109
+ send("check_#{iter_node.first}", iter_node.last)
110
+ end
111
+ end
112
+
113
+ def check_block_pass(iter_node)
114
+ mid = iter_node.last[:mid]
115
+ if BLOCK_PASS_WARNINGS[mid]
116
+ warn("#{mid} with block_pass: might not behave as expected", iter_node.last)
117
+ else
118
+ send("check_#{iter_node.first}", iter_node.last)
119
+ end
120
+ end
121
+ end
122
+
123
+ end
@@ -0,0 +1,227 @@
1
+
2
+ require "ruby2cext/error"
3
+
4
+ module Ruby2CExtension
5
+
6
+ module Scopes
7
+ class Scope
8
+ VMODES = [:public, :private, :protected, :module_function].freeze
9
+
10
+ def initialize(tbl, vmode_methods = VMODES, private_vmode = false)
11
+ @vmode_methods = vmode_methods
12
+ @vmode = private_vmode ? :private : :public
13
+ @tbl = tbl || []
14
+ @closure_tbl = nil # must be set by user
15
+ end
16
+ attr_reader :tbl, :vmode, :vmode_methods
17
+ attr_accessor :need_heap, :closure_tbl
18
+
19
+ def get_lvar_idx(i)
20
+ raise Ruby2CExtError, "wrong lvar index: #{i}" unless i >= 0 && i < tbl.size
21
+ "lvar[#{i}]"
22
+ end
23
+ def get_lvar(sym)
24
+ raise Ruby2CExtError, "unknown lvar: #{sym}" unless (i = tbl.index(sym))
25
+ get_lvar_idx(i)
26
+ end
27
+ def get_lvar_ary
28
+ self.need_heap = true
29
+ "lvar_ary"
30
+ end
31
+ def get_dvar(arg)
32
+ raise Ruby2CExtError, "dvars not available here"
33
+ end
34
+ alias get_dvar_curr get_dvar
35
+ alias get_dvar_ary get_dvar
36
+
37
+ def vmode_method?(method_id)
38
+ if (res = vmode_methods.include?(method_id))
39
+ @vmode = method_id
40
+ end
41
+ res
42
+ end
43
+ def vmode_def_fun
44
+ case vmode
45
+ when :protected, :private
46
+ "rb_define_#{vmode}_method"
47
+ when :public
48
+ "rb_define_method"
49
+ when :module_function
50
+ "rb_define_module_function"
51
+ else
52
+ raise Ruby2CExtError::Bug, "unknown vmode: #{@vmode}"
53
+ end
54
+ end
55
+
56
+ def new_dyna_scope
57
+ DynaScope.new(self, nil, 1)
58
+ end
59
+
60
+ def var_ptr_for_wrap
61
+ tbl.empty? ? nil : "lvar"
62
+ end
63
+
64
+ def init_c_code
65
+ return nil if tbl.empty?
66
+ res = []
67
+ if need_heap
68
+ res << "VALUE *lvar;"
69
+ res << "VALUE lvar_ary = rb_ary_new2(#{tbl.size});"
70
+ res << "rb_mem_clear(RARRAY(lvar_ary)->ptr, #{tbl.size});"
71
+ res << "RARRAY(lvar_ary)->len = #{tbl.size};"
72
+ res << "lvar = RARRAY(lvar_ary)->ptr;"
73
+ else
74
+ res << "VALUE lvar[#{tbl.size}];\nrb_mem_clear(lvar, #{tbl.size});"
75
+ end
76
+ res.join("\n")
77
+ end
78
+ end
79
+
80
+ class DynaScope
81
+ def initialize(base_scope, outer_scope, depth)
82
+ @base_scope = base_scope
83
+ @outer_scope = outer_scope
84
+ @depth = depth
85
+ @closure_tbl = nil # must be set by user
86
+ @tbl = []
87
+ end
88
+ attr_reader :tbl, :base_scope, :outer_scope, :depth
89
+ attr_accessor :need_heap, :need_closure, :closure_tbl
90
+
91
+ def outer_closure_tbl
92
+ (outer_scope || base_scope).closure_tbl
93
+ end
94
+ def add_closure_need(need)
95
+ # need is in [1, self.depth) or :lvar
96
+ unless need == :lvar || (1...depth) === need
97
+ raise Ruby2CExtError::Bug, "unexpected needed closure vars: #{need}"
98
+ end
99
+ self.need_closure = true
100
+ outer_closure_tbl << need unless outer_closure_tbl.include? need
101
+ end
102
+ def get_closure_ary(clos_idx)
103
+ add_closure_need(clos_idx)
104
+ "closure[#{outer_closure_tbl.index(clos_idx)}]"
105
+ end
106
+ def get_closure_var(clos_idx, var_idx)
107
+ "RARRAY(#{get_closure_ary(clos_idx)})->ptr[#{var_idx}]"
108
+ end
109
+
110
+ def get_lvar_idx(i)
111
+ raise Ruby2CExtError, "wrong lvar index: #{i}" unless i >= 0 && i < base_scope.tbl.size
112
+ get_closure_var(:lvar, i)
113
+ end
114
+ def get_lvar(sym)
115
+ raise Ruby2CExtError, "unknown lvar: #{sym}" unless (i = base_scope.tbl.index(sym))
116
+ get_lvar_idx(i)
117
+ end
118
+ def get_lvar_ary
119
+ get_closure_ary(:lvar)
120
+ end
121
+
122
+ def get_dvar(sym)
123
+ if tbl.include?(sym)
124
+ return get_dvar_curr(sym)
125
+ end
126
+ cur = self
127
+ while (cur = cur.outer_scope)
128
+ if (i = cur.tbl.index(sym))
129
+ return get_closure_var(cur.depth, i)
130
+ end
131
+ end
132
+ # Ruby versions <= 1.8.5 should/will never reach here
133
+ # (otherwise it is a bug in the parser).
134
+ # But starting after 1.8.5 dvars are only initialized, if there
135
+ # is an assignment in a sub-block. Because of that we can reach
136
+ # here, in that case we want a new dvar_curr:
137
+ get_dvar_curr(sym)
138
+ end
139
+ def get_dvar_curr(sym)
140
+ unless (i = tbl.index(sym))
141
+ i = tbl.size
142
+ tbl << sym
143
+ end
144
+ "dvar[#{i}]"
145
+ end
146
+ def get_dvar_ary(idx)
147
+ if idx == depth
148
+ self.need_heap = true
149
+ "dvar_ary"
150
+ else
151
+ get_closure_ary(idx)
152
+ end
153
+ end
154
+
155
+ # blocks always has public vmode
156
+ def vmode
157
+ :public
158
+ end
159
+ def vmode_method?(method_id)
160
+ false
161
+ end
162
+ def vmode_def_fun
163
+ "rb_define_method"
164
+ end
165
+
166
+ def new_dyna_scope
167
+ DynaScope.new(base_scope, self, depth + 1)
168
+ end
169
+
170
+ def var_ptr_for_wrap
171
+ tbl.empty? ? nil : "dvar"
172
+ end
173
+
174
+ def init_c_code
175
+ return nil if tbl.empty?
176
+ res = []
177
+ if need_heap
178
+ res << "VALUE *dvar;"
179
+ res << "VALUE dvar_ary = rb_ary_new2(#{tbl.size});"
180
+ res << "rb_mem_clear(RARRAY(dvar_ary)->ptr, #{tbl.size});"
181
+ res << "RARRAY(dvar_ary)->len = #{tbl.size};"
182
+ res << "dvar = RARRAY(dvar_ary)->ptr;"
183
+ else
184
+ res << "VALUE dvar[#{tbl.size}];\nrb_mem_clear(dvar, #{tbl.size});"
185
+ end
186
+ res.join("\n")
187
+ end
188
+ end
189
+
190
+ class WrappedScope
191
+ def initialize(base_scope)
192
+ @base_scope = base_scope
193
+ end
194
+
195
+ # redirect to @base_scope
196
+ def method_missing(meth, *arg)
197
+ res = @base_scope.send(meth, *arg)
198
+ if String === res
199
+ case res
200
+ when "lvar_ary"
201
+ raise Ruby2CExtError::Bug, "unexpected need for lvar_ary in WrappedScope"
202
+ when "dvar_ary"
203
+ raise Ruby2CExtError::Bug, "unexpected need for dvar_ary in WrappedScope"
204
+ when /\bclosure\b/
205
+ res.gsub!(/\bclosure\b/, "(wrap_ptr->closure)")
206
+ when /\b[ld]var\b/
207
+ res.gsub!(/\b[ld]var\b/, "(wrap_ptr->var)")
208
+ when /^rb_define/
209
+ # nothing
210
+ else
211
+ raise Ruby2CExtError::Bug, "unexpected string returned from #{@base_scope.class}##{meth}: #{res}"
212
+ end
213
+ end
214
+ res
215
+ end
216
+
217
+ def var_ptr_for_wrap
218
+ raise Ruby2CExtError::Bug, "unexpected call of var_ptr_for_wrap"
219
+ end
220
+
221
+ def init_c_code
222
+ raise Ruby2CExtError::Bug, "unexpected call of init_c_code"
223
+ end
224
+ end
225
+ end
226
+
227
+ end