class Metasm::RubyStaticCompiler

a ruby2c C generator for use in the any ruby interpreter (generates C suitable for use as a standard Ruby extension)

Public Class Methods

compile(klass, *methlist) click to toggle source

add a new ruby function to the current @cp

# File samples/dynamic_ruby.rb, line 1603
def self.compile(klass, *methlist)
        @rcp ||= new
        methlist.each { |meth|
                ast = RubyHack.read_method_ast(klass, meth)
                @rcp.compile(ast, klass, meth)
        }
        self
end
compile_singleton(klass, *methlist) click to toggle source
# File samples/dynamic_ruby.rb, line 1612
def self.compile_singleton(klass, *methlist)
        @rcp ||= new
        methlist.each { |meth|
                ast = RubyHack.read_singleton_method_ast(klass, meth)
                @rcp.compile(ast, klass, meth, true)
        }
        self
end
dump() click to toggle source
# File samples/dynamic_ruby.rb, line 1621
        def self.dump
                <<EOS + @rcp.cp.dump_definition('Init_compiledruby')
#ifdef __ELF__
asm .pt_gnu_stack rw;
#endif
EOS
        end
new(cp=nil) click to toggle source
Calls superclass method Metasm::RubyLiveCompiler.new
# File samples/dynamic_ruby.rb, line 1633
        def initialize(cp=nil)
                super(cp)

                @cp.parse <<EOS
// static VALUE method(VALUE self, VALUE arg0, VALUE arg1) { return (VALUE)0; }
// static VALUE const_Lol;
static void do_init_once(void) {
        // const_Lol = rb_const_get(*rb_cObject, rb_intern("Lol"));
        // rb_define_method(const_Lol, "method", method, 2);
}

int Init_compiledruby(void) __attribute__((export)) {
        // use a separate func to avoid having to append statements before the 'return'
        do_init_once();
        return 0;
}
EOS
        end

Public Instance Methods

compile(ast, klass, method, singleton=false) click to toggle source
Calls superclass method Metasm::RubyLiveCompiler.compile
# File samples/dynamic_ruby.rb, line 1657
def compile(ast, klass, method, singleton=false)
        @compiled_func_cache ||= {}

        mname = super(ast, klass, method, singleton)
        return if not mname

        @compiled_func_cache[[klass, method.to_s, singleton]] = @cur_cfunc

        cls = rb_const(nil, klass)

        init.statements << fcall("rb_define#{'_singleton' if singleton}_method", cls, method.to_s, @cur_cfunc, method_arity)

        mname
end
declare_newtopvar(name, initializer, type=value) click to toggle source
# File samples/dynamic_ruby.rb, line 1672
def declare_newtopvar(name, initializer, type=value)
        v = C::Variable.new(name, type)
        v.storage = :static
        @cp.toplevel.symbol[v.name] = v
        pos = @cp.toplevel.statements.index @cp.toplevel.statements.find { |st|
                st.kind_of? C::Declaration and st.var.type.kind_of? C::Function and st.var.initializer
        } || -1
        @cp.toplevel.statements.insert pos, C::Declaration.new(v)

        if initializer
                pos = -1
                if name =~ /^intern_/
                        pos = 0
                        init.statements.each { |st|
                                break unless st.kind_of? C::CExpression and st.op == :'=' and st.lexpr.kind_of? C::Variable and st.lexpr.name < name
                                pos += 1
                        }
                end
                init.statements.insert(pos, C::CExpression[v, :'=', initializer])
        end

        v
end
dump(m="Init_compiledruby") click to toggle source
# File samples/dynamic_ruby.rb, line 1629
def dump(m="Init_compiledruby")
        m ? @cp.dump_definition(m, 'do_init_once') : @cp.to_s
end
get_cfuncptr(klass, method, singleton=false) click to toggle source
# File samples/dynamic_ruby.rb, line 1735
def get_cfuncptr(klass, method, singleton=false)
        # is it a func we have in the current cparser ?
        if ptr = @compiled_func_cache[[klass, method.to_s, singleton]]
                return ptr
        end

        # check if it's a C or ruby func in the current interpreter
        cls = singleton ? (class << klass ; self ; end) : klass
        ptr = RubyHack.get_method_node_ptr(cls, method)
        return if ptr == 0
        ftype = RubyHack::NODETYPE[(RubyHack.memory_read_int(ptr) >> 11) & 0xff]
        return if ftype != :cfunc

        # ok, so assume it will be the same next time
        n = escape_varname "fptr_#{klass.name}#{singleton ? '.' : '#'}#{method}".gsub('::', '_')
        if not v = @cp.toplevel.symbol[n]
                v = get_cfuncptr_dyn(klass, method, singleton, n)
        end

        v
end
get_cfuncptr_dyn(klass, method, singleton, n) click to toggle source
# File samples/dynamic_ruby.rb, line 1757
def get_cfuncptr_dyn(klass, method, singleton, n)
        arity = singleton ? klass.method(method).arity : klass.instance_method(method).arity
        fproto = C::Function.new(value, [])
        case arity
        when -1; fproto.args << C::Variable.new(nil, C::BaseType.new(:int)) << C::Variable.new(nil, C::Pointer.new(value)) << C::Variable.new(nil, value)
        when -2; fproto.args << C::Variable.new(nil, value) << C::Variable.new(nil, value)
        else (arity+1).times { fproto.args << C::Variable.new(nil, value) }
        end

        if not ptr = init.symbol['ptr']
                ptr = C::Variable.new('ptr', C::Pointer.new(C::BaseType.new(:int)))
                init.symbol[ptr.name] = ptr
                init.statements << C::Declaration.new(ptr)
        end

        cls = rb_const(nil, klass)
        cls = fcall('rb_singleton_class', cls) if singleton
        init.statements << C::CExpression[ptr, :'=', fcall('rb_method_node', cls, rb_intern(method))]

        # dynamically recheck that klass#method is a :cfunc
        cnd = C::CExpression[[:'!', ptr], :'||', [[[[ptr, :'[]', [0]], :>>, [11]], :&, [0xff]], :'!=', [RubyHack::NODETYPE.index(:cfunc)]]]
        init.statements << C::If.new(cnd, rb_raise("CFunc expected at #{klass}#{singleton ? '.' : '#'}#{method}"), nil)

        vi = C::CExpression[[ptr, :'[]', [1]], C::Pointer.new(fproto)]
        declare_newtopvar(n, vi, C::Pointer.new(fproto))
end
init() click to toggle source

returns the 'do_init_once' function body

# File samples/dynamic_ruby.rb, line 1653
def init
        @cp.toplevel.symbol['do_init_once'].initializer
end
rb_const(constname, owner = resolve_const_owner(constname)) click to toggle source

#rb_const 'FOO', Bar::Baz ==>

const_Bar = rb_const_get(rb_cObject, rb_intern("Bar"));
const_Bar_Baz = rb_const_get(const_Bar, rb_intern("Baz"));
const_Bar_Baz_FOO = rb_const_get(const_Bar_Baz, rb_intern("FOO"));

use #rb_const(nil, class) to get a pointer to a class/module

# File samples/dynamic_ruby.rb, line 1706
def rb_const(constname, owner = resolve_const_owner(constname))
        raise Fail, "no dynamic constant resolution #{constname}" if not owner

        @const_value ||= { [::Object, 'Object'] => rb_global('rb_cObject') }

        k = ::Object
        v = nil
        cname = owner.name
        cname += '::' + constname if constname
        cname.split('::').each { |n|
                kk = k.const_get(n)
                if not v = @const_value[[k, n]]
                        # class A ; end ; B = A  => B.name => 'A'
                        vn = "const_#{escape_varname((k.name + '::' + n).sub(/^Object::/, '').gsub('::', '_'))}"
                        vi = fcall('rb_const_get', rb_const(nil, k), fcall('rb_intern', n))
                        v = declare_newtopvar(vn, vi)
                        # n wont be reused, so do not alloc a global intern_#{n} for this
                        @const_value[[k, n]] = v
                end
                k = kk
        }
        v
end
rb_funcall(recv, meth, *args) click to toggle source

dynamic trace of all #rb_funcall made from our module

# File samples/dynamic_ruby.rb, line 1786
        def rb_funcall(recv, meth, *args)
                if not defined? @rb_fcid
                        @cp.parse <<EOS
int atexit(void(*)(void));
int printf(char*, ...);

static unsigned rb_fcid_max = 1;
static unsigned rb_fcntr[1];

static void rb_fcstat(void)
{
        unsigned i;
        for (i=0 ; i<rb_fcid_max ; ++i)
                if (rb_fcntr[i])
                        printf("%u %u\\n", i, rb_fcntr[i]);
}
EOS
                        @rb_fcid = -1
                        @rb_fcntr = @cp.toplevel.symbol['rb_fcntr']
                        @rb_fcid_max = @cp.toplevel.symbol['rb_fcid_max']
                        init.statements << fcall('atexit', @cp.toplevel.symbol['rb_fcstat'])
                end
                @rb_fcid += 1
                @rb_fcid_max.initializer = C::CExpression[[@rb_fcid+1], @rb_fcid_max.type]
                @rb_fcntr.type.length = @rb_fcid+1

                ctr = C::CExpression[:'++', [@rb_fcntr, :'[]', [@rb_fcid]]]
                C::CExpression[ctr, :',', super(recv, meth, *args)]
        end
rb_global(cname) click to toggle source

TODO remove this when the C compiler is fixed

# File samples/dynamic_ruby.rb, line 1731
def rb_global(cname)
        C::CExpression[:*, @cp.toplevel.symbol[cname]]
end
rb_intern(sym) click to toggle source
# File samples/dynamic_ruby.rb, line 1696
def rb_intern(sym)
        n = escape_varname("intern_#{sym}")
        @cp.toplevel.symbol[n] || declare_newtopvar(n, fcall('rb_intern', sym.to_s), C::BaseType.new(:int, :unsigned))
end