crystalizer 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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