crystalizer 0.2.2

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