ruby2cext 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +27 -0
- data/README +44 -0
- data/bin/rb2cx +178 -0
- data/doc/eval2c.html +281 -0
- data/doc/index.html +218 -0
- data/doc/limitations.html +581 -0
- data/doc/optimizations.html +222 -0
- data/doc/rb2cx.html +151 -0
- data/doc/style.css +27 -0
- data/lib/ruby2cext/c_function.rb +621 -0
- data/lib/ruby2cext/common_node_comp.rb +1409 -0
- data/lib/ruby2cext/compiler.rb +242 -0
- data/lib/ruby2cext/error.rb +15 -0
- data/lib/ruby2cext/eval2c.rb +129 -0
- data/lib/ruby2cext/parser.rb +36 -0
- data/lib/ruby2cext/plugin.rb +24 -0
- data/lib/ruby2cext/plugins/builtin_methods.rb +820 -0
- data/lib/ruby2cext/plugins/case_optimize.rb +105 -0
- data/lib/ruby2cext/plugins/const_cache.rb +38 -0
- data/lib/ruby2cext/plugins/inline_methods.rb +69 -0
- data/lib/ruby2cext/plugins/require_include.rb +71 -0
- data/lib/ruby2cext/plugins/warnings.rb +123 -0
- data/lib/ruby2cext/scopes.rb +227 -0
- data/lib/ruby2cext/str_to_c_strlit.rb +12 -0
- data/lib/ruby2cext/tools.rb +84 -0
- data/lib/ruby2cext/version.rb +22 -0
- data/testfiles/bench.rb +116 -0
- data/testfiles/eval2c/test_eval2c.rb +37 -0
- data/testfiles/test.rb +615 -0
- data/testfiles/vmode_test.rb +73 -0
- data/testfiles/warn_test.rb +35 -0
- metadata +87 -0
@@ -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
|