ruby2cext 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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