class Metasm::RubyLiveCompiler
a ruby2c C generator for use in the current ruby interpreter generates C suitable for shellcode compilation & insertion in the current interpreter has hardcoded addresses etc
Constants
- RUBY_H
Attributes
Public Class Methods
# File samples/dynamic_ruby.rb, line 276 def self.compile(klass, *methlist) @rcp ||= new methlist.each { |meth| ast = RubyHack.read_method_ast(klass, meth) n = @rcp.compile(ast, klass, meth) next if not n raw = RubyHack.compile_c(@rcp.cp.dump_definition(n)).encoded RubyHack.set_method_binary(klass, meth, raw) } self end
# File samples/dynamic_ruby.rb, line 296 def initialize(cp=nil) @cp = cp || DynLdr.host_cpu.new_cparser @cp.parse RUBY_H @iter_break = nil @optim_hint = @@optim_hint.dup end
# File samples/dynamic_ruby.rb, line 293 def self.optim_hint; @@optim_hint; end
Public Instance Methods
check if the var falls in an ::optim_hint, if so generate only selected code optim is a hash varclass (keyof @optim_hint) => c_stmt optim key can also be a C::Stmt that is used in the If clause if optim == optim, you can omit the latter must have an 'other' key that is calls the generic ruby method
# File samples/dynamic_ruby.rb, line 1332 def add_optimized_statement(scope, varid, varc, optim={}) cat = @optim_hint[varid] cat = 'ary' if cat == 'ary_bnd' and not optim['ary_bnd'] if not st = optim[cat] st = optim['other'] if not cat and optim.keys.all? { |k| k.kind_of? String } # no need to cascade if we have a hash and can optim ary only optim.each { |i, s| case i when 'ary'; st = C::If.new(rb_test_class_ary(varc), s, st) when 'hash'; st = C::If.new(rb_test_class_hash(varc), s, st) when 'string'; st = C::If.new(rb_test_class_string(varc), s, st) when 'other'; # already done as default case when 'fixnum'; # add test last when C::Statement; st = C::If.new(i, s, st) end } if fs = optim['fixnum'] # first test to perform (fast path) st = C::If.new(C::CExpression[varc, :&, 1], fs, st) end end end scope.statements << st end
the recursive AST to C compiler may append C statements to scope returns the C::CExpr holding the VALUE of the current ruby statement want_value is an optionnal hint as to the returned VALUE is needed or not if want_value is a C::Variable, the statements should try to populate this var instead of some random tmp var eg to simplify :if encoding unless we have 'foo = if 42;..'
# File samples/dynamic_ruby.rb, line 812 def ast_to_c(ast, scope, want_value = true) ret = case ast.to_a[0] when :block if ast[1] ast[1..-2].each { |a| ast_to_c(a, scope, false) } ast_to_c(ast.last, scope, want_value) end when :lvar local(ast[1]) when :lasgn if scope == @scope l = local(ast[1], :none) else # w = 4 if false ; p w => should be nil l = local(ast[1]) end st = ast_to_c(ast[2], scope, l) scope.statements << C::CExpression[l, :'=', st] if st != l l when :dvar dvar(ast[1]) when :dasgn_curr l = dvar(ast[1]) st = ast_to_c(ast[2], scope, l) scope.statements << C::CExpression[l, :'=', st] if st != l l when :ivar fcall('rb_ivar_get', rb_self, rb_intern(ast[1])) when :iasgn if want_value tmp = get_new_tmp_var("ivar_#{ast[1]}", want_value) scope.statements << C::CExpression[tmp, :'=', ast_to_c(ast[2], scope)] scope.statements << fcall('rb_ivar_set', rb_self, rb_intern(ast[1]), tmp) tmp else scope.statements << fcall('rb_ivar_set', rb_self, rb_intern(ast[1]), ast_to_c(ast[2], scope)) end when :cvar fcall('rb_cvar_get', rb_selfclass, rb_intern(ast[1])) when :cvasgn if want_value tmp = get_new_tmp_var("cvar_#{ast[1]}", want_value) scope.statements << C::CExpression[tmp, :'=', ast_to_c(ast[2], scope)] scope.statements << fcall('rb_cvar_set', rb_selfclass, rb_intern(ast[1]), tmp, rb_false) tmp else scope.statements << fcall('rb_cvar_set', rb_selfclass, rb_intern(ast[1]), ast_to_c(ast[2], scope), rb_false) end when :gvar fcall('rb_gv_get', ast[1]) when :gasgn if want_value tmp = get_new_tmp_var("gvar_#{ast[1]}", want_value) scope.statements << C::CExpression[tmp, :'=', ast_to_c(ast[2], scope)] scope.statements << fcall('rb_gv_set', ast[1], tmp) tmp else scope.statements << fcall('rb_gv_set', ast[1], ast_to_c(ast[2], scope)) end when :attrasgn # foo.bar= 42 (same as :call, except for return value) recv = ast_to_c(ast[1], scope) raise Fail, "unsupported #{ast.inspect}" if not ast[3] or ast[3][0] != :array if ast[3].length != 2 if ast[2] != '[]=' or ast[3].length != 3 raise Fail, "unsupported #{ast.inspect}" end # foo[4] = 2 idx = ast_to_c(ast[3][1], scope) end arg = ast_to_c(ast[3].last, scope) if want_value tmp = get_new_tmp_var('call', want_value) scope.statements << C::CExpression[tmp, :'=', arg] end if idx scope.statements << rb_funcall(recv, ast[2], idx, arg) else scope.statements << rb_funcall(recv, ast[2], arg) end tmp when :rb2cvar # hax, used in vararg parsing get_var(ast[1]) when :rb2cstmt ast[1] when :block_arg local(ast[3], fcall('rb_block_proc')) when :lit case ast[1] when Symbol # XXX ID2SYM C::CExpression[[rb_intern(ast[1].to_s), :<<, 8], :|, 0xe] when Range fcall('rb_range_new', ast[1].begin.object_id, ast[1].end.object_id, ast[1].exclude_end? ? 0 : 1) else # true/false/nil/fixnum ast[1].object_id end when :self rb_self when :str fcall('rb_str_new2', ast[1]) when :array tmp = get_new_tmp_var('ary', want_value) scope.statements << C::CExpression[tmp, :'=', fcall('rb_ary_new')] ast[1..-1].each { |e| scope.statements << fcall('rb_ary_push', tmp, ast_to_c(e, scope)) } tmp when :hash raise Fail, "bad #{ast.inspect}" if ast[1][0] != :array tmp = get_new_tmp_var('hash', want_value) scope.statements << C::CExpression[tmp, :'=', fcall('rb_hash_new')] ki = nil ast[1][1..-1].each { |k| if not ki ki = k else scope.statements << fcall('rb_hash_aset', tmp, ast_to_c(ki, scope), ast_to_c(k, scope)) ki = nil end } tmp when :iter if v = optimize_iter(ast, scope, want_value) return v end # for full support of :iter, we need access to the interpreter's ruby_block private global variable in eval.c # we can find it by analysing rb_block_given_p, but this won't work with a static precompiled rubyhack... # even with access to ruby_block, there we would need to redo PUSH_BLOCK, create a temporary dvar list, # handle [:break, lol], and do all the stack magic reused in rb_yield (probably incl setjmp etc) raise Fail, "unsupported iter #{ast[3].inspect} { | #{ast[1].inspect} | #{ast[2].inspect} }" when :call, :vcall, :fcall if v = optimize_call(ast, scope, want_value) return v end recv = ((ast[0] == :call) ? ast_to_c(ast[1], scope) : rb_self) if not ast[3] f = rb_funcall(recv, ast[2]) elsif ast[3][0] == :array args = ast[3][1..-1].map { |a| ast_to_c(a, scope) } f = rb_funcall(recv, ast[2], *args) elsif ast[3][0] == :splat args = ast_to_c(ast[3], scope) if not args.kind_of? C::Variable tmp = get_new_tmp_var('args', want_value) scope.statements << C::CExpression[tmp, :'=', args] args = tmp end f = fcall('rb_funcall3', recv, rb_intern(ast[2]), rb_ary_len(args), rb_ary_ptr(args)) # elsif ast[3][0] == :argscat else raise Fail, "unsupported #{ast.inspect}" end if want_value tmp ||= get_new_tmp_var('call', want_value) scope.statements << C::CExpression[tmp, :'=', f] tmp else scope.statements << f f end when :if, :when if ast[0] == :when and ast[1][0] == :array cnd = nil ast[1][1..-1].map { |cd| rb_test(ast_to_c(cd, scope), scope) }.each { |cd| cnd = (cnd ? C::CExpression[cnd, :'||', cd] : cd) } else cnd = rb_test(ast_to_c(ast[1], scope), scope) end tbdy = C::Block.new(scope) ebdy = C::Block.new(scope) if ast[3] or want_value if want_value tmp = get_new_tmp_var('if', want_value) thn = ast_to_c(ast[2], tbdy, tmp) tbdy.statements << C::CExpression[tmp, :'=', thn] if tmp != thn if ast[3] els = ast_to_c(ast[3], ebdy, tmp) else # foo = if bar ; baz ; end => nil if !bar els = rb_nil end ebdy.statements << C::CExpression[tmp, :'=', els] if tmp != els else ast_to_c(ast[2], tbdy, false) ast_to_c(ast[3], ebdy, false) end scope.statements << C::If.new(cnd, tbdy, ebdy) tmp when :while, :until pib = @iter_break @iter_break = nil # XXX foo = while ()... body = C::Block.new(scope) if ast[3] == 0 # do .. while(); ast_to_c(ast[2], body, false) end t = nil e = C::Break.new t, e = e, t if ast[0] == :until body.statements << C::If.new(rb_test(ast_to_c(ast[1], body), body), t, e) if ast[3] != 0 # do .. while(); ast_to_c(ast[2], body, false) end scope.statements << C::For.new(nil, nil, nil, body) @iter_break = pib nil.object_id when :and, :or, :not # beware lazy evaluation ! tmp = get_new_tmp_var('and', want_value) v1 = ast_to_c(ast[1], scope, tmp) # and/or need that tmp has the actual v1 value (returned when shortcircuit) scope.statements << C::CExpression[tmp, :'=', v1] if v1 != tmp v1 = tmp case ast[0] when :and t = C::Block.new(scope) v2 = ast_to_c(ast[2], t, tmp) t.statements << C::CExpression[tmp, :'=', v2] if v2 != tmp when :or e = C::Block.new(scope) v2 = ast_to_c(ast[2], e, tmp) e.statements << C::CExpression[tmp, :'=', v2] if v2 != tmp when :not t = C::CExpression[tmp, :'=', rb_false] e = C::CExpression[tmp, :'=', rb_true] end scope.statements << C::If.new(rb_test(v1, scope), t, e) tmp when :return scope.statements << C::Return.new(ast_to_c(ast[1], scope)) nil.object_id when :break if @iter_break v = (ast[1] ? ast_to_c(ast[1], scope, @iter_break) : nil.object_id) scope.statements << C::CExpression[@iter_break, :'=', [[v], value]] if @iter_break != v end scope.statements << C::Break.new nil.object_id when nil, :args nil.object_id when :nil rb_nil when :false rb_false when :true rb_true when :const rb_const(ast[1]) when :colon2 if cst = check_const(ast[1]) rb_const(ast[2], cst) else fcall('rb_const_get', ast_to_c(ast[1], scope), rb_intern(ast[2])) end when :colon3 rb_const(ast[1], ::Object) when :defined case ast[1][0] when :ivar fcall('rb_ivar_defined', rb_self, rb_intern(ast[1][1])) else raise Fail, "unsupported #{ast.inspect}" end when :masgn # parallel assignment: put everything in an Array, then pop everything back? rb_masgn(ast, scope, want_value) when :evstr fcall('rb_obj_as_string', ast_to_c(ast[1], scope)) when :dot2, :dot3 fcall('rb_range_new', ast_to_c(ast[1], scope), ast_to_c(ast[2], scope), ast[0] == :dot2 ? 0 : 1) when :splat fcall('rb_Array', ast_to_c(ast[1], scope)) when :to_ary fcall('rb_ary_to_ary', ast_to_c(ast[1], scope)) when :dstr # dynamic string: "foo#{bar}baz" tmp = get_new_tmp_var('dstr') scope.statements << C::CExpression[tmp, :'=', fcall('rb_str_new2', ast[1][1])] ast[2..-1].compact.each { |s| if s[0] == :str # directly append the char* scope.statements << fcall('rb_str_cat2', tmp, s[1]) else scope.statements << fcall('rb_str_append', tmp, ast_to_c(s, scope)) end } tmp when :case compile_case(ast, scope, want_value) when :ensure # TODO ret = ast_to_c(ast[1], scope, want_value) ast_to_c(ast[3], scope, false) ret else raise Fail, "unsupported #{ast.inspect}" end if want_value ret = C::CExpression[[ret], value] if ret.kind_of? Integer or ret.kind_of? String ret end end
checks if ast maps to a constant, returns it if it does
# File samples/dynamic_ruby.rb, line 360 def check_const(ast) case ast[0] when :const resolve_const_owner(ast[1]) when :colon2 if cst = check_const(ast[1]) cst.const_get(ast[2]) end when :colon3 ::Object.const_get(ast[2]) end end
convert a ruby AST to a new C function returns the new function name
# File samples/dynamic_ruby.rb, line 305 def compile(ast, klass, meth, singleton=false) return if not ast # TODO handle arbitrary block/yield constructs # TODO analyse to find/optimize numeric locals that never need a ruby VALUE (ie native int vs INT2FIX) # TODO detect block/closure exported out of the func & abort compilation @klass = klass @meth = meth @meth_singleton = singleton mname = escape_varname("m_#{@klass}#{singleton ? '.' : '#'}#{@meth}".gsub('::', '_')) @cp.parse "static void #{mname}(VALUE self) { }" @cur_cfunc = @cp.toplevel.symbol[mname] @cur_cfunc.type.type = value # return type = VALUE, w/o 'missing return statement' warning @scope = @cur_cfunc.initializer case ast[0] when :ivar # attr_reader ret = fcall('rb_ivar_get', rb_self, rb_intern(ast[1])) when :attrset # attr_writer compile_args(@cur_cfunc, [nil, 1]) ret = fcall('rb_ivar_set', rb_self, rb_intern(ast[1]), local(2)) when :scope # standard ruby function @cref = ast[1][:cref] if ast[2] and ast[2][0] == :block and ast[2][1] and ast[2][1][0] == :args compile_args(@cur_cfunc, ast[2][1]) end want_value = true if meth.to_s == 'initialize' and not singleton want_value = false end ret = ast_to_c(ast[2], @scope, want_value) ret = rb_nil if not want_value #when :cfunc # native ruby extension else raise "unhandled function ast #{ast.inspect}" end @scope.statements << C::Return.new(ret) mname end
# File samples/dynamic_ruby.rb, line 373 def compile_args(func, args) case method_arity when -1 # args[1] == 0 and (args[2] or args[3]) compile_args_m1(func, args) when -2 # args[1] > 0 and (args[2] or args[3]) compile_args_m2(func, args) else # fixed arity = args[1]: VALUE func(VALUE self, VALUE local_2, VALUE local_3) args[1].times { |i| v = C::Variable.new("local_#{i+2}", value) @scope.symbol[v.name] = v func.type.args << v } end end
update func prototype to reflect arity -1 VALUE func(int argc, VALUE *argv, VALUE self)
# File samples/dynamic_ruby.rb, line 391 def compile_args_m1(func, args) c = C::Variable.new("arg_c", C::BaseType.new(:int, :unsigned)) v = C::Variable.new("arg_v", C::Pointer.new(value)) @scope.symbol[c.name] = c @scope.symbol[v.name] = v func.type.args.unshift v func.type.args.unshift c args[1].times { |i| local(i+2, C::CExpression[v, :'[]', [i]]) } if args[2] # [:block, [:lasgn, 2, [:lit, 4]]] raise Fail, "unhandled vararglist #{args.inspect}" if args[2][0] != :block args[2][1..-1].each_with_index { |a, i| raise Fail, "unhandled arg #{a.inspect}" if a[0] != :lasgn cnd = C::CExpression[c, :>, i] thn = C::CExpression[local(a[1], :none), :'=', [v, :'[]', [i]]] els = C::Block.new(@scope) ast_to_c(a, els, false) @scope.statements << C::If.new(cnd, thn, els) } end if args[3] raise Fail, "unhandled vararglist3 #{args.inspect}" if args[3][0] != :lasgn skiplen = args[1] + args[2].length - 1 alloc = fcall('rb_ary_new4', [c, :-, [skiplen]], [v, :+, [skiplen]]) local(args[3][1], C::CExpression[[c, :>, skiplen], :'?:', [alloc, fcall('rb_ary_new')]]) end end
update func prototype to reflect arity -2 VALUE func(VALUE self, VALUE arg_array)
# File samples/dynamic_ruby.rb, line 426 def compile_args_m2(func, args) v = C::Variable.new("arglist", value) @scope.symbol[v.name] = v func.type.args << v args[1].times { |i| local(i+2, fcall('rb_ary_shift', v)) } # populate arguments with default values if args[2] # [:block, [:lasgn, 2, [:lit, 4]]] raise Fail, "unhandled vararglist #{args.inspect}" if args[2][0] != :block args[2][1..-1].each { |a| raise Fail, "unhandled arg #{a.inspect}" if a[0] != :lasgn t = C::CExpression[local(a[1], :none), :'=', fcall('rb_ary_shift', v)] e = C::Block.new(@scope) ast_to_c([:lasgn, a[1], a[2]], e, false) @scope.statements << C::If.new(rb_ary_len(v), t, e) } end if args[3] raise Fail, "unhandled vararglist3 #{args.inspect}" if args[3][0] != :lasgn local(args[3][1], C::CExpression[v]) end end
compile a case/when create a real C switch() for Fixnums, and put the others === in the default case XXX will get the wrong order for “case x; when 1; when Fixnum; when 3;” …
# File samples/dynamic_ruby.rb, line 457 def compile_case(ast, scope, want_value) # this generates # var = stuff_to_test() # if (var & 1) # switch (var >> 1) { # case 12: # stuff(); # break; # default: # goto default_case; # } # else # default_case: # if (var == true.object_id || rb_test(rb_funcall(bla, '===', var))) # foo(); # else { # default(); # } # if want_value == true ret = get_new_tmp_var('case', want_value) want_value = ret elsif want_value ret = want_value end var = ast_to_c(ast[1], scope, want_value || true) if not var.kind_of? C::Variable ret ||= get_new_tmp_var('case', want_value) scope.statements << C::CExpression[ret, :'=', var] var = ret end # the scope to put all case int in body_int = C::Block.new(scope) # the scope to put the if (cs === var) cascade body_other_head = body_other = nil default = nil ast[2..-1].each { |cs| if cs[0] == :when raise Fail if cs[1][0] != :array # numeric case, add a case to body_int if cs[1][1..-1].all? { |cd| cd[0] == :lit and (cd[1].kind_of? Fixnum or cd[1].kind_of? Range) } cs[1][1..-1].each { |cd| if cd[1].kind_of? Range b = cd[1].begin e = cd[1].end e -= 1 if cd[1].exclude_end? raise Fail unless b.kind_of? Integer and e.kind_of? Integer body_int.statements << C::Case.new(b, e, nil) else body_int.statements << C::Case.new(cd[1], nil, nil) end } cb = C::Block.new(scope) v = ast_to_c(cs[2], cb, want_value) cb.statements << C::CExpression[ret, :'=', v] if want_value and v != ret cb.statements << C::Break.new body_int.statements << cb # non-numeric (or mixed) case, add if ( cs === var ) else cnd = nil cs[1][1..-1].each { |cd| if (cd[0] == :lit and (cd[1].kind_of?(Fixnum) or cd[1].kind_of?(Symbol))) or [:nil, :true, :false].include?(cd[0]) # true C equality cd = C::CExpression[var, :==, ast_to_c(cd, scope)] else # own block for ast_to_c to honor lazy evaluation tb = C::Block.new(scope) test = rb_test(rb_funcall(ast_to_c(cd, tb), '===', var), tb) # discard own block unless needed if tb.statements.empty? cd = test else tb.statements << test cd = C::CExpression[tb, value] end end cnd = (cnd ? C::CExpression[cnd, :'||', cd] : cd) } cb = C::Block.new(scope) v = ast_to_c(cs[2], cb, want_value) cb.statements << C::CExpression[ret, :'=', v] if want_value and v != ret fu = C::If.new(cnd, cb, nil) if body_other body_other.belse = fu else body_other_head = fu end body_other = fu end # default case statement else cb = C::Block.new(scope) v = ast_to_c(cs, cb, want_value) cb.statements << C::CExpression[ret, :'=', v] if want_value and v != ret default = cb end } # if we use the value of the case, we must add an 'else: nil' if want_value and not default default = C::Block.new(scope) default.statements << C::CExpression[ret, :'=', rb_nil] end # assemble everything scope.statements << if body_int.statements.empty? if body_other body_other.belse = default body_other_head else raise Fail, "empty case? #{ast.inspect}" if not default default end else if body_other_head @default_label_cnt ||= 0 dfl = "default_label_#{@default_label_cnt += 1}" body_other_head = C::Label.new(dfl, body_other_head) body_int.statements << C::Case.new('default', nil, C::Goto.new(dfl)) body_other.belse = default if default end body_int = C::Switch.new(C::CExpression[var, :>>, 1], body_int) C::If.new(C::CExpression[var, :&, 1], body_int, body_other_head) end ret end
declare a new function variable no initializer if init == :none
# File samples/dynamic_ruby.rb, line 611 def declare_newvar(name, initializer) v = C::Variable.new(name, value) v.initializer = initializer if initializer != :none @scope.symbol[v.name] = v @scope.statements << C::Declaration.new(v) v end
# File samples/dynamic_ruby.rb, line 288 def dump(m=nil) m ? @cp.dump_definition(m) : @cp.to_s end
retrieve/create a new dynamic variable (block argument/variable) pass :none to avoid initializer
# File samples/dynamic_ruby.rb, line 647 def dvar(n, init=nil) get_var "dvar_#{n}", init end
return a string suitable for use as a variable name hexencode any char not in [A-z0-9_]
# File samples/dynamic_ruby.rb, line 621 def escape_varname(n) n.gsub(/[^\w]/) { |c| c.unpack('H*')[0] } end
create a C::CExpr[toplevel.symbol, :funcall, args] casts int/strings in arglist to CExpr
# File samples/dynamic_ruby.rb, line 597 def fcall(fname, *arglist) args = arglist.map { |a| (a.kind_of?(Integer) or a.kind_of?(String)) ? [a] : a } fv = @cp.toplevel.symbol[fname] raise "need prototype for #{fname}!" if not fv C::CExpression[fv, :funcall, args] end
return ptr, arity ptr is a CExpr pointing to the C func implementing klass#method
# File samples/dynamic_ruby.rb, line 1360 def get_cfuncptr(klass, method, singleton=false) 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 fast = RubyHack.read_node(ptr) arity = fast[1][:arity] fptr = fast[1][:fptr] 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 C::CExpression[[fptr], C::Pointer.new(fproto)] end
create a new temporary variable XXX put_var ?
# File samples/dynamic_ruby.rb, line 634 def get_new_tmp_var(base=nil, var=nil) return var if var.kind_of? C::Variable @tmp_var_id ||= 0 get_var("tmp_#{"#{base}_" if base}#{@tmp_var_id += 1}") end
retrieve or create a local var pass :none to avoid initializer
# File samples/dynamic_ruby.rb, line 627 def get_var(name, initializer=:none) name = escape_varname(name) @scope.symbol[name] ||= declare_newvar(name, initializer || rb_nil) end
retrieve/create a new local variable with optionnal initializer
# File samples/dynamic_ruby.rb, line 641 def local(n, init=nil) get_var "local_#{n}", init end
return the arity of method 'name' on self
# File samples/dynamic_ruby.rb, line 350 def method_arity(name=@meth) @meth_singleton ? @klass.method(name).arity : @klass.instance_method(name).arity end
optional optimization of a call (eg a == 1, c+2, …) return nil for normal #rb_funcall, or a C::CExpr to use as retval.
# File samples/dynamic_ruby.rb, line 1134 def optimize_call(ast, scope, want_value) ce = C::CExpression op = ast[2] int = C::BaseType.new(:ptr) # signed VALUE args = ast[3][1..-1] if ast[3] and ast[3][0] == :array arg0 = args[0] if args and args[0] if arg0 and arg0[0] == :lit and arg0[1].kind_of?(Fixnum) and %w[== > < >= <= + -].include?(op) # TODO or @optim_hint[ast[1]] == 'fixnum' # optimize 'x==42', 'x+42', 'x-42' o2 = arg0[1] if o2 < 0 and ['+', '-'].include? op # need o2 >= 0 for overflow detection op = {'+' => '-', '-' => '+'}[op] o2 = -o2 return if not o2.kind_of? Fixnum # -0x40000000 end int_v = o2.object_id recv = ast_to_c(ast[1], scope) tmp = get_new_tmp_var('opt', want_value) if not recv.kind_of? C::Variable scope.statements << ce[tmp, :'=', recv] recv = tmp end case op when '==' # XXX assume == only return true for full equality: if not Fixnum, then always false # which breaks 1.0 == 1 and maybe others, but its ok scope.statements << C::If.new(ce[recv, :'==', [int_v]], ce[tmp, :'=', rb_true], ce[tmp, :'=', rb_false]) when '>', '<', '>=', '<=' # do the actual comparison on signed >>1 if both Fixnum t = C::If.new( ce[[[[recv], int], :>>, [1]], op.to_sym, [[[int_v], int], :>>, [1]]], ce[tmp, :'=', rb_true], ce[tmp, :'=', rb_false]) # fallback to actual rb_funcall e = ce[tmp, :'=', rb_funcall(recv, op, o2.object_id)] add_optimized_statement scope, ast[1], recv, 'fixnum' => t, 'other' => e when '+' e = ce[recv, :+, [int_v-1]] # overflow to Bignum ? cnd = ce[[recv, :&, [1]], :'&&', [[[recv], int], :<, [[e], int]]] t = ce[tmp, :'=', e] e = ce[tmp, :'=', rb_funcall(recv, op, o2.object_id)] if @optim_hint[ast[1]] == 'fixnum' # add_optimized_statement wont handle the overflow check correctly scope.statements << t else scope.statements << C::If.new(cnd, t, e) end when '-' e = ce[recv, :-, [int_v-1]] cnd = ce[[recv, :&, [1]], :'&&', [[[recv], int], :>, [[e], int]]] t = ce[tmp, :'=', e] e = ce[tmp, :'=', rb_funcall(recv, op, o2.object_id)] if @optim_hint[ast[1]] == 'fixnum' scope.statements << t else scope.statements << C::If.new(cnd, t, e) end end tmp # Symbol#== elsif arg0 and arg0[0] == :lit and arg0[1].kind_of? Symbol and op == '==' s_v = ast_to_c(arg0, scope) tmp = get_new_tmp_var('opt', want_value) recv = ast_to_c(ast[1], scope, tmp) if not recv.kind_of? C::Variable scope.statements << ce[tmp, :'=', recv] recv = tmp end scope.statements << C::If.new(ce[recv, :'==', [s_v]], ce[tmp, :'=', rb_true], ce[tmp, :'=', rb_false]) tmp elsif arg0 and op == '<<' tmp = get_new_tmp_var('opt', want_value) recv = ast_to_c(ast[1], scope, tmp) arg = ast_to_c(arg0, scope) if recv != tmp scope.statements << ce[tmp, :'=', recv] recv = tmp end ar = fcall('rb_ary_push', recv, arg) st = fcall('rb_str_concat', recv, arg) oth = rb_funcall(recv, op, arg) oth = ce[tmp, :'=', oth] if want_value add_optimized_statement scope, ast[1], recv, 'ary' => ar, 'string' => st, 'other' => oth tmp elsif arg0 and args.length == 1 and op == '[]' return if ast[1][0] == :const # Expression[42] tmp = get_new_tmp_var('opt', want_value) recv = ast_to_c(ast[1], scope, tmp) if not recv.kind_of? C::Variable scope.statements << ce[tmp, :'=', recv] recv = tmp end idx = get_new_tmp_var('idx') arg = ast_to_c(arg0, scope, idx) if not arg.kind_of? C::Variable scope.statements << ce[idx, :'=', arg] arg = idx end idx = ce[[idx], int] ar = C::Block.new(scope) ar.statements << ce[idx, :'=', [[[arg], int], :>>, [1]]] ar.statements << C::If.new(ce[idx, :<, [0]], ce[idx, :'=', [idx, :+, rb_ary_len(recv)]], nil) ar.statements << C::If.new(ce[[idx, :<, [0]], :'||', [idx, :>=, [[rb_ary_len(recv)], int]]], ce[tmp, :'=', rb_nil], ce[tmp, :'=', rb_ary_ptr(recv, idx)]) st = C::Block.new(scope) st.statements << ce[idx, :'=', [[[arg], int], :>>, [1]]] st.statements << C::If.new(ce[idx, :<, [0]], ce[idx, :'=', [idx, :+, rb_str_len(recv)]], nil) st.statements << C::If.new(ce[[idx, :<, [0]], :'||', [idx, :>=, [[rb_str_len(recv)], int]]], ce[tmp, :'=', rb_nil], ce[tmp, :'=', [[[[rb_str_ptr(recv, idx), :&, [0xff]], :<<, [1]], :|, [1]], value]]) hsh = ce[tmp, :'=', fcall('rb_hash_aref', recv, arg)] oth = ce[tmp, :'=', rb_funcall(recv, op, arg)] # ary/string only valid with fixnum argument ! add_optimized_statement scope, ast[1], recv, 'hash' => hsh, 'other' => oth, 'ary_bnd' => ce[tmp, :'=', rb_ary_ptr(recv, ce[[[arg], int], :>>, [1]])], ce[[arg, :&, 1], :'&&', rb_test_class_ary(recv)] => ar, ce[[arg, :&, 1], :'&&', rb_test_class_string(recv)] => st tmp elsif ast[1] and not arg0 and op == 'empty?' tmp = get_new_tmp_var('opt', want_value) recv = ast_to_c(ast[1], scope, tmp) if not recv.kind_of? C::Variable scope.statements << ce[tmp, :'=', recv] recv = tmp end ar = C::If.new(rb_ary_len(recv), ce[tmp, :'=', rb_false], ce[tmp, :'=', rb_true]) add_optimized_statement scope, ast[1], recv, 'ary' => ar, 'other' => ce[tmp, :'=', rb_funcall(recv, op)] tmp elsif ast[1] and not arg0 and op == 'pop' tmp = get_new_tmp_var('opt', want_value) recv = ast_to_c(ast[1], scope, tmp) if not recv.kind_of? C::Variable scope.statements << ce[tmp, :'=', recv] recv = tmp end t = fcall('rb_ary_pop', recv) e = rb_funcall(recv, op) if want_value t = ce[tmp, :'=', t] e = ce[tmp, :'=', e] end add_optimized_statement scope, ast[1], recv, 'ary' => t, 'other' => e tmp elsif ast[1] and op == 'kind_of?' and arg0 and (arg0[0] == :const or arg0[0] == :colon3) # TODO check const maps to toplevel when :const test = case arg0[1] when 'Symbol' tmp = get_new_tmp_var('kindof', want_value) ce[[ast_to_c(ast[1], scope, tmp), :'&', [0xf]], :'==', [0xe]] #when 'Numeric', 'Integer' when 'Fixnum' tmp = get_new_tmp_var('kindof', want_value) ce[ast_to_c(ast[1], scope, tmp), :'&', [0x1]] when 'Array' rb_test_class_ary(ast_to_c(ast[1], scope)) when 'String' rb_test_class_string(ast_to_c(ast[1], scope)) else return end puts "shortcut may be incorrect for #{ast.inspect}" if arg0[0] == :const tmp ||= get_new_tmp_var('kindof', want_value) scope.statements << C::If.new(test, ce[tmp, :'=', rb_true], ce[tmp, :'=', rb_false]) tmp elsif not ast[1] or ast[1] == [:self] optimize_call_static(ast, scope, want_value) end end
call C funcs directly assume private function calls are not virtual and hardlink them here
# File samples/dynamic_ruby.rb, line 1382 def optimize_call_static(ast, scope, want_value) arity = method_arity(ast[2]) rescue return if ast[2].to_s == @meth.to_s # self is recursive fptr = @cur_cfunc else fptr = get_cfuncptr(@klass, ast[2], @meth_singleton) return if not fptr end c_arglist = [] if not ast[3] args = [] elsif ast[3][0] == :array args = ast[3][1..-1] elsif ast[3][0] == :splat args = ast_to_c(ast[3], scope) if arity != -2 and !args.kind_of?(C::Variable) tmp = get_new_tmp_var('arg') scope.statements << C::CExpression[tmp, :'=', args] args = tmp end case arity when -2 c_arglist << rb_self << args when -1 c_arglist << [rb_ary_len(args)] << rb_ary_ptr(args) << rb_self else cnd = C::CExpression[rb_ary_len(args), :'!=', [arity]] scope.statements << C::If.new(cnd, rb_raise("#{arity} args expected", 'rb_eArgumentError'), nil) c_arglist << rb_self arity.times { |i| c_arglist << rb_ary_ptr(args, i) } end arity = :canttouchthis else return # TODO end case arity when :canttouchthis when -2 arg = get_new_tmp_var('arg') scope.statements << C::CExpression[arg, :'=', fcall('rb_ary_new')] args.each { |a| scope.statements << fcall('rb_ary_push', arg, ast_to_c(a, scope)) } c_arglist << rb_self << arg when -1 case args.length when 0 argv = C::CExpression[[0], C::Pointer.new(value)] when 1 val = ast_to_c(args[0], scope) if not val.kind_of? C::Variable argv = get_new_tmp_var('argv') scope.statements << C::CExpression[argv, :'=', val] val = argv end argv = C::CExpression[:'&', val] else argv = get_new_tmp_var('argv') argv.type = C::Array.new(value, args.length) args.each_with_index { |a, i| val = ast_to_c(a, scope) scope.statements << C::CExpression[[argv, :'[]', [i]], :'=', val] } end c_arglist << [args.length] << argv << rb_self else c_arglist << rb_self args.each { |a| va = get_new_tmp_var('arg') val = ast_to_c(a, scope, va) scope.statements << C::CExpression[va, :'=', val] if val != va c_arglist << va } end f = C::CExpression[fptr, :funcall, c_arglist] if want_value ret = get_new_tmp_var('ccall', want_value) scope.statements << C::CExpression[ret, :'=', f] ret else scope.statements << f end end
# File samples/dynamic_ruby.rb, line 1473 def optimize_iter(ast, scope, want_value) b_args, b_body, b_recv = ast[1, 3] old_ib = @iter_break if want_value # a new tmpvar, so we can overwrite it in 'break :foo' @iter_break = get_new_tmp_var('iterbreak') else @iter_break = nil end if b_recv[0] == :call and b_recv[2] == 'reverse_each' # convert ary.reverse_each to ary.reverse.each b_recv = b_recv.dup b_recv[1] = [:call, b_recv[1], 'reverse'] b_recv[2] = 'each' elsif b_recv[0] == :call and b_recv[2] == 'each_key' # convert hash.each_key to hash.keys.each b_recv = b_recv.dup b_recv[1] = [:call, b_recv[1], 'keys'] b_recv[2] = 'each' end # loop { } if b_recv[0] == :fcall and b_recv[2] == 'loop' body = C::Block.new(scope) ast_to_c(b_body, body, false) scope.statements << C::For.new(nil, nil, nil, body) # int.times { |i| } elsif b_recv[0] == :call and not b_recv[3] and b_recv[2] == 'times' limit = get_new_tmp_var('limit') recv = ast_to_c(b_recv[1], scope, limit) scope.statements << C::If.new(C::CExpression[:'!', [recv, :&, 1]], rb_raise('only Fixnum#times handled'), nil) if want_value scope.statements << C::CExpression[@iter_break, :'=', recv] end scope.statements << C::CExpression[limit, :'=', [recv, :>>, 1]] cntr = get_new_tmp_var('cntr') cntr.type = C::BaseType.new(:int, :unsigned) body = C::Block.new(scope) if b_args and b_args[0] == :dasgn_curr body.statements << C::CExpression[dvar(b_args[1]), :'=', [[cntr, :<<, 1], :|, 1]] end ast_to_c(b_body, body, false) scope.statements << C::For.new(C::CExpression[cntr, :'=', [[0], cntr.type]], C::CExpression[cntr, :<, limit], C::CExpression[:'++', cntr], body) # ary.each { |e| } elsif b_recv[0] == :call and not b_recv[3] and b_recv[2] == 'each' and b_args and b_args[0] == :dasgn_curr ary = get_new_tmp_var('ary') recv = ast_to_c(b_recv[1], scope, ary) scope.statements << C::CExpression[ary, :'=', recv] if ary != recv scope.statements << C::If.new(rb_test_class_ary(ary), nil, rb_raise('only Array#each { |e| } handled')) if want_value scope.statements << C::CExpression[@iter_break, :'=', ary] end cntr = get_new_tmp_var('cntr') cntr.type = C::BaseType.new(:int, :unsigned) body = C::Block.new(scope) if b_args and b_args[0] == :dasgn_curr body.statements << C::CExpression[dvar(b_args[1]), :'=', [rb_ary_ptr(ary), :'[]', [cntr]]] end ast_to_c(b_body, body, false) scope.statements << C::For.new(C::CExpression[cntr, :'=', [[0], cntr.type]], C::CExpression[cntr, :<, rb_ary_len(ary)], C::CExpression[:'++', cntr], body) # ary.find { |e| } elsif b_recv[0] == :call and not b_recv[3] and b_recv[2] == 'find' and b_args and b_args[0] == :dasgn_curr ary = get_new_tmp_var('ary') recv = ast_to_c(b_recv[1], scope, ary) scope.statements << C::CExpression[ary, :'=', recv] if ary != recv scope.statements << C::If.new(rb_test_class_ary(ary), nil, rb_raise('only Array#find { |e| } handled')) if want_value scope.statements << C::CExpression[@iter_break, :'=', rb_nil] end cntr = get_new_tmp_var('cntr') cntr.type = C::BaseType.new(:int, :unsigned) body = C::Block.new(scope) if b_args and b_args[0] == :dasgn_curr body.statements << C::CExpression[dvar(b_args[1]), :'=', [rb_ary_ptr(ary), :'[]', [cntr]]] end # same as #each up to this point (except default retval), now add a 'if (body_value) break ary[cntr];' # XXX 'find { next true }' found = ast_to_c(b_body, body) t = C::Block.new(body) t.statements << C::CExpression[@iter_break, :'=', rb_ary_ptr(ary, cntr)] t.statements << C::Break.new body.statements << C::If.new(rb_test(found, body), t, nil) scope.statements << C::For.new(C::CExpression[cntr, :'=', [[0], cntr.type]], C::CExpression[cntr, :<, rb_ary_len(ary)], C::CExpression[:'++', cntr], body) # ary.map { |e| } elsif b_recv[0] == :call and not b_recv[3] and b_recv[2] == 'map' and b_args and b_args[0] == :dasgn_curr ary = get_new_tmp_var('ary') recv = ast_to_c(b_recv[1], scope, ary) scope.statements << C::CExpression[ary, :'=', recv] if ary != recv scope.statements << C::If.new(rb_test_class_ary(ary), nil, rb_raise('only Array#map { |e| } handled')) if want_value scope.statements << C::CExpression[@iter_break, :'=', fcall('rb_ary_new')] end cntr = get_new_tmp_var('cntr') cntr.type = C::BaseType.new(:int, :unsigned) body = C::Block.new(scope) if b_args and b_args[0] == :dasgn_curr body.statements << C::CExpression[dvar(b_args[1]), :'=', [rb_ary_ptr(ary), :'[]', [cntr]]] end # same as #each up to this point (except default retval), now add a '@iter_break << body_value' # XXX 'next' unhandled val = ast_to_c(b_body, body) body.statements << fcall('rb_ary_push', @iter_break, val) scope.statements << C::For.new(C::CExpression[cntr, :'=', [[0], cntr.type]], C::CExpression[cntr, :<, rb_ary_len(ary)], C::CExpression[:'++', cntr], body) else @iter_break = old_ib return end ret = @iter_break @iter_break = old_ib ret || nil.object_id end
ARY_LEN(expr)
# File samples/dynamic_ruby.rb, line 726 def rb_ary_len(expr) rb_cast_pvalue(expr, 2) end
ARY_PTR(expr)
# File samples/dynamic_ruby.rb, line 721 def rb_ary_ptr(expr, idx=nil) p = C::CExpression[[rb_cast_pvalue(expr, 4)], C::Pointer.new(value)] idx ? C::CExpression[p, :'[]', [idx]] : p end
returns a CExpr casting expr to a VALUE*
# File samples/dynamic_ruby.rb, line 657 def rb_cast_pvalue(expr, idx) C::CExpression[[[expr], C::Pointer.new(value)], :'[]', [idx]] end
returns a static pointer to the constant
# File samples/dynamic_ruby.rb, line 749 def rb_const(constname, owner = resolve_const_owner(constname)) raise Fail, "no dynamic constant resolution #{constname}" if not owner cst = owner.const_get(constname) C::CExpression[[RubyHack.rb_obj_to_value(cst)], value] end
# File samples/dynamic_ruby.rb, line 673 def rb_false C::CExpression[[false.object_id], value] end
create a #rb_funcall construct
# File samples/dynamic_ruby.rb, line 684 def rb_funcall(recv, meth, *args) fcall('rb_funcall', recv, rb_intern(meth), args.length, *args) end
# File samples/dynamic_ruby.rb, line 784 def rb_global(cname) @cp.toplevel.symbol[cname] end
call #rb_intern on a string
# File samples/dynamic_ruby.rb, line 678 def rb_intern(n) # use the current interpreter's value C::CExpression[n.to_sym.to_i] end
compile a :masgn
# File samples/dynamic_ruby.rb, line 756 def rb_masgn(ast, scope, want_value) raise Fail, "masgn with no rhs #{ast.inspect}" if not ast[2] raise Fail, "masgn with no lhs array #{ast.inspect}" if not ast[1] or ast[1][0] != :array if not want_value and ast[2][0] == :array and not ast[3] and ast[2].length == ast[1].length rb_masgn_optimized(ast, scope) return nil.object_id end full = get_new_tmp_var('masgn', want_value) ary = ast_to_c(ast[2], scope, full) scope.statements << C::CExpression[full, :'=', ary] if full != ary ast[1][1..-1].each_with_index { |e, i| raise Fail, "weird masgn lhs #{e.inspect} in #{ast.inspect}" if e[-1] != nil # local_42 = full[i] e = e.dup e[-1] = [:rb2cstmt, rb_ary_ptr(full, i)] ast_to_c(e, scope, false) } if ast[3] raise Fail, "weird masgn lhs #{e.inspect} in #{ast.inspect}" if ast[3][-1] != nil # local_28 = full[12..-1].to_a e = ast[3].dup e[-1] = [:call, [:call, [:rb2cvar, full.name], '[]', [:array, [:dot2, [:lit, ast[1].length-1], [:lit, -1]]]], 'to_a'] ast_to_c(e, scope, false) end full end
compile an optimized :masgn with rhs.length == lhs.length (no need of a ruby array)
# File samples/dynamic_ruby.rb, line 789 def rb_masgn_optimized(ast, scope) vars = [] ast[2][1..-1].each { |rhs| var = get_new_tmp_var('masgn_opt') vars << var r = ast_to_c(rhs, scope, var) scope.statements << C::CExpression[var, :'=', r] if var != r } ast[1][1..-1].each { |lhs| var = vars.shift lhs = lhs.dup raise Fail, "weird masgn lhs #{lhs.inspect} in #{ast.inspect}" if lhs[-1] != nil lhs[-1] = [:rb2cvar, var.name] ast_to_c(lhs, scope, false) } end
# File samples/dynamic_ruby.rb, line 667 def rb_nil C::CExpression[[nil.object_id], value] end
generate C code to raise a RuntimeError, reason
# File samples/dynamic_ruby.rb, line 706 def rb_raise(reason, cls='rb_eRuntimeError') fcall('rb_raise', rb_global(cls), reason) end
retrieve self (1st func arg)
# File samples/dynamic_ruby.rb, line 652 def rb_self @scope.symbol['self'] end
retrieve the current class, from self->klass XXX will segfault with self.kind_of? Fixnum/true/false/nil/sym
# File samples/dynamic_ruby.rb, line 663 def rb_selfclass rb_cast_pvalue(rb_self, 1) end
STR_LEN(expr)
# File samples/dynamic_ruby.rb, line 740 def rb_str_len(expr) rb_cast_pvalue(expr, 2) end
STR_PTR(expr)
# File samples/dynamic_ruby.rb, line 735 def rb_str_ptr(expr, idx=nil) p = C::CExpression[[rb_cast_pvalue(expr, 3)], C::Pointer.new(C::BaseType.new(:char))] idx ? C::CExpression[p, :'[]', [idx]] : p end
ruby bool test of a var assigns to a temporary var, and check against false/nil
# File samples/dynamic_ruby.rb, line 690 def rb_test(expr, scope) if nil.object_id == 0 or false.object_id == 0 # just to be sure nf = nil.object_id | false.object_id C::CExpression[[expr, :|, nf], :'!=', nf] else if expr.kind_of? C::Variable tmp = expr else tmp = get_new_tmp_var('test') scope.statements << C::CExpression[tmp, :'=', expr] end C::CExpression[[tmp, :'!=', rb_nil], :'&&', [tmp, :'!=', rb_false]] end end
return a C expr equivallent to TYPE(expr) == T_ARRAY
# File samples/dynamic_ruby.rb, line 717 def rb_test_class_ary(expr) rb_test_class_type(expr, 9) end
# File samples/dynamic_ruby.rb, line 744 def rb_test_class_hash(expr) rb_test_class_type(expr, 0xb) end
TYPE(expr) == T_STRING
# File samples/dynamic_ruby.rb, line 731 def rb_test_class_string(expr) rb_test_class_type(expr, 7) end
return a C expr equivallent to TYPE(expr) == type for non-immediate types XXX expr evaluated 3 times
# File samples/dynamic_ruby.rb, line 712 def rb_test_class_type(expr, type) C::CExpression[[[expr, :>, [7]], :'&&', [[expr, :&, [3]], :==, [0]]], :'&&', [[rb_cast_pvalue(expr, 0), :&, [0x3f]], :'==', [type]]] end
# File samples/dynamic_ruby.rb, line 670 def rb_true C::CExpression[[true.object_id], value] end
find the scope where constname is defined from @cref
# File samples/dynamic_ruby.rb, line 355 def resolve_const_owner(constname) @cref.find { |cr| cr.constants.map { |c| c.to_s }.include? constname.to_s } end
the VALUE typedef
# File samples/dynamic_ruby.rb, line 605 def value @cp.toplevel.symbol['VALUE'] end