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,222 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Optimizations</title>
|
5
|
+
<link href="style.css" media="all" rel="Stylesheet" type="text/css">
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h1>Optimizations</h1>
|
9
|
+
|
10
|
+
|
11
|
+
<p>Without any optimizations enabled Ruby2CExtension tries to match Ruby’s
|
12
|
+
semantics as close as possible, but sometimes it might be desired to sacrifice
|
13
|
+
some compatibility in exchange for faster execution.</p>
|
14
|
+
|
15
|
+
|
16
|
+
<p>Ruby2CExtension offers different optimizations which can be enabled
|
17
|
+
indivdually (see <a href="rb2cx.html">rb2cx</a> and <a href="eval2c.html">Eval2C</a>). All these
|
18
|
+
optimizations should be safe, i.e. they won’t produce segfaults or other C
|
19
|
+
level failures, but, as mentioned above, they sacrifice compatibility for
|
20
|
+
faster execution and thus can result in “wrong” behavior of the compiled Ruby
|
21
|
+
code.</p>
|
22
|
+
|
23
|
+
|
24
|
+
<p>Sections: <a href="#section1">Constant Lookup Caching</a>, <a href="#section2">Optimization of Calls to Built-in Methods</a>, <a href="#section3">Inlining of Some Built-in Methods</a>, <a href="#section4">Case Optimization</a>.</p>
|
25
|
+
|
26
|
+
|
27
|
+
<h2 id="section1">Constant Lookup Caching</h2>
|
28
|
+
|
29
|
+
|
30
|
+
<p>This optimization simply assumes that constants really are constant. So the
|
31
|
+
constant lookup for each constant is only performed once and then the result
|
32
|
+
is cached for later use.</p>
|
33
|
+
|
34
|
+
|
35
|
+
<p>Because in Ruby constants can actually change their value, this optimization
|
36
|
+
can lead to wrong results. So if your code depends on changing constants, then
|
37
|
+
this optimization should not be enabled. Otherwise it can provide a small
|
38
|
+
performance improvement.</p>
|
39
|
+
|
40
|
+
|
41
|
+
<h2 id="section2">Optimization of Calls to Built-in Methods</h2>
|
42
|
+
|
43
|
+
|
44
|
+
<p>This optimization is by far the most important one, because it can produce
|
45
|
+
very significant speedups for most Ruby code. It replaces normal calls to many
|
46
|
+
(C-implemented) methods of the built-in classes with (almost) direct calls to
|
47
|
+
the C functions that implement these methods.</p>
|
48
|
+
|
49
|
+
|
50
|
+
<p>The motivation behind this optimization is that method calls are generally
|
51
|
+
considered to be one of the “slowest” parts of Ruby and since almost
|
52
|
+
everything is method calls in Ruby it helps to improve at least some of them.
|
53
|
+
A method call basically consists of two parts: first the method is looked up
|
54
|
+
in the receivers class (or its ancestors) and then Ruby sets up the
|
55
|
+
environment for the method and executes the code. For many of the
|
56
|
+
C-implemented methods of the built-in classes the setup is not necessary and
|
57
|
+
if we assume that they are generally not modified/redefined, we can also avoid
|
58
|
+
the lookup. So we basically can directly call the C function that implements
|
59
|
+
the method and thereby avoid most of the method call overhead in these cases.</p>
|
60
|
+
|
61
|
+
|
62
|
+
<p>Because this can not be done for every C-implemented method, there is a list
|
63
|
+
of method names (with arities and built-in types that have this method) for
|
64
|
+
which the optimization can be applied. When a call to such a method is
|
65
|
+
compiled, instead of doing a normal <code>rb_funcall()</code>, another C function is
|
66
|
+
called which checks if the type of the receiver matches one of the built-in
|
67
|
+
types that have this method and if yes, then the implementing C function is
|
68
|
+
called directly otherwise a normal Ruby call is performed.</p>
|
69
|
+
|
70
|
+
|
71
|
+
<p>To be able to directly call the C functions that implement the methods, the
|
72
|
+
pointers to these C functions have to be looked up at least once. So, when the
|
73
|
+
C extension is <code>require</code>d, for all methods that are used in this
|
74
|
+
extension the pointers to the C functions are looked up. This is also kind of
|
75
|
+
a sanity check: if that lookup fails or the lookup returns that the method is
|
76
|
+
not implemented by a C function (e.g. because it was modified/overridden by a
|
77
|
+
Ruby implementation), then this method will later be called by just using
|
78
|
+
<code>rb_funcall()</code>.</p>
|
79
|
+
|
80
|
+
|
81
|
+
<p>The built-in types which are included in this optimization are <code>Array</code>,
|
82
|
+
<code>Bignum</code>, <code>FalseClass</code>, <code>Fixnum</code>, <code>Float</code>, <code>Hash</code>, <code>NilClass</code>, <code>Regexp</code>,
|
83
|
+
<code>String</code>, <code>Symbol</code> and <code>TrueClass</code>. For the detailed method list please see
|
84
|
+
<code>lib/ruby2cext/plugins/builtin_methods.rb</code>.</p>
|
85
|
+
|
86
|
+
|
87
|
+
<p>If the type of the receiver is known at compile time, then it is possibly to
|
88
|
+
even further optimize by directly calling the implementing C function, instead
|
89
|
+
of doing the indirect call with the runtime type checking. An example for such
|
90
|
+
a case is the expression <code>1 == some_var</code>, in this expression the
|
91
|
+
receiver is a fixnum. The compile time type detection also works for string
|
92
|
+
literals, array literals, hash literals and regexp literals.</p>
|
93
|
+
|
94
|
+
|
95
|
+
<p>As mentioned above, this optimization can speedup most Ruby code, but it also
|
96
|
+
has downsides:</p>
|
97
|
+
|
98
|
+
|
99
|
+
<ul>
|
100
|
+
<li>If the type of the receiver of such a method call is not one of the
|
101
|
+
supported built-in types, then the call will actually be slightly slower
|
102
|
+
than it would have been without the optimization (because of the additional
|
103
|
+
type check). But the overhead is very small.</li>
|
104
|
+
<li>If one of the supported methods is modified after the compiled C extension
|
105
|
+
is <code>require</code>d, then this modification will not be used in the
|
106
|
+
extension, the extension will just continue to use the C implementation.</li>
|
107
|
+
</ul>
|
108
|
+
|
109
|
+
|
110
|
+
But in general this optimization can and should always be used, unless it is
|
111
|
+
necessary to modify built-in methods after the extension is
|
112
|
+
<code>require</code>d.
|
113
|
+
|
114
|
+
<h2 id="section3">Inlining of Some Built-in Methods</h2>
|
115
|
+
|
116
|
+
|
117
|
+
<p>This optimization goes one step further than the previous optimization by
|
118
|
+
avoiding the method calls completely and instead directly inlining the code
|
119
|
+
for the following methods: <code>nil?</code>, <code>equal?</code> and <code>__send__</code> (without a block).</p>
|
120
|
+
|
121
|
+
|
122
|
+
<p>Because these three methods should never be redefined in any class (according
|
123
|
+
to their documentation), this optimization can be applied to all calls of
|
124
|
+
these methods (with the correct arities) independent of the type of the
|
125
|
+
receiver.</p>
|
126
|
+
|
127
|
+
|
128
|
+
<p>Details:</p>
|
129
|
+
|
130
|
+
|
131
|
+
<ul>
|
132
|
+
<li>Any call to <code>nil?</code> with no arguments will be translated to a simple check
|
133
|
+
that determines if the receiver is <code>nil</code>.</li>
|
134
|
+
<li>Any call to <code>equal?</code> with one argument will be translated to a simple check
|
135
|
+
that determines if the receiver is equal to the first argument (i.e. they
|
136
|
+
have the same id).</li>
|
137
|
+
<li>Any call to <code>__send__</code> with one or more arguments and without a block will
|
138
|
+
be translated to code that directly performs the method call. As a result
|
139
|
+
<code>__send__(:foo, 1, 2, 3)</code> will perform practically as fast as
|
140
|
+
<code>foo(1, 2, 3)</code> in compiled code.</li>
|
141
|
+
</ul>
|
142
|
+
|
143
|
+
|
144
|
+
<p>In general this optimization can and should always be used, unless one of the
|
145
|
+
affected methods is redefined somewhere.</p>
|
146
|
+
|
147
|
+
|
148
|
+
<h2 id="section4">Case Optimization</h2>
|
149
|
+
|
150
|
+
|
151
|
+
<p>This optimization can in some cases transform Ruby <code>case</code>/<code>when</code> statements to
|
152
|
+
real C <code>switch</code>/<code>case</code> statements. This works for cases with Ruby immediate
|
153
|
+
values, for which the integral value is known at compile time, i.e. <code>nil</code>,
|
154
|
+
<code>true</code>, <code>false</code> and fixnums.</p>
|
155
|
+
|
156
|
+
|
157
|
+
<p>These cases have to be put at the beginning, all following non optimizable
|
158
|
+
whens will be handled normally. Example:</p>
|
159
|
+
|
160
|
+
|
161
|
+
<pre><code>case x
|
162
|
+
when true
|
163
|
+
# opt
|
164
|
+
when 2, 3, nil
|
165
|
+
# opt
|
166
|
+
when false, 26
|
167
|
+
# opt
|
168
|
+
when /foo/
|
169
|
+
# normal
|
170
|
+
when 23
|
171
|
+
# normal because, it is after another normal case
|
172
|
+
end
|
173
|
+
</code></pre>
|
174
|
+
|
175
|
+
<p>This basically gets converted to the following C code:</p>
|
176
|
+
|
177
|
+
|
178
|
+
<pre><code>switch (eval(x)) {
|
179
|
+
case Qtrue:
|
180
|
+
code;
|
181
|
+
break;
|
182
|
+
case LONG2FIX(2):
|
183
|
+
case LONG2FIX(3):
|
184
|
+
case Qnil:
|
185
|
+
code;
|
186
|
+
break;
|
187
|
+
case Qfalse:
|
188
|
+
case LONG2FIX(26):
|
189
|
+
code;
|
190
|
+
break;
|
191
|
+
default:
|
192
|
+
other cases ...
|
193
|
+
}
|
194
|
+
</code></pre>
|
195
|
+
|
196
|
+
<p>There also is a fallback that ensures that cases like</p>
|
197
|
+
|
198
|
+
|
199
|
+
<pre><code>case 2.0
|
200
|
+
when 2
|
201
|
+
...
|
202
|
+
end
|
203
|
+
</code></pre>
|
204
|
+
|
205
|
+
<p>still work.</p>
|
206
|
+
|
207
|
+
|
208
|
+
<p>This optimization can be useful for some kind of opcode dispatch in an
|
209
|
+
interpreter for example.</p>
|
210
|
+
|
211
|
+
|
212
|
+
<p>It should always produce the correct results, unless the <code>===</code> method of
|
213
|
+
<code>Fixnum</code>, <code>TrueClass</code>, <code>FalseClass</code> or <code>NilClass</code> is modified. So it is
|
214
|
+
generally okay to enable this optimization.</p>
|
215
|
+
|
216
|
+
|
217
|
+
<p>When this optimization is combined with the built-in methods optimization,
|
218
|
+
then the gained speedup is not really big (because the <code>===</code> calls for the
|
219
|
+
<code>when</code> cases are optimized by the built-in methods optimization anyway), but
|
220
|
+
it can be useful for larger <code>case</code>/<code>when</code> statements.</p>
|
221
|
+
</body>
|
222
|
+
</html>
|
data/doc/rb2cx.html
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>rb2cx</title>
|
5
|
+
<link href="style.css" media="all" rel="Stylesheet" type="text/css">
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h1>rb2cx</h1>
|
9
|
+
|
10
|
+
|
11
|
+
<p><code>rb2cx</code> is the command line interface to the functionality of Ruby2CExtension.
|
12
|
+
It takes one or more Ruby files, translates them to equivalent C extensions
|
13
|
+
and optionally compiles them with the C compiler.</p>
|
14
|
+
|
15
|
+
|
16
|
+
<p>Sections: <a href="#section1">Overview</a>, <a href="#section2">General Options</a>, <a href="#section3">Include Option</a>, <a href="#section4">Optimization Options</a>, <a href="#section5">Examples</a>.</p>
|
17
|
+
|
18
|
+
|
19
|
+
<h2 id="section1">Overview</h2>
|
20
|
+
|
21
|
+
|
22
|
+
<p>The general usage is very simple, just run <code>rb2cx</code> with the filenames of the
|
23
|
+
Ruby files as arguments:</p>
|
24
|
+
|
25
|
+
|
26
|
+
<pre><code>rb2cx file1.rb file2.rb
|
27
|
+
</code></pre>
|
28
|
+
|
29
|
+
<p>This will produce <code>file1.c</code>, <code>file2.c</code> and the compiled extensions <code>file1.so</code>,
|
30
|
+
<code>file2.so</code> (the file extension depends on the platform).</p>
|
31
|
+
|
32
|
+
|
33
|
+
<p>Additionally it is possible to specify some options before the filenames.</p>
|
34
|
+
|
35
|
+
|
36
|
+
<h2 id="section2">General Options</h2>
|
37
|
+
|
38
|
+
|
39
|
+
<ul>
|
40
|
+
<li><code>-h</code>/<code>--help</code>: a help message is printed and the program exits regardless of
|
41
|
+
other arguments.</li>
|
42
|
+
<li><code>-c</code>/<code>--only-c</code>: only the translation to C code is performed, the compilation
|
43
|
+
to native extensions is omited.</li>
|
44
|
+
<li><code>-v</code>/<code>--verbose</code>: some status messages are printed, e.g. which files are
|
45
|
+
currently translated or compiled.</li>
|
46
|
+
<li><code>-w</code>/<code>--warnings</code>: warnings are printed for some things that might not work
|
47
|
+
as expected. The warnings do not cover everything mentioned in the
|
48
|
+
<a href="limitations.html">limitations documentation</a>.</li>
|
49
|
+
<li><code>-V</code>/<code>--version</code>: the Ruby2CExtension version is printed.</li>
|
50
|
+
</ul>
|
51
|
+
|
52
|
+
|
53
|
+
<h2 id="section3">Include Option</h2>
|
54
|
+
|
55
|
+
|
56
|
+
<p>Ruby2CExtension has an experimental feature that allows dependencies of a
|
57
|
+
compiled Ruby file to be included in the same extension. This is best
|
58
|
+
described with an example. Let’s say we have 3 files: <code>a.rb</code>, <code>b.rb</code> and
|
59
|
+
<code>c.rb</code>:</p>
|
60
|
+
|
61
|
+
|
62
|
+
<pre><code># a.rb
|
63
|
+
puts "a"
|
64
|
+
class A; end
|
65
|
+
</code></pre>
|
66
|
+
|
67
|
+
<pre><code># b.rb
|
68
|
+
puts "b"
|
69
|
+
require "a"
|
70
|
+
class B; end
|
71
|
+
</code></pre>
|
72
|
+
|
73
|
+
<pre><code># c.rb
|
74
|
+
require "a"
|
75
|
+
require "b"
|
76
|
+
puts "c"
|
77
|
+
</code></pre>
|
78
|
+
|
79
|
+
<p>The <code>require</code>-include feature is enabled if the <code>-I</code>/<code>--include</code> option
|
80
|
+
followed by a search path is given (possibly multiple times). The search paths
|
81
|
+
can be absolute or relative paths, they are searched for <code>require</code>d
|
82
|
+
files. So, if the example is compiled to a C extension with</p>
|
83
|
+
|
84
|
+
|
85
|
+
<pre><code>rb2cx -I . c.rb
|
86
|
+
</code></pre>
|
87
|
+
|
88
|
+
<p>then the following will happen. For each call to <code>require</code> with no explicit
|
89
|
+
receiver and one argument that is a simple string (i.e. no interpolation) the
|
90
|
+
search paths are searched for a file that matches the argument to the
|
91
|
+
<code>require</code> call (with an algorithm similar to Ruby’s). If no matching file is
|
92
|
+
found, then the call to <code>require</code> is compiled as usual. But if a matching file
|
93
|
+
is found, then that file is read and it is translated to C and instead of
|
94
|
+
compiling the original <code>require</code> call that translated C code will be executed,
|
95
|
+
unless a <code>require</code> of that file was encountered before.</p>
|
96
|
+
|
97
|
+
|
98
|
+
<p>So in the example we will get one C extension, that contains the code of all
|
99
|
+
three files and the <code>require</code>s are performed at the right moment
|
100
|
+
and in correct order. The output will be (notice that the <code>require
|
101
|
+
"a"</code> in <code>b.rb</code> does not result in a second execution of <code>a.rb</code>, as
|
102
|
+
expected):</p>
|
103
|
+
|
104
|
+
|
105
|
+
<pre><code>a
|
106
|
+
b
|
107
|
+
c
|
108
|
+
</code></pre>
|
109
|
+
|
110
|
+
As stated above, this feature is experimental, it should work well for many
|
111
|
+
cases, e.g. for a library that has one main file which is always
|
112
|
+
<code>require</code>d by user code, but is split into multiple files for
|
113
|
+
maintenance. Such a library could be compiled into a single C extension. But
|
114
|
+
it can break for various reasons, e.g. Ruby will not be aware, that the
|
115
|
+
included files are already “<code>require</code>d” (so if a file is already
|
116
|
+
included in a C extension, but also <code>require</code>d by other normal Ruby
|
117
|
+
code, then that file will in effect execute twice).
|
118
|
+
|
119
|
+
<p>If the verbose mode is enabled (the <code>-v</code>/<code>--verbose</code> option), then each
|
120
|
+
inclusion of a file will be logged. This way one can easily check if the
|
121
|
+
expected files are actually included.</p>
|
122
|
+
|
123
|
+
|
124
|
+
<h2 id="section4">Optimization Options</h2>
|
125
|
+
|
126
|
+
|
127
|
+
<p>Ruby2CExtension can use various <a href="optimizations.html">optimizations</a> to improve
|
128
|
+
the performance of the resulting C extension. These optimizations are not
|
129
|
+
enabled by default, because they can all result in wrong behavior. The
|
130
|
+
optimizations are enabled by the <code>-O</code>/<code>--optimization</code> option followed by one
|
131
|
+
of the following optimization names:</p>
|
132
|
+
|
133
|
+
|
134
|
+
<ul>
|
135
|
+
<li><code>const_cache</code>: enables constant lookup caching</li>
|
136
|
+
<li><code>builtin_methods</code>: enables optimization of calls to built-in methods</li>
|
137
|
+
<li><code>inline_methods</code>: enables inlining of some built-in methods</li>
|
138
|
+
<li><code>case_optimize</code>: enables case optimization</li>
|
139
|
+
<li><code>all</code>: enables all of the above optimizations</li>
|
140
|
+
</ul>
|
141
|
+
|
142
|
+
|
143
|
+
<h2 id="section5">Examples</h2>
|
144
|
+
|
145
|
+
|
146
|
+
<pre><code>rb2cx -wv file.rb
|
147
|
+
rb2cx -I . -O all file.rb
|
148
|
+
rb2cx -I . -I ../libs -O const_cache -O builtin_methods -w file.rb
|
149
|
+
</code></pre>
|
150
|
+
</body>
|
151
|
+
</html>
|
data/doc/style.css
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
body {
|
2
|
+
color: #333;
|
3
|
+
background-color: white;
|
4
|
+
margin: 0 2em;
|
5
|
+
min-width: 41em;
|
6
|
+
}
|
7
|
+
h1 {
|
8
|
+
color: black;
|
9
|
+
border-bottom: 2px solid red;
|
10
|
+
}
|
11
|
+
h2 {
|
12
|
+
color: black;
|
13
|
+
}
|
14
|
+
h3 {
|
15
|
+
color: black;
|
16
|
+
}
|
17
|
+
pre {
|
18
|
+
color: black;
|
19
|
+
background-color: #eee;
|
20
|
+
padding: 0.7em 1em;
|
21
|
+
margin: 0.8em 0;
|
22
|
+
border: 1px dotted #999;
|
23
|
+
}
|
24
|
+
code {
|
25
|
+
color: black;
|
26
|
+
background-color: #eee;
|
27
|
+
}
|
@@ -0,0 +1,621 @@
|
|
1
|
+
|
2
|
+
require "ruby2cext/str_to_c_strlit"
|
3
|
+
require "ruby2cext/error"
|
4
|
+
require "ruby2cext/tools"
|
5
|
+
require "ruby2cext/common_node_comp"
|
6
|
+
require "ruby2cext/scopes"
|
7
|
+
|
8
|
+
module Ruby2CExtension
|
9
|
+
|
10
|
+
module CFunction
|
11
|
+
# contains all different Types of C functions that are compiled from ruby nodes
|
12
|
+
|
13
|
+
class Base
|
14
|
+
include CommonNodeComp
|
15
|
+
extend Tools::EnsureNodeTypeMixin
|
16
|
+
attr_reader :scope, :compiler, :closure_tbl
|
17
|
+
attr_accessor :need_res, :need_self, :need_cref, :need_class, :need_wrap
|
18
|
+
def initialize(compiler, scope)
|
19
|
+
@compiler = compiler
|
20
|
+
@scope = scope
|
21
|
+
@closure_tbl = []
|
22
|
+
@scope.closure_tbl = @closure_tbl
|
23
|
+
@lines = []
|
24
|
+
@while_stack = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# some redirects to compiler
|
28
|
+
def un(str); compiler.un(str); end
|
29
|
+
def sym(sym); compiler.sym(sym); end
|
30
|
+
def global_const(str, register_gc = true)
|
31
|
+
compiler.global_const(str, register_gc)
|
32
|
+
end
|
33
|
+
def global_var(str)
|
34
|
+
compiler.global_var(str)
|
35
|
+
end
|
36
|
+
def add_helper(str); compiler.add_helper(str); end
|
37
|
+
|
38
|
+
def get_lines
|
39
|
+
@lines.join("\n")
|
40
|
+
end
|
41
|
+
def l(line) # add_line
|
42
|
+
# ignore lines with only whitespace or only alnum chars (variable name)
|
43
|
+
unless line =~ /\A\s*\z/ || (line =~ /\A(\w*);?\z/ && !(%w[break continue].include? $1))
|
44
|
+
@lines << line
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def push_while(redo_lbl, next_lbl, break_lbl)
|
49
|
+
@while_stack << [redo_lbl, next_lbl, break_lbl]
|
50
|
+
end
|
51
|
+
def pop_while
|
52
|
+
raise Ruby2CExtError::Bug, "popped from empty while stack" if @while_stack.empty?
|
53
|
+
@while_stack.pop
|
54
|
+
end
|
55
|
+
def in_while?(lbl_type = nil)
|
56
|
+
return false if @while_stack.empty?
|
57
|
+
case lbl_type
|
58
|
+
when nil
|
59
|
+
true
|
60
|
+
when :redo
|
61
|
+
@while_stack.last[0]
|
62
|
+
when :next
|
63
|
+
@while_stack.last[1]
|
64
|
+
when :break
|
65
|
+
@while_stack.last[2]
|
66
|
+
else
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def break_allowed?(with_value)
|
72
|
+
in_while?(:break)
|
73
|
+
end
|
74
|
+
def comp_break(hash)
|
75
|
+
if (lbl = in_while?(:break))
|
76
|
+
l "while_res = #{comp(hash[:stts])};"
|
77
|
+
l "goto #{lbl};"
|
78
|
+
"Qnil"
|
79
|
+
else
|
80
|
+
raise Ruby2CExtError::NotSupported, "break is not supported here"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def next_allowed?
|
85
|
+
in_while?(:next)
|
86
|
+
end
|
87
|
+
def comp_next(hash)
|
88
|
+
if (lbl = in_while?(:next))
|
89
|
+
# hash[:stts] is silently ignored (as ruby does)
|
90
|
+
l "goto #{lbl};"
|
91
|
+
"Qnil"
|
92
|
+
else
|
93
|
+
raise Ruby2CExtError::NotSupported, "next is not supported here"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def redo_allowed?
|
98
|
+
in_while?(:redo)
|
99
|
+
end
|
100
|
+
def comp_redo(hash)
|
101
|
+
if (lbl = in_while?(:redo))
|
102
|
+
l "goto #{lbl};"
|
103
|
+
"Qnil"
|
104
|
+
else
|
105
|
+
raise Ruby2CExtError::NotSupported, "redo is not supported here"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def return_allowed?
|
110
|
+
false
|
111
|
+
end
|
112
|
+
def comp_return(hash)
|
113
|
+
raise Ruby2CExtError::NotSupported, "return is not supported here"
|
114
|
+
end
|
115
|
+
|
116
|
+
def comp_retry(hash)
|
117
|
+
l "rb_jump_tag(0x4 /* TAG_RETRY */);"
|
118
|
+
"Qnil"
|
119
|
+
end
|
120
|
+
|
121
|
+
def need_closure_ptr
|
122
|
+
false # only needed in Block
|
123
|
+
end
|
124
|
+
|
125
|
+
def assign_res(str)
|
126
|
+
self.need_res = true
|
127
|
+
unless str.strip == "res"
|
128
|
+
l "res = #{str};"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
def get_self
|
132
|
+
self.need_self = true
|
133
|
+
"self"
|
134
|
+
end
|
135
|
+
def get_cref
|
136
|
+
self.need_cref = true
|
137
|
+
get_cref_impl # subclass
|
138
|
+
end
|
139
|
+
def get_class
|
140
|
+
self.need_class = true
|
141
|
+
"s_class"
|
142
|
+
end
|
143
|
+
def get_cbase
|
144
|
+
"(#{get_cref}->nd_clss)"
|
145
|
+
end
|
146
|
+
def get_cvar_cbase
|
147
|
+
# there is always at least one real class in the cref chain
|
148
|
+
add_helper <<-EOC
|
149
|
+
static VALUE cvar_cbase(NODE *cref) {
|
150
|
+
while (FL_TEST(cref->nd_clss, FL_SINGLETON)) { cref = cref->nd_next; }
|
151
|
+
return cref->nd_clss;
|
152
|
+
}
|
153
|
+
EOC
|
154
|
+
"cvar_cbase(#{get_cref})"
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_closure_ary_var
|
158
|
+
"my_closure_ary"
|
159
|
+
end
|
160
|
+
|
161
|
+
def get_wrap_ptr
|
162
|
+
"(&the_wrap)"
|
163
|
+
end
|
164
|
+
|
165
|
+
def add_closure_need(sym)
|
166
|
+
closure_tbl << sym unless closure_tbl.include? sym
|
167
|
+
end
|
168
|
+
|
169
|
+
def closure_buid_c_code
|
170
|
+
if closure_tbl.empty?
|
171
|
+
nil
|
172
|
+
else
|
173
|
+
res = ["#{get_closure_ary_var} = rb_ary_new2(#{closure_tbl.size});"]
|
174
|
+
closure_tbl.each_with_index { |entry, idx|
|
175
|
+
case entry
|
176
|
+
when Integer
|
177
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = #{scope.get_dvar_ary(entry)};"
|
178
|
+
when :lvar
|
179
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = #{scope.get_lvar_ary};"
|
180
|
+
when :self
|
181
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = #{get_self};"
|
182
|
+
when :class
|
183
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = #{get_class};"
|
184
|
+
when :cref
|
185
|
+
add_helper <<-EOC
|
186
|
+
static void cref_data_mark(NODE *n) {
|
187
|
+
rb_gc_mark((VALUE)n);
|
188
|
+
}
|
189
|
+
EOC
|
190
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = " +
|
191
|
+
"Data_Wrap_Struct(rb_cObject, cref_data_mark, 0, #{get_cref});"
|
192
|
+
else
|
193
|
+
raise Ruby2CExtError::Bug, "unexpected closure_tbl entry: #{entry.inspect}"
|
194
|
+
end
|
195
|
+
}
|
196
|
+
res << "RARRAY(#{get_closure_ary_var})->len = #{closure_tbl.size};"
|
197
|
+
res.join("\n")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def wrap_buid_c_code
|
202
|
+
add_helper <<-EOC
|
203
|
+
struct wrap {
|
204
|
+
VALUE self;
|
205
|
+
VALUE s_class;
|
206
|
+
NODE *cref;
|
207
|
+
VALUE my_closure_ary;
|
208
|
+
VALUE *closure;
|
209
|
+
VALUE *var;
|
210
|
+
long state;
|
211
|
+
};
|
212
|
+
EOC
|
213
|
+
res = []
|
214
|
+
res << "the_wrap.self = #{get_self};" if need_self
|
215
|
+
res << "the_wrap.s_class = #{get_class};" if need_class
|
216
|
+
res << "the_wrap.cref = #{get_cref};" if need_cref
|
217
|
+
res << "the_wrap.my_closure_ary = #{get_closure_ary_var};" unless closure_tbl.empty?
|
218
|
+
res << "the_wrap.closure = closure;" if need_closure_ptr
|
219
|
+
res << "the_wrap.var = #{scope.var_ptr_for_wrap};" if scope.var_ptr_for_wrap
|
220
|
+
res.compact.join("\n")
|
221
|
+
end
|
222
|
+
|
223
|
+
def init_c_code
|
224
|
+
cb_c_code = closure_buid_c_code # must be called before the rest because it might change self or scope
|
225
|
+
res = []
|
226
|
+
res << "VALUE res;" if need_res
|
227
|
+
res << "VALUE s_class = (#{get_cref})->nd_clss;" if need_class
|
228
|
+
res << "VALUE #{get_closure_ary_var};" if cb_c_code
|
229
|
+
res << "struct wrap the_wrap;" if need_wrap
|
230
|
+
res << scope.init_c_code
|
231
|
+
res << cb_c_code if cb_c_code
|
232
|
+
res << wrap_buid_c_code if need_wrap
|
233
|
+
res.compact.join("\n")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
class ClassModuleScope < Base
|
238
|
+
def self.compile(outer, scope_node, class_mod_var, is_class)
|
239
|
+
ensure_node_type(scope_node, :scope)
|
240
|
+
vmode_methods = Scopes::Scope::VMODES.dup
|
241
|
+
vmode_methods.delete :module_function if is_class
|
242
|
+
cf = self.new(outer.compiler, Scopes::Scope.new(scope_node.last[:tbl], vmode_methods))
|
243
|
+
cf.instance_eval {
|
244
|
+
block = make_block(scope_node.last[:next])
|
245
|
+
l "return #{comp(block)};"
|
246
|
+
}
|
247
|
+
body = "#{cf.init_c_code}\n#{cf.get_lines}"
|
248
|
+
args = []
|
249
|
+
args << "VALUE self" if cf.need_self
|
250
|
+
args << "NODE *cref" if cf.need_cref
|
251
|
+
sig = "static VALUE FUNNAME(#{args.join(", ")}) {"
|
252
|
+
fname = cf.compiler.add_fun("#{sig}\n#{body}\n}", "class_module_scope")
|
253
|
+
outer.instance_eval {
|
254
|
+
args = []
|
255
|
+
args << class_mod_var if cf.need_self
|
256
|
+
args << "NEW_NODE(NODE_CREF, #{class_mod_var}, 0, #{get_cref})" if cf.need_cref
|
257
|
+
assign_res("#{fname}(#{args.join(", ")})")
|
258
|
+
}
|
259
|
+
"res"
|
260
|
+
end
|
261
|
+
|
262
|
+
def get_cref_impl
|
263
|
+
"cref"
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
class ToplevelScope < ClassModuleScope
|
269
|
+
def self.compile(compiler, scope_node, private_vmode = true)
|
270
|
+
ensure_node_type(scope_node, :scope)
|
271
|
+
cf = self.new(compiler, Scopes::Scope.new(scope_node.last[:tbl], [:public, :private], private_vmode))
|
272
|
+
cf.instance_eval {
|
273
|
+
block = make_block(scope_node.last[:next])
|
274
|
+
l "#{comp(block)};"
|
275
|
+
}
|
276
|
+
body = "#{cf.init_c_code}\n#{cf.get_lines}"
|
277
|
+
sig = "static void FUNNAME(VALUE self, NODE *cref) {"
|
278
|
+
cf.compiler.add_fun("#{sig}\n#{body}\n}", "toplevel_scope") # and return the function name
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
class Method < Base
|
283
|
+
def self.compile(outer, scope_node, def_fun, class_var, mid)
|
284
|
+
ensure_node_type(scope_node, :scope)
|
285
|
+
cf = self.new(outer.compiler, Scopes::Scope.new(scope_node.last[:tbl], []))
|
286
|
+
cf.instance_eval {
|
287
|
+
block_array = make_block(scope_node.last[:next]).last.dup # dup the block_array to allow modification
|
288
|
+
arg = block_array.shift
|
289
|
+
ba = nil
|
290
|
+
unless block_array.empty? || block_array.first.first != :block_arg
|
291
|
+
ba = block_array.shift
|
292
|
+
end
|
293
|
+
handle_method_args(arg, ba)
|
294
|
+
l "return #{comp([:block, block_array])};"
|
295
|
+
}
|
296
|
+
body = "#{cf.init_c_code}\n#{cf.get_lines}"
|
297
|
+
sig = "static VALUE FUNNAME(int meth_argc, VALUE *meth_argv, VALUE self) {"
|
298
|
+
fname = cf.compiler.add_fun("#{sig}\n#{body}\n}", "method")
|
299
|
+
if cf.need_cref
|
300
|
+
outer.instance_eval {
|
301
|
+
add_helper <<-EOC
|
302
|
+
static void def_only_once(ID mid) {
|
303
|
+
rb_raise(rb_eTypeError, "def for \\"%s\\" can only be used once", rb_id2name(mid));
|
304
|
+
}
|
305
|
+
EOC
|
306
|
+
l "if (#{cf.cref_global_var}) def_only_once(#{sym(mid)});"
|
307
|
+
l "#{cf.cref_global_var} = (VALUE)(#{get_cref});"
|
308
|
+
}
|
309
|
+
end
|
310
|
+
outer.l "#{def_fun}(#{class_var}, #{mid.to_s.to_c_strlit}, #{fname}, -1);"
|
311
|
+
"Qnil"
|
312
|
+
end
|
313
|
+
|
314
|
+
def return_allowed?
|
315
|
+
true
|
316
|
+
end
|
317
|
+
def comp_return(hash)
|
318
|
+
l "return #{comp(hash[:stts])};"
|
319
|
+
"Qnil"
|
320
|
+
end
|
321
|
+
|
322
|
+
def get_cref_impl
|
323
|
+
@cref_global_var ||= global_var("Qfalse")
|
324
|
+
"(RNODE(#{@cref_global_var}))"
|
325
|
+
end
|
326
|
+
attr_reader :cref_global_var
|
327
|
+
end
|
328
|
+
|
329
|
+
class Block < Base
|
330
|
+
def self.compile(outer, block_node, var_node)
|
331
|
+
ensure_node_type(block_node, :block)
|
332
|
+
cf = self.new(outer.compiler, outer.scope.new_dyna_scope)
|
333
|
+
cf.instance_eval {
|
334
|
+
if Array === var_node
|
335
|
+
if var_node.first == :masgn
|
336
|
+
dup_hash = var_node.last.dup
|
337
|
+
c_if("ruby_current_node->nd_state != 1") { # 1 is YIELD_FUNC_AVALUE
|
338
|
+
# do "svalue_to_mrhs"
|
339
|
+
c_if("bl_val == Qundef") {
|
340
|
+
l "bl_val = rb_ary_new2(0);"
|
341
|
+
}
|
342
|
+
c_else {
|
343
|
+
#if dup_hash[:head] # TODO
|
344
|
+
l "VALUE tmp = rb_check_array_type(bl_val);"
|
345
|
+
l "bl_val = (NIL_P(tmp) ? rb_ary_new3(1, bl_val) : tmp);"
|
346
|
+
#else
|
347
|
+
# l "bl_val = rb_ary_new3(1, bl_val);"
|
348
|
+
#end
|
349
|
+
}
|
350
|
+
}
|
351
|
+
dup_hash[:value] = "bl_val"
|
352
|
+
comp_masgn(dup_hash)
|
353
|
+
else
|
354
|
+
c_if("ruby_current_node->nd_state == 1") { # 1 is YIELD_FUNC_AVALUE
|
355
|
+
# do "avalue_to_svalue"
|
356
|
+
l "if (RARRAY(bl_val)->len == 0) bl_val = Qnil;"
|
357
|
+
l "else if (RARRAY(bl_val)->len == 1) bl_val = RARRAY(bl_val)->ptr[0];"
|
358
|
+
}
|
359
|
+
handle_assign(var_node, "bl_val")
|
360
|
+
end
|
361
|
+
end
|
362
|
+
l "block_redo:"
|
363
|
+
l "return #{comp_block(block_node.last)};"
|
364
|
+
}
|
365
|
+
body = "#{cf.init_c_code(outer)}\n#{cf.get_lines}"
|
366
|
+
sig = "static VALUE FUNNAME(VALUE bl_val, VALUE closure_ary, VALUE bl_self) {"
|
367
|
+
fname = cf.compiler.add_fun("#{sig}\n#{body}\n}", "block")
|
368
|
+
[fname, cf.need_closure_ptr]
|
369
|
+
end
|
370
|
+
|
371
|
+
def init_c_code(outer)
|
372
|
+
cb_c_code = closure_buid_c_code # must be called before the rest because it might change self or scope
|
373
|
+
outer.add_closure_need(:self) if need_self
|
374
|
+
outer.add_closure_need(:class) if need_class
|
375
|
+
outer.add_closure_need(:cref) if need_cref
|
376
|
+
res = []
|
377
|
+
res << "VALUE res;" if need_res
|
378
|
+
if need_closure_ptr
|
379
|
+
res << "VALUE *closure = RARRAY(closure_ary)->ptr;"
|
380
|
+
end
|
381
|
+
res << "VALUE self = (bl_self == Qundef ? closure[#{outer.closure_tbl.index(:self)}] : bl_self);" if need_self
|
382
|
+
res << "VALUE s_class = (bl_self == Qundef ? closure[#{outer.closure_tbl.index(:class)}] : ruby_class);" if need_class
|
383
|
+
if need_cref
|
384
|
+
# see #define Data_Get_Struct
|
385
|
+
res << "NODE *cref = (Check_Type(closure[#{outer.closure_tbl.index(:cref)}]," +
|
386
|
+
" T_DATA), (NODE*)DATA_PTR(closure[#{outer.closure_tbl.index(:cref)}]));"
|
387
|
+
end
|
388
|
+
res << "VALUE #{get_closure_ary_var};" if cb_c_code
|
389
|
+
res << "struct wrap the_wrap;" if need_wrap
|
390
|
+
res << scope.init_c_code
|
391
|
+
res << cb_c_code if cb_c_code
|
392
|
+
res << wrap_buid_c_code if need_wrap
|
393
|
+
res.compact.join("\n")
|
394
|
+
end
|
395
|
+
|
396
|
+
def break_allowed?(with_value)
|
397
|
+
super || !with_value
|
398
|
+
end
|
399
|
+
def comp_break(hash)
|
400
|
+
if in_while?(:break)
|
401
|
+
super
|
402
|
+
else
|
403
|
+
raise Ruby2CExtError::NotSupported, "break with a value is not supported in a block" if hash[:stts]
|
404
|
+
l "rb_iter_break();"
|
405
|
+
"Qnil"
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def next_allowed?
|
410
|
+
true
|
411
|
+
end
|
412
|
+
def comp_next(hash)
|
413
|
+
if in_while?(:next)
|
414
|
+
super
|
415
|
+
else
|
416
|
+
l "return #{comp(hash[:stts])};"
|
417
|
+
"Qnil"
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def redo_allowed?
|
422
|
+
true
|
423
|
+
end
|
424
|
+
def comp_redo(hash)
|
425
|
+
if in_while?(:redo)
|
426
|
+
super
|
427
|
+
else
|
428
|
+
l "goto block_redo;"
|
429
|
+
"Qnil"
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def need_closure_ptr
|
434
|
+
scope.need_closure || need_self || need_class || need_cref
|
435
|
+
end
|
436
|
+
|
437
|
+
def get_cref_impl
|
438
|
+
"cref"
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
class Wrap < Base
|
443
|
+
|
444
|
+
def self.compile(outer, base_name, cflow_hash = nil)
|
445
|
+
cf = self.new(outer, cflow_hash)
|
446
|
+
cf.instance_eval {
|
447
|
+
if cf.cflow_hash
|
448
|
+
l "#{get_wrap_ptr}->state &= (1<<20)-1;" # set bits 20+ to 0
|
449
|
+
end
|
450
|
+
l "return #{yield cf};"
|
451
|
+
}
|
452
|
+
body = "#{cf.init_c_code}\n#{cf.get_lines}"
|
453
|
+
sig = "static VALUE FUNNAME(struct wrap *wrap_ptr) {"
|
454
|
+
outer.need_wrap = true
|
455
|
+
cf.compiler.add_fun("#{sig}\n#{body}\n}", base_name) # and return the function name
|
456
|
+
end
|
457
|
+
|
458
|
+
attr_reader :base_cfun, :outer_cfun, :cflow_hash
|
459
|
+
|
460
|
+
# the following attr_accessors from Base are redefined, to redirect to base_cfun
|
461
|
+
[:need_self, :need_cref, :need_class, :need_wrap].each { |a|
|
462
|
+
define_method(a) { base_cfun.send(a) }
|
463
|
+
asgn_sym = :"#{a}="
|
464
|
+
define_method(asgn_sym) { |arg| base_cfun.send(asgn_sym, arg) }
|
465
|
+
}
|
466
|
+
|
467
|
+
# cflow_hash can either be nil or a hash, if it is nil then break,
|
468
|
+
# next, redo and return are not possible from the wrap to
|
469
|
+
# outer_cfun.
|
470
|
+
#
|
471
|
+
# If cflow_hash is a hash then those are possible if outer_cfun
|
472
|
+
# allows them. For this to work only the bits 0-19 of
|
473
|
+
# wrap_ptr->state may be modified by code inside this wrap and
|
474
|
+
# after the call the code generated by Wrap.handle_wrap_cflow has
|
475
|
+
# to be inserted.
|
476
|
+
def initialize(outer_cfun, cflow_hash = nil)
|
477
|
+
@outer_cfun = outer_cfun
|
478
|
+
if Wrap === outer_cfun
|
479
|
+
@base_cfun = outer_cfun.base_cfun
|
480
|
+
else
|
481
|
+
@base_cfun = outer_cfun
|
482
|
+
end
|
483
|
+
@cflow_hash = cflow_hash
|
484
|
+
@compiler = base_cfun.compiler
|
485
|
+
@scope = Scopes::WrappedScope.new(base_cfun.scope)
|
486
|
+
@closure_tbl = base_cfun.closure_tbl
|
487
|
+
@lines = []
|
488
|
+
@while_stack = []
|
489
|
+
end
|
490
|
+
|
491
|
+
BREAK_FLAG = 1 << 20
|
492
|
+
NEXT_FLAG = 1 << 21
|
493
|
+
REDO_FLAG = 1 << 22
|
494
|
+
RETURN_FLAG = 1 << 23
|
495
|
+
|
496
|
+
def break_allowed?(with_value)
|
497
|
+
super || (cflow_hash && outer_cfun.break_allowed?(with_value))
|
498
|
+
end
|
499
|
+
def comp_break(hash)
|
500
|
+
if in_while?(:break)
|
501
|
+
super
|
502
|
+
elsif break_allowed?(hash[:stts])
|
503
|
+
@cflow_hash[:break] ||= hash[:stts]
|
504
|
+
l "#{get_wrap_ptr}->state |= #{BREAK_FLAG};"
|
505
|
+
l "return #{comp(hash[:stts])};"
|
506
|
+
"Qnil"
|
507
|
+
else
|
508
|
+
raise Ruby2CExtError::NotSupported, "break #{hash[:stts] ? "with a value " : ""}is not supported here"
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def next_allowed?
|
513
|
+
super || (cflow_hash && outer_cfun.next_allowed?)
|
514
|
+
end
|
515
|
+
def comp_next(hash)
|
516
|
+
if in_while?(:next)
|
517
|
+
super
|
518
|
+
elsif next_allowed?
|
519
|
+
@cflow_hash[:next] = true
|
520
|
+
l "#{get_wrap_ptr}->state |= #{NEXT_FLAG};"
|
521
|
+
# hash[:stts] might be evaluated unnecessarily (because it
|
522
|
+
# is ignored in while loops), but oh well ...
|
523
|
+
l "return #{comp(hash[:stts])};"
|
524
|
+
"Qnil"
|
525
|
+
else
|
526
|
+
raise Ruby2CExtError::NotSupported, "next is not supported here"
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
def redo_allowed?
|
531
|
+
super || (cflow_hash && outer_cfun.redo_allowed?)
|
532
|
+
end
|
533
|
+
def comp_redo(hash)
|
534
|
+
if in_while?(:redo)
|
535
|
+
super
|
536
|
+
elsif redo_allowed?
|
537
|
+
@cflow_hash[:redo] = true
|
538
|
+
l "#{get_wrap_ptr}->state |= #{REDO_FLAG};"
|
539
|
+
l "return Qnil;"
|
540
|
+
"Qnil"
|
541
|
+
else
|
542
|
+
raise Ruby2CExtError::NotSupported, "redo is not supported here"
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
def return_allowed?
|
547
|
+
cflow_hash && outer_cfun.return_allowed?
|
548
|
+
end
|
549
|
+
def comp_return(hash)
|
550
|
+
if return_allowed?
|
551
|
+
@cflow_hash[:return] = true
|
552
|
+
l "#{get_wrap_ptr}->state |= #{RETURN_FLAG};"
|
553
|
+
l "return #{comp(hash[:stts])};"
|
554
|
+
"Qnil"
|
555
|
+
else
|
556
|
+
raise Ruby2CExtError::NotSupported, "return is not supported here"
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
# the result of the call to the wrap cfun is expected in "res"
|
561
|
+
def self.handle_wrap_cflow(cfun, cflow_hash)
|
562
|
+
cfun.instance_eval {
|
563
|
+
if cflow_hash.has_key?(:break)
|
564
|
+
c_if("#{get_wrap_ptr}->state & #{BREAK_FLAG}") {
|
565
|
+
l comp_break(:stts => (cflow_hash[:break] ? "res" : false))
|
566
|
+
}
|
567
|
+
end
|
568
|
+
if cflow_hash.has_key?(:next)
|
569
|
+
c_if("#{get_wrap_ptr}->state & #{NEXT_FLAG}") {
|
570
|
+
l comp_next(:stts => "res")
|
571
|
+
}
|
572
|
+
end
|
573
|
+
if cflow_hash.has_key?(:redo)
|
574
|
+
c_if("#{get_wrap_ptr}->state & #{REDO_FLAG}") {
|
575
|
+
l comp_redo({})
|
576
|
+
}
|
577
|
+
end
|
578
|
+
if cflow_hash.has_key?(:return)
|
579
|
+
c_if("#{get_wrap_ptr}->state & #{RETURN_FLAG}") {
|
580
|
+
l comp_return(:stts => "res")
|
581
|
+
}
|
582
|
+
end
|
583
|
+
}
|
584
|
+
end
|
585
|
+
|
586
|
+
def get_self
|
587
|
+
self.need_self = true
|
588
|
+
"(wrap_ptr->self)"
|
589
|
+
end
|
590
|
+
def get_cref_impl
|
591
|
+
"(wrap_ptr->cref)"
|
592
|
+
end
|
593
|
+
def get_class
|
594
|
+
self.need_class = true
|
595
|
+
"(wrap_ptr->s_class)"
|
596
|
+
end
|
597
|
+
|
598
|
+
def get_closure_ary_var
|
599
|
+
"(wrap_ptr->my_closure_ary)"
|
600
|
+
end
|
601
|
+
|
602
|
+
def get_wrap_ptr
|
603
|
+
"wrap_ptr"
|
604
|
+
end
|
605
|
+
|
606
|
+
[:closure_buid_c_code, :wrap_buid_c_code, :need_closure_ptr].each { |m|
|
607
|
+
define_method(m) {
|
608
|
+
raise Ruby2CExtError::Bug, "the method #{m} may not be called for an instance of Wrap"
|
609
|
+
}
|
610
|
+
}
|
611
|
+
|
612
|
+
def init_c_code
|
613
|
+
res = []
|
614
|
+
res << "VALUE res;" if need_res
|
615
|
+
res.compact.join("\n")
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
end
|
620
|
+
|
621
|
+
end
|