Class: Ruby2CExtension::Compiler

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby2cext/compiler.rb

Constant Summary collapse

NODE_TRANSFORM_OPTIONS =
{:include_node => true, :keep_newline_nodes => true}
COMPILE_COMMAND =

added -c

"#{conf["LDSHARED"]} #{cflags} -I . -I #{conf["archdir"]} "
DLEXT =

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, logger = nil) ⇒ Compiler

Returns a new instance of Compiler.



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/ruby2cext/compiler.rb', line 23

def initialize(name, logger = nil)
  @name = name
  @logger = logger
  @funs = []
  @funs_reuseable = {}
  @toplevel_funs = []
  @sym_man = Tools::SymbolManager.new
  @global_man = Tools::GlobalManager.new
  @uniq_names = Tools::UniqueNames.new
  @helpers = {}
  @plugins = []
  @preprocessors = {}
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



21
22
23
# File 'lib/ruby2cext/compiler.rb', line 21

def logger
  @logger
end

#nameObject (readonly)

Returns the value of attribute name.



21
22
23
# File 'lib/ruby2cext/compiler.rb', line 21

def name
  @name
end

#pluginsObject (readonly)

Returns the value of attribute plugins.



21
22
23
# File 'lib/ruby2cext/compiler.rb', line 21

def plugins
  @plugins
end

Class Method Details

.compile_c_file_to_dllib(c_file_name, logger = nil) ⇒ Object

compiles a C file using the compiler from rbconfig



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/ruby2cext/compiler.rb', line 287

def self.compile_c_file_to_dllib(c_file_name, logger = nil)
  conf = ::Config::CONFIG
  unless c_file_name =~ /\.c\z/
    raise Ruby2CExtError, "#{c_file_name} is no C file"
  end
  dl_name = c_file_name.sub(/c\z/, DLEXT)
  cmd = "#{COMPILE_COMMAND} -o #{dl_name} #{c_file_name}"
  if RUBY_PLATFORM =~ /mswin32/
    cmd << " -link /INCREMENTAL:no /EXPORT:Init_#{File.basename(c_file_name, ".c")}"
  end
  if RUBY_PLATFORM =~ /mingw/
    cmd << " #{ conf['DLDFLAGS'] } #{ conf['SOLIBS'] }  "
    cmd << " -L#{ conf['libdir'] } #{ conf["LIBRUBYARG_SHARED"] } "
  end
  logger.info(cmd) if logger

  unless system(cmd) # run it
    raise Ruby2CExtError, "error while executing '#{cmd}' #{`cmd`}"
  end
  dl_name
end

.compile_file(file_name, plugins, include_paths, only_c, logger) ⇒ Object

plugins is => :all || [:name1, :name2], also => true or something logger if require ‘logger’; Logger.new include_paths = [] # dirs only_c – pass true if you just want the source, not compiled



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/ruby2cext/compiler.rb', line 41

def Compiler.compile_file(file_name, plugins, include_paths, only_c, logger)
  bn = File.basename(file_name)
  unless bn =~ /\A(.*)\.rb\w?\z/
    raise "#{file_name} is no ruby file"
  end
  name = $1;
  unless name =~ /\A\w+\z/
    raise "'#{name}' is not a valid extension name"
  end
  file_name = File.join(File.dirname(file_name), bn)

  logger.info("reading #{file_name}")
  source_str = IO.read(file_name)

  logger.info("translating #{file_name} to C")
  c = Compiler.new(name, logger)
  unless include_paths.empty?
    plugins = plugins.merge({:require_include => [include_paths, [file_name]]})
  end
  logger.debug("plugins = #{plugins.inspect}")
  c.add_plugins(plugins)
  logger.debug("plugins used: #{c.plugins.map { |pi| pi.class }.inspect}")
  c.add_rb_file(source_str, file_name)
  c_code = c.to_c_code

  c_file_name = File.join(File.dirname(file_name), "#{name}.c")
  logger.info("writing #{c_file_name}")
  File.open(c_file_name, "w") { |f| f.puts(c_code) }

  if only_c
     c_code
  else
    logger.info("compiling #{c_file_name}")
    Compiler.compile_c_file_to_dllib(c_file_name, logger)
  end
end

Instance Method Details

#add_fun(code, base_name) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/ruby2cext/compiler.rb', line 175

def add_fun(code, base_name)
  unless (name = @funs_reuseable[code])
    name = un(base_name)
    lines = code.split("\n")
    unless lines.shift =~ /^\s*static / # first line needs static
      raise Ruby2CExtError::Bug, "trying to add a non static function"
    end
    if lines.grep(/^\s*static /).empty? # only reuseably without static variables
      @funs_reuseable[code] = name
    end
    unless code.sub!("FUNNAME", name)
      raise Ruby2CExtError::Bug, "trying to add a function without FUNNAME"
    end
    @funs << code
  end
  name
end

#add_helper(str) ⇒ Object



171
172
173
# File 'lib/ruby2cext/compiler.rb', line 171

def add_helper(str)
  @helpers[str] ||= true
end

#add_plugin(plugin_class, *args) ⇒ Object



193
194
195
# File 'lib/ruby2cext/compiler.rb', line 193

def add_plugin(plugin_class, *args)
  @plugins << plugin_class.new(self, *args)
end

#add_plugins(options) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/ruby2cext/compiler.rb', line 197

def add_plugins(options)
  if options[:warnings]
    add_plugin(Plugins::Warnings)
  end
  if (opt = options[:optimizations])
    if opt == :all
      opt = {
        :const_cache=>true,
        :case_optimize=>true,
       # :direct_self_call=>true, # also buggy...
       # :inline_builtin=>true, # causes a bug [just itself, too]
        :cache_call=>true,
        :builtin_methods=>true,
        :inline_methods=>true,
        :ivar_cache=>true
      }
    end
    if opt[:const_cache]
      add_plugin(Plugins::ConstCache)
    end
    if opt[:case_optimize]
      add_plugin(Plugins::CaseOptimize)
    end
    if opt[:direct_self_call]
      add_plugin(Plugins::DirectSelfCall)
    end
    if opt[:inline_builtin]
      add_plugin(Plugins::InlineBuiltin)
    end
    if opt[:inline_methods]
      add_plugin(Plugins::InlineMethods)
    end
    if opt[:cache_call]
      add_plugin(Plugins::CacheCall)
    end
    if (builtins = opt[:builtin_methods])
      if Array === builtins
        builtins = builtins.map { |b| b.to_s.to_sym } # allow symbols, strings and the actual classes to work
      else
        builtins = Plugins::BuiltinMethods::SUPPORTED_BUILTINS
      end
      add_plugin(Plugins::BuiltinMethods, builtins)
    end
    if opt[:ivar_cache]
      add_plugin(Plugins::IVarCache)
    end
  end
  if (ri_args = options[:require_include])
    unless Array === ri_args.first
      ri_args = [ri_args] # to allow just an array of include paths to also work
    end
    add_plugin(Plugins::RequireInclude, *ri_args)
  end
end

#add_preprocessor(node_type, &pp_proc) ⇒ Object

preprocessors can be added by plugins. preprocessors are procs that take two arguments: the current cfun and the node (tree) to preprocess (which will have type node_type)

The proc can either return a (modified) node (tree) or string. If a node (tree) is returned then that will be translated as usual, if a string is returned, that string will be the result

Example, a preprocessor that replaces 23 with 42: add_preprocessor(:lit) { |cfun, node|

node.last[:lit] == 23 ? [:lit, {:lit=>42}] : node

}

Another way to do the same: add_preprocessor(:lit) { |cfun, node|

node.last[:lit] == 23 ? cfun.comp_lit(:lit=>42) : node

}

If multiple preprocessors are added for the same node type then they will be called after each other with the result of the previous one unless it is a string, then the following preprocessors are ignored



273
274
275
# File 'lib/ruby2cext/compiler.rb', line 273

def add_preprocessor(node_type, &pp_proc)
  (@preprocessors[node_type] ||= []) << pp_proc
end

#add_rb_file(source_str, file_name) ⇒ Object



141
142
143
144
145
# File 'lib/ruby2cext/compiler.rb', line 141

def add_rb_file(source_str, file_name)
  rb_file_to_toplevel_functions(source_str, file_name).each { |fn|
    add_toplevel(fn)
  }
end

#add_toplevel(function_name) ⇒ Object



111
112
113
# File 'lib/ruby2cext/compiler.rb', line 111

def add_toplevel(function_name)
  @toplevel_funs << function_name
end

#compile_toplevel_function(node_tree, private_vmode = true) ⇒ Object

non destructive: node_tree will not be changed



116
117
118
# File 'lib/ruby2cext/compiler.rb', line 116

def compile_toplevel_function(node_tree, private_vmode = true)
  CFunction::ToplevelScope.compile(self, node_tree, private_vmode)
end

#global_const(str, register_gc = true) ⇒ Object



154
155
156
# File 'lib/ruby2cext/compiler.rb', line 154

def global_const(str, register_gc = true)
  @global_man.get(str, true, register_gc)
end

#global_var(str) ⇒ Object



157
158
159
# File 'lib/ruby2cext/compiler.rb', line 157

def global_var(str)
  @global_man.get(str, false, true)
end

#log(str, warning = false) ⇒ Object



161
162
163
164
165
166
167
168
169
# File 'lib/ruby2cext/compiler.rb', line 161

def log(str, warning = false)
  if logger
    if warning
      logger.warn(str)
    else
      logger.info(str)
    end
  end
end

#preprocessors_for(node_type) ⇒ Object



277
278
279
# File 'lib/ruby2cext/compiler.rb', line 277

def preprocessors_for(node_type)
  @preprocessors[node_type]
end

#rb_file_to_toplevel_functions(source_str, file_name) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/ruby2cext/compiler.rb', line 122

def rb_file_to_toplevel_functions(source_str, file_name)
  res = []
  hash = Parser.parse_string(source_str, file_name)
  # add all BEGIN blocks, if available
  if (beg_tree = hash[:begin])
    beg_tree = beg_tree.transform(NODE_TRANSFORM_OPTIONS)
    if beg_tree.first == :block
      beg_tree.last.each { |s| res << compile_toplevel_function(s, false) }
    else
      res << compile_toplevel_function(beg_tree, false)
    end
  end
  # add toplevel scope
  if (tree = hash[:tree])
    res << compile_toplevel_function(tree.transform(NODE_TRANSFORM_OPTIONS))
  end
  res
end

#sym(sym) ⇒ Object



151
152
153
# File 'lib/ruby2cext/compiler.rb', line 151

def sym(sym)
  @sym_man.get(sym)
end

#to_c_code(time_stamp = Time.now) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/ruby2cext/compiler.rb', line 81

def to_c_code(time_stamp = Time.now)
  plugins_global = @plugins.map { |plugin| plugin.global_c_code }
  plugins_init = @plugins.map { |plugin| plugin.init_c_code }
  res = [
    "/* generated by #{FULL_VERSION_STRING} on #{time_stamp} */",
    "#include <ruby.h>",
    "#include <node.h>",
    "#include <env.h>",
    "#include <st.h>",
    "extern VALUE ruby_top_self;",
    "static VALUE org_ruby_top_self;",
    @sym_man.to_c_code,
    @global_man.to_c_code,
  ]
  res.concat(@helpers.keys.sort)
  res.concat(plugins_global)
  res.concat(@funs)
  res << "void Init_#{@name}() {"
  res << "org_ruby_top_self = ruby_top_self;"
  # just to be sure
  res << "rb_global_variable(&org_ruby_top_self);"
  res << "init_syms();"
  res << "init_globals();"
  res << "NODE *cref = rb_node_newnode(NODE_CREF, rb_cObject, 0, 0);"
  res.concat(plugins_init)
  @toplevel_funs.each { |f| res << "#{f}(ruby_top_self, cref);" }
  res << "}"
  res.join("\n").split("\n").map { |l| l.strip }.reject { |l| l.empty? }.join("\n")
end

#un(str) ⇒ Object

uniq name



148
149
150
# File 'lib/ruby2cext/compiler.rb', line 148

def un(str)
  @uniq_names.get(str)
end