Module: Yadriggy::C

Defined in:
lib/yadriggy/c.rb,
lib/yadriggy/c/c.rb,
lib/yadriggy/c/ffi.rb,
lib/yadriggy/c/ctype.rb,
lib/yadriggy/c/config.rb,
lib/yadriggy/c/opencl.rb,
lib/yadriggy/c/codegen.rb,
lib/yadriggy/c/program.rb,
lib/yadriggy/c/ctypecheck.rb

Overview

C language embedded in Ruby

Defined Under Namespace

Modules: CFI, CType, Config Classes: ArrayType, BuildError, ClangTypeChecker, CodeGen, FFIArray, Float32Array, FloatArray, ForeignMethodType, IntArray, NativeMethodType, OclCodeGen, OclTypeChecker, Program, WithReturnType

Class Method Summary collapse

Class Method Details

.attach(module_obj, func_names, method_types, lib_name, dir = './') ⇒ Module

Attaches functions to a module. The functions are retrieved from the library generated by compiling Ruby methods.

Parameters:

  • module_obj (Module)

    the module object.

  • func_names (Array<String>)

    the function names in C.

  • method_types (Array<Type>)

    the types of the original methods.

  • lib_name (String)

    the library name.

  • dir (String) (defaults to: './')

    the directory where the library is found.

Returns:

  • (Module)

    the module object ‘module_obj`.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/yadriggy/c/ffi.rb', line 143

def self.attach(module_obj, func_names, method_types, lib_name, dir='./')
  module_obj.module_eval('extend FFI::Library')
  module_obj.ffi_lib("#{dir}lib#{lib_name}#{Config::LibExtension}")
  func_names.each_with_index do |name,i|
    mtype = method_types[i]
    module_obj.attach_function((name + '__org').to_sym,
                               name.to_sym,
                               CFI::param_types(mtype),
                               CFI::return_type(mtype))
    ivk_name = invoker_name(mtype)
    module_obj.module_eval "      def self.\#{name}(*args)\n        Yadriggy::C::invoke\#{ivk_name}(self, :\#{name}__org, args)\n      end\n    code\n  end\n  module_obj\nend\n", __FILE__, __LINE__

.attach_funcs(pub_methods, checker, gen, module_name, lib_name, dir) ⇒ Pair<Module,Array<String>>

Returns the module where the methods are attached. The second element is method names.

Returns:

  • (Pair<Module,Array<String>>)

    the module where the methods are attached. The second element is method names.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/yadriggy/c/c.rb', line 191

def self.attach_funcs(pub_methods, checker, gen, module_name,
                      lib_name, dir)
  func_names = pub_methods.map { |ast| gen.c_function_name(ast.tree) }
  func_types = pub_methods.map { |ast| checker.type(ast.tree) }

  func_names, func_types = gen.expand_functions(func_names, func_types)

  unless module_name.nil?
    make_attach_file(module_name, func_names, func_types,
                     lib_name, dir)
  end

  [attach(Module.new, func_names, func_types, lib_name, dir),
    func_names]
end

.compile(obj, lib_name = nil, dir = Config::WorkDir, module_name = nil) ⇒ Module

Compiles methods into binary code.

Parameters:

  • obj (Proc|Method|UnboundMethod|Object)

    the exposed method or a block. If ‘obj` is neither a method or a block, all the public methods available on `obj` are exposed. The methods invoked by the exposed methods are also compiled.

  • lib_name (String) (defaults to: nil)

    the library name.

  • dir (String) (defaults to: Config::WorkDir)

    the directory name.

  • module_name (String) (defaults to: nil)

    the module name where the exposed methods are attached when the generated Ruby script is executed. If ‘method_name` is nil, no Ruby script is generated.

Returns:

  • (Module)

    the module object where the exposed methods are attached. It does not have a name.



92
93
94
95
96
# File 'lib/yadriggy/c/c.rb', line 92

def self.compile(obj, lib_name=nil, dir=Config::WorkDir, module_name=nil)
  mod, funcs = compile0(obj, lib_name, dir, module_name,
                        ClangTypeChecker, CodeGen)[0]
  mod
end

.compile0(obj, lib_name, dir, module_name, typechecker_class, gen_class) ⇒ Pair<Module,Array<String>>

Returns:

  • (Pair<Module,Array<String>>)


100
101
102
103
104
105
106
107
108
109
110
# File 'lib/yadriggy/c/c.rb', line 100

def self.compile0(obj, lib_name, dir, module_name,
                  typechecker_class, gen_class)
  begin
    compile1(obj, lib_name, dir, module_name,
             typechecker_class, gen_class)
  rescue SyntaxError, CheckError, BuildError => err
    raise err if Yadriggy.debug > 0
    warn err.message
    nil
  end
end

.compile1(obj, lib_name, dir, module_name, typechecker_class, gen_class) ⇒ Pair<Module,Array<String>>

Returns:

  • (Pair<Module,Array<String>>)

Raises:



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/yadriggy/c/c.rb', line 114

def self.compile1(obj, lib_name, dir, module_name,
                  typechecker_class, gen_class)
  lib_name0 = obj.class.name
  method_objs = if obj.is_a?(Proc) || obj.is_a?(Method) ||
                    obj.is_a?(UnboundMethod)
                  lib_name0 += obj.object_id.to_s(16)
                  [obj]
                else
                  obj.public_methods(false).map do |name|
                    obj.method(name)
                  end
                end
  lib_name = lib_name0.gsub('::', '_').downcase if lib_name.nil?

  raise BuildError.new('no methods specified') if method_objs.size < 1

  checker = typechecker_class.new(@syntax)
  pub_methods = compiled_methods(checker, method_objs)

  dir += File::Separator unless dir.end_with?(File::Separator)
  FileUtils.mkdir_p(dir)
  printer = Yadriggy::FilePrinter.new(gen_class.c_src_file(dir, lib_name))
  gen = gen_class.new(printer, checker, pub_methods)

  generate_funcs(pub_methods[0], gen, printer)
  gen.build_lib(lib_name, dir)

  attach_funcs(pub_methods, checker, gen, module_name, lib_name, dir)
end

.compiled_methods(checker, method_objs) ⇒ Array<ASTree>

Returns the ASTs of compiled methods.

Returns:

  • (Array<ASTree>)

    the ASTs of compiled methods.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/yadriggy/c/c.rb', line 146

def self.compiled_methods(checker, method_objs)
  ast = nil
  pub_methods = method_objs.map do |mthd|
    if ast.nil?
      ast = Yadriggy::reify(mthd)
    else
      ast = ast.reify(mthd)
    end

    if ast.nil?
      raise SyntaxError.new(
        "cannot locate the source: #{mthd.name.to_s} in #{mthd.receiver.class}")
    end

    @syntax.raise_error unless @syntax.check(ast.tree)
    checker.typecheck(ast.tree)
    ast
  end

  return pub_methods
end

.generate_funcs(ast, gen, printer) ⇒ Object

Raises:



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/yadriggy/c/c.rb', line 169

def self.generate_funcs(ast, gen, printer)
  gen.name_global_variables
  gen.headers
  gen.variable_declarations
  ast.astrees.each do |e|
    gen.prototype(e.tree)
  end
  gen.preamble
  ast.astrees.each do |e|
    printer << :nl
    gen.c_function(e.tree)
  end

  printer.output
  printer.close

  raise BuildError.new(gen.error_messages) if gen.errors?
end

.invoke(module_obj, func_name, args) ⇒ Object



235
236
237
238
239
240
241
242
243
244
# File 'lib/yadriggy/c/ffi.rb', line 235

def self.invoke(module_obj, func_name, args)
  args2 = args.map do |e|
    if e.is_a?(IntArray) || e.is_a?(FloatArray) || e.is_a?(Float32Array)
      e.memory_pointer
    else
      e
    end
  end
  module_obj.method(func_name).call(*args2)
end

.invoke_float(module_obj, func_name, args) ⇒ Object



223
224
225
226
# File 'lib/yadriggy/c/ffi.rb', line 223

def self.invoke_float(module_obj, func_name, args)
  r = invoke(module_obj, func_name, args)
  FloatArray.new(0, r)
end

.invoke_float32(module_obj, func_name, args) ⇒ Object



229
230
231
232
# File 'lib/yadriggy/c/ffi.rb', line 229

def self.invoke_float32(module_obj, func_name, args)
  r = invoke(module_obj, func_name, args)
  Float32Array.new(0, r)
end

.invoke_int(module_obj, func_name, args) ⇒ Object



217
218
219
220
# File 'lib/yadriggy/c/ffi.rb', line 217

def self.invoke_int(module_obj, func_name, args)
  r = invoke(module_obj, func_name, args)
  IntArray.new(0, r)
end

.invoker_name(mtype) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/yadriggy/c/ffi.rb', line 203

def self.invoker_name(mtype)
  case ArrayType.role(MethodType.role(mtype)&.result_type)&.element_type
  when RubyClass::Integer
    '_int'
  when RubyClass::Float
    '_float'
  when CType::Float32Type
    '_float32'
  else
    ''
  end
end

.make_attach_file(module_name, func_names, method_types, lib_name, dir = './') ⇒ String

Generates a Ruby source file. When the file is executed, it retrieves functions from the library generated by compiling Ruby methods, and then it attaches the functions to a specified module.

Parameters:

  • module_name (String)

    the module name.

  • func_names (Array<String>)

    the function names in C.

  • method_types (Array<Type>)

    the types of the original methods.

  • lib_name (String)

    the library name.

  • dir (String) (defaults to: './')

    the directory where the library is found.

Returns:

  • (String)

    the generated file name.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/yadriggy/c/ffi.rb', line 174

def self.make_attach_file(module_name, func_names, method_types,
                          lib_name, dir='./')
  file_name0 = module_name.gsub(/::|\./, '_').downcase
  file_name = "#{dir}#{file_name0}.rb"
  printer = Yadriggy::FilePrinter.new(file_name)
  printer << "require 'yadriggy/c/ffi'" << :nl << :nl
  printer << "module #{module_name} extend FFI::Library" << :nl
  printer << "  self.ffi_lib(\"#{dir}lib#{lib_name}#{Config::LibExtension}\")" << :nl
  func_names.each_with_index do |name,i|
    mtype = method_types[i]
    printer << "  self.attach_function(:\"#{name}__org\", "
    printer <<     ":\"#{name}\", "
    printer <<     "#{CFI::param_types(mtype).to_s}, "
    printer <<     ":#{CFI::return_type(mtype)})"
    printer << :nl

    printer << "  def self.#{name}(*args)" << :nl
    printer << "    Yadriggy::C::invoke#{invoker_name(mtype)}(self, "
    printer <<       ":\"#{name}__org\", args)" << :nl
    printer << '  end' << :nl
  end
  printer << 'end' << :nl

  printer.output
  printer.close
  file_name
end

.ocl_compile(obj, lib_name = nil, dir = Config::WorkDir, module_name = nil) ⇒ Object

Compiles OpenCL methods into binary code.



13
14
15
16
17
18
# File 'lib/yadriggy/c/opencl.rb', line 13

def self.ocl_compile(obj, lib_name=nil, dir=Config::WorkDir,
                     module_name=nil)
  mod, funcs = compile0(obj, lib_name, dir, module_name,
                        OclTypeChecker, OclCodeGen)
  mod
end

.run(lib_name = nil, *args, dir: Config::WorkDir, &block) ⇒ Object

Compiles and runs a block.

Parameters:

  • lib_name (String) (defaults to: nil)

    the library name.

  • dir (String) (defaults to: Config::WorkDir)

    the directory name.

  • args (Object...)

    the arguments to the block.

Returns:

  • (Object)

    the result of running the given block.

Raises:



213
214
215
216
217
218
# File 'lib/yadriggy/c/c.rb', line 213

def self.run(lib_name=nil, *args, dir: Config::WorkDir, &block)
  raise BuildError.new('no block given') if block.nil?
  mod, mths = compile0(block, lib_name, dir, nil,
                       ClangTypeChecker, CodeGen)
  mod.method(mths[0]).call(*args)
end

.syntaxSyntax

Returns the syntax.

Returns:



75
76
77
# File 'lib/yadriggy/c/c.rb', line 75

def self.syntax
  @syntax
end