ruby2cext 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,242 @@
1
+
2
+ require "rubynode"
3
+ require "rbconfig"
4
+ require "ruby2cext/parser"
5
+ require "ruby2cext/error"
6
+ require "ruby2cext/tools"
7
+ require "ruby2cext/c_function"
8
+ require "ruby2cext/version"
9
+
10
+ module Ruby2CExtension
11
+
12
+ class Compiler
13
+
14
+ attr_reader :name, :logger, :plugins
15
+
16
+ def initialize(name, logger = nil)
17
+ @name = name
18
+ @logger = logger
19
+ @funs = []
20
+ @funs_reuseable = {}
21
+ @toplevel_funs = []
22
+ @sym_man = Tools::SymbolManager.new
23
+ @global_man = Tools::GlobalManager.new
24
+ @uniq_names = Tools::UniqueNames.new
25
+ @helpers = {}
26
+ @plugins = []
27
+ @preprocessors = {}
28
+ end
29
+
30
+ def to_c_code(time_stamp = Time.now)
31
+ plugins_global = @plugins.map { |plugin| plugin.global_c_code }
32
+ plugins_init = @plugins.map { |plugin| plugin.init_c_code }
33
+ res = [
34
+ "/* generated by #{FULL_VERSION_STRING} on #{time_stamp} */",
35
+ "#include <ruby.h>",
36
+ "#include <node.h>",
37
+ "#include <env.h>",
38
+ "#include <st.h>",
39
+ "extern VALUE ruby_top_self;",
40
+ "static VALUE org_ruby_top_self;",
41
+ @sym_man.to_c_code,
42
+ @global_man.to_c_code,
43
+ ]
44
+ res.concat(@helpers.keys.sort)
45
+ res.concat(plugins_global)
46
+ res.concat(@funs)
47
+ res << "void Init_#{@name}() {"
48
+ res << "org_ruby_top_self = ruby_top_self;"
49
+ # just to be sure
50
+ res << "rb_global_variable(&org_ruby_top_self);"
51
+ res << "init_syms();"
52
+ res << "init_globals();"
53
+ res << "NODE *cref = rb_node_newnode(NODE_CREF, rb_cObject, 0, 0);"
54
+ res.concat(plugins_init)
55
+ @toplevel_funs.each { |f| res << "#{f}(ruby_top_self, cref);" }
56
+ res << "}"
57
+ res.join("\n").split("\n").map { |l| l.strip }.reject { |l| l.empty? }.join("\n")
58
+ end
59
+
60
+ def add_toplevel(function_name)
61
+ @toplevel_funs << function_name
62
+ end
63
+
64
+ # non destructive: node_tree will not be changed
65
+ def compile_toplevel_function(node_tree, private_vmode = true)
66
+ CFunction::ToplevelScope.compile(self, node_tree, private_vmode)
67
+ end
68
+
69
+ NODE_TRANSFORM_OPTIONS = {:include_node => true, :keep_newline_nodes => true}
70
+
71
+ def rb_file_to_toplevel_functions(source_str, file_name)
72
+ res = []
73
+ hash = Parser.parse_string(source_str, file_name)
74
+ # add all BEGIN blocks, if available
75
+ if (beg_tree = hash[:begin])
76
+ beg_tree = beg_tree.transform(NODE_TRANSFORM_OPTIONS)
77
+ if beg_tree.first == :block
78
+ beg_tree.last.each { |s| res << compile_toplevel_function(s, false) }
79
+ else
80
+ res << compile_toplevel_function(beg_tree, false)
81
+ end
82
+ end
83
+ # add toplevel scope
84
+ if (tree = hash[:tree])
85
+ res << compile_toplevel_function(tree.transform(NODE_TRANSFORM_OPTIONS))
86
+ end
87
+ res
88
+ end
89
+
90
+ def add_rb_file(source_str, file_name)
91
+ rb_file_to_toplevel_functions(source_str, file_name).each { |fn|
92
+ add_toplevel(fn)
93
+ }
94
+ end
95
+
96
+ # uniq name
97
+ def un(str)
98
+ @uniq_names.get(str)
99
+ end
100
+ def sym(sym)
101
+ @sym_man.get(sym)
102
+ end
103
+ def global_const(str, register_gc = true)
104
+ @global_man.get(str, true, register_gc)
105
+ end
106
+ def global_var(str)
107
+ @global_man.get(str, false, true)
108
+ end
109
+
110
+ def log(str, warning = false)
111
+ if logger
112
+ if warning
113
+ logger.warn(str)
114
+ else
115
+ logger.info(str)
116
+ end
117
+ end
118
+ end
119
+
120
+ def add_helper(str)
121
+ @helpers[str] ||= true
122
+ end
123
+
124
+ def add_fun(code, base_name)
125
+ unless (name = @funs_reuseable[code])
126
+ name = un(base_name)
127
+ lines = code.split("\n")
128
+ unless lines.shift =~ /^\s*static / # first line needs static
129
+ raise Ruby2CExtError::Bug, "trying to add a non static function"
130
+ end
131
+ if lines.grep(/^\s*static /).empty? # only reuseably without static variables
132
+ @funs_reuseable[code] = name
133
+ end
134
+ unless code.sub!("FUNNAME", name)
135
+ raise Ruby2CExtError::Bug, "trying to add a function without FUNNAME"
136
+ end
137
+ @funs << code
138
+ end
139
+ name
140
+ end
141
+
142
+ def add_plugin(plugin_class, *args)
143
+ @plugins << plugin_class.new(self, *args)
144
+ end
145
+
146
+ def add_plugins(options)
147
+ if options[:warnings]
148
+ require "ruby2cext/plugins/warnings"
149
+ add_plugin(Plugins::Warnings)
150
+ end
151
+ if (opt = options[:optimizations])
152
+ if opt == :all
153
+ opt = {
154
+ :const_cache=>true, :case_optimize=>true,
155
+ :builtin_methods=>true, :inline_methods=>true
156
+ }
157
+ end
158
+ if opt[:const_cache]
159
+ require "ruby2cext/plugins/const_cache"
160
+ add_plugin(Plugins::ConstCache)
161
+ end
162
+ if opt[:case_optimize]
163
+ require "ruby2cext/plugins/case_optimize"
164
+ add_plugin(Plugins::CaseOptimize)
165
+ end
166
+ if opt[:inline_methods]
167
+ require "ruby2cext/plugins/inline_methods"
168
+ add_plugin(Plugins::InlineMethods)
169
+ end
170
+ if (builtins = opt[:builtin_methods])
171
+ require "ruby2cext/plugins/builtin_methods"
172
+ if Array === builtins
173
+ builtins = builtins.map { |b| b.to_s.to_sym } # allow symbols, strings and the actual classes to work
174
+ else
175
+ builtins = Plugins::BuiltinMethods::SUPPORTED_BUILTINS
176
+ end
177
+ add_plugin(Plugins::BuiltinMethods, builtins)
178
+ end
179
+ end
180
+ if (ri_args = options[:require_include])
181
+ require "ruby2cext/plugins/require_include"
182
+ unless Array === ri_args.first
183
+ ri_args = [ri_args] # to allow just an array of include paths to also work
184
+ end
185
+ add_plugin(Plugins::RequireInclude, *ri_args)
186
+ end
187
+ end
188
+
189
+ # preprocessors can be added by plugins. preprocessors are procs that
190
+ # take two arguments: the current cfun and the node (tree) to
191
+ # preprocess (which will have type node_type)
192
+ #
193
+ # The proc can either return a (modified) node (tree) or string. If a
194
+ # node (tree) is returned then that will be translated as usual, if a
195
+ # string is returned, that string will be the result
196
+ #
197
+ # Example, a preprocessor that replaces 23 with 42:
198
+ # add_preprocessor(:lit) { |cfun, node|
199
+ # node.last[:lit] == 23 ? [:lit, {:lit=>42}] : node
200
+ # }
201
+ #
202
+ # Another way to do the same:
203
+ # add_preprocessor(:lit) { |cfun, node|
204
+ # node.last[:lit] == 23 ? cfun.comp_lit(:lit=>42) : node
205
+ # }
206
+ #
207
+ # If multiple preprocessors are added for the same node type then they
208
+ # will be called after each other with the result of the previous one
209
+ # unless it is a string, then the following preprocessors are ignored
210
+ def add_preprocessor(node_type, &pp_proc)
211
+ (@preprocessors[node_type] ||= []) << pp_proc
212
+ end
213
+
214
+ def preprocessors_for(node_type)
215
+ @preprocessors[node_type]
216
+ end
217
+
218
+ conf = ::Config::CONFIG
219
+ cflags = [conf["CCDLFLAGS"], conf["CFLAGS"], conf["ARCH_FLAG"]].join(" ")
220
+ COMPILE_COMMAND = "#{conf["LDSHARED"]} #{cflags} -I . -I #{conf["archdir"]}"
221
+ DLEXT = conf["DLEXT"]
222
+
223
+ # compiles a C file using the compiler from rbconfig
224
+ def self.compile_c_file_to_dllib(c_file_name, logger = nil)
225
+ unless c_file_name =~ /\.c\z/
226
+ raise Ruby2CExtError, "#{c_file_name} is no C file"
227
+ end
228
+ dl_name = c_file_name.sub(/c\z/, DLEXT)
229
+ cmd = "#{COMPILE_COMMAND} -o #{dl_name} #{c_file_name}"
230
+ if RUBY_PLATFORM =~ /mswin32/
231
+ cmd << " -link /INCREMENTAL:no /EXPORT:Init_#{File.basename(c_file_name, ".c")}"
232
+ end
233
+ logger.info(cmd) if logger
234
+ unless system(cmd) # run it
235
+ raise Ruby2CExtError, "error while executing '#{cmd}'"
236
+ end
237
+ dl_name
238
+ end
239
+
240
+ end
241
+
242
+ end
@@ -0,0 +1,15 @@
1
+
2
+ module Ruby2CExtension
3
+
4
+ class Ruby2CExtError < StandardError
5
+ class NotSupported < self
6
+ end
7
+
8
+ class Bug < self
9
+ def initialize(msg)
10
+ super("BUG! #{msg}")
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,129 @@
1
+
2
+ require "rubynode"
3
+ require "digest/sha1"
4
+ require "ruby2cext/compiler"
5
+ require "ruby2cext/error"
6
+ require "ruby2cext/version"
7
+
8
+ module Ruby2CExtension
9
+
10
+ class Eval2C
11
+
12
+ attr_reader :path, :prefix, :plugins, :logger, :force_recompile
13
+
14
+ DEFAULT_PATH = File.join(File.expand_path(ENV["HOME"] || ENV["USERPROFILE"] || ENV["HOMEPATH"] || "."), ".ruby2cext")
15
+
16
+ def initialize(options = {})
17
+ unless (@path = options[:path])
18
+ @path = DEFAULT_PATH
19
+ Dir.mkdir(@path, 0700) unless File.exists?(@path)
20
+ end
21
+ @path = File.expand_path(@path)
22
+ unless File.directory?(@path)
23
+ raise Ruby2CExtError, "#{@path} is no directory"
24
+ end
25
+ unless File.stat(@path).mode & 022 == 0 # no writing for group and others
26
+ warn "Ruby2CExtension::Eval2C warning: #{@path} is insecure"
27
+ end
28
+ @prefix = options[:prefix] || "eval2c"
29
+ @plugins = options[:plugins] || {:optimizations => :all}
30
+ @logger = options[:logger]
31
+ @force_recompile = options[:force_recompile]
32
+ @done = {}
33
+ @digest_extra = Ruby2CExtension::FULL_VERSION_STRING + @plugins.inspect.split(//).sort.join("")
34
+ end
35
+
36
+ private
37
+
38
+ def compile_helper(digest_str)
39
+ name = "#{prefix}_#{Digest::SHA1.hexdigest(digest_str + @digest_extra)}"
40
+ gvar_name = "$__#{name}__"
41
+ dl_filename = File.join(path, "#{name}.#{Compiler::DLEXT}")
42
+ if !File.exists?(dl_filename) || (force_recompile && !@done[name])
43
+ c = Compiler.new(name, logger)
44
+ c.add_plugins(plugins)
45
+ yield c, name, gvar_name
46
+ c_filename = File.join(path, "#{name}.c")
47
+ File.open(c_filename, "w") { |f| f.puts(c.to_c_code) }
48
+ unless Compiler.compile_c_file_to_dllib(c_filename, logger) == dl_filename
49
+ raise Ruby2CExtError::Bug, "unexpected return value from compile_c_file_to_dllib"
50
+ end
51
+ @done[name] = true
52
+ end
53
+ require dl_filename
54
+ eval(gvar_name) # return the proc
55
+ end
56
+
57
+ public
58
+
59
+ def compile_to_proc(code_str)
60
+ compile_helper(code_str) { |compiler, name, gvar_name|
61
+ compiler.add_rb_file("#{gvar_name} = proc { #{code_str} }", name)
62
+ }
63
+ end
64
+
65
+ def module_eval(mod, code_str)
66
+ mod.module_eval(&compile_to_proc(code_str))
67
+ end
68
+ alias :class_eval :module_eval
69
+
70
+ def instance_eval(object, code_str)
71
+ object.instance_eval(&compile_to_proc(code_str))
72
+ end
73
+
74
+ def toplevel_eval(code_str)
75
+ compile_to_proc(code_str).call
76
+ end
77
+
78
+ def compile_methods(mod, *methods)
79
+ methods = methods.map { |m| m.to_sym }.uniq
80
+ defs = methods.map { |m|
81
+ bnode = mod.instance_method(m).body_node
82
+ unless bnode.type == :scope
83
+ raise Ruby2CExtError, "the method #{m} cannot be compiled, only methods defined using def can be compiled"
84
+ end
85
+ [:defn, {:mid => m, :defn => bnode.transform(Compiler::NODE_TRANSFORM_OPTIONS)}]
86
+ }
87
+ node_tree = [:scope, {:next => [:gasgn, {:vid => :$test, :value =>
88
+ [:iter, {
89
+ :var => false,
90
+ :iter => [:fcall, {:args => false, :mid => :proc}],
91
+ :body => [:block, defs]
92
+ }]
93
+ }]}]
94
+ # save current visibility of the methods
95
+ publ_methods = mod.public_instance_methods.map { |m| m.to_sym } & methods
96
+ prot_methods = mod.protected_instance_methods.map { |m| m.to_sym } & methods
97
+ priv_methods = mod.private_instance_methods.map { |m| m.to_sym } & methods
98
+ # compile to test if the methods don't need a cref and to get a string for the hash
99
+ c = Compiler.new("test")
100
+ c.add_toplevel(c.compile_toplevel_function(node_tree))
101
+ test_code = c.to_c_code(nil) # no time_stamp
102
+ # don't allow methods that need a cref, because the compiled version would get a different cref
103
+ if test_code =~ /^static void def_only_once/ # a bit hackish ...
104
+ raise Ruby2CExtError, "the method(s) cannot be compiled, because at least one needs a cref"
105
+ end
106
+ # compile the proc
107
+ def_proc = compile_helper(test_code) { |compiler, name, gvar_name|
108
+ node_tree.last[:next].last[:vid] = gvar_name.to_sym
109
+ compiler.add_toplevel(compiler.compile_toplevel_function(node_tree))
110
+ }
111
+ # try to remove all the methods
112
+ mod.module_eval {
113
+ methods.each { |m|
114
+ remove_method(m) rescue nil
115
+ }
116
+ }
117
+ # add the compiled methods
118
+ mod.module_eval &def_proc
119
+ # restore original visibility
120
+ mod.module_eval {
121
+ public *publ_methods unless publ_methods.empty?
122
+ protected *prot_methods unless prot_methods.empty?
123
+ private *priv_methods unless priv_methods.empty?
124
+ }
125
+ end
126
+
127
+ end
128
+
129
+ end
@@ -0,0 +1,36 @@
1
+
2
+ require "rubynode"
3
+
4
+ module Ruby2CExtension
5
+
6
+ # not really a parser, uses rubynode
7
+ module Parser
8
+ def self.parse_string(str, file_name = "(parse)")
9
+ res = {}
10
+ # for the first parsing use original str, because it doesn't matter
11
+ # for BEGIN stuff and we get better exceptions this way.
12
+ if (tmp = str.parse_begin_to_nodes(file_name, 1))
13
+ res[:begin] = tmp
14
+ end
15
+ # now wrap str in a class scope and strip the class node of
16
+ # afterwards, to get a clean scope in the result. src should
17
+ # not have syntax errors if str didn't.
18
+ src = "class Object\n#{str}\nend"
19
+ begin
20
+ old_verb = $VERBOSE
21
+ # turn warnings of here to avoid the repetition of parse warnings
22
+ $VERBOSE = nil
23
+ if (tmp = src.parse_to_nodes(file_name, 0))
24
+ tmp = tmp.nd_next.nd_body
25
+ if tmp.type == :scope && tmp.nd_next
26
+ res[:tree] = tmp
27
+ end
28
+ end
29
+ ensure
30
+ $VERBOSE = old_verb
31
+ end
32
+ res
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,24 @@
1
+
2
+ module Ruby2CExtension
3
+
4
+ class Plugin
5
+ attr_reader :compiler
6
+
7
+ def initialize(compiler)
8
+ @compiler = compiler
9
+ end
10
+
11
+ # C code returned by this method will be inserted into the final C file
12
+ # between the helpers and the C functions
13
+ def global_c_code
14
+ nil
15
+ end
16
+
17
+ # C code returned by this method will be inserted into the Init_*()
18
+ # function before the calling of the toplevel scopes
19
+ def init_c_code
20
+ nil
21
+ end
22
+ end
23
+
24
+ end