ruby2cext 0.2.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.
@@ -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