Thursday, March 26, 2009

Ruby FFI 0.3.2 released

Ruby-FFI is a ruby extension for programmatically loading dynamic
libraries, binding functions within them, and calling those functions
from Ruby code.

This release is compatible with the FFI implementation released in JRuby 1.2.0

Get it via 'gem install ffi' or download the source and/or gem files
from the project page at http://kenai.com/projects/ruby-ffi

Changes since 0.2.0
  • Switch compilation to rake-compiler
    • Makes cross-compilation from linux -> win32 super easy
    • win32 support is available now, but highly experimental

  • Performance improvements
    • struct field access approx 3x faster than 0.2.0
    • function invocation approx 20% faster than 0.2.0

  • A bunch of minor improvements
    • Struct instances can now be passed as :pointer parameters without calling Struct#pointer
    • Support for array struct members
    • Structs are now padded correctly to the alignment of the struct's
      largest field
    • Global library variables
    • Callbacks in global library variables
    • Strings passed in as :string arguments are scrubbed to avoid
      poison-null-byte attacks.
    • Union support
    • nil can be passed as a :string argument (passed as NULL)
    • Structs can now be fields inside another struct
    • Lots of internal cleanups and refactorings.



Special thanks to:

Yehuda Katz
Luc  Heinrich
Andrea Fazzi
Mike Dalessio
Hongli Lai
Evan Phoenix

Thursday, March 12, 2009

Insane things to do with FFI

Inspired by jmettraux's FFI bindings to call the lua interpreter (http://github.com/jmettraux/rufus-lua/tree/master) from ruby using FFI, I whipped up the following:

$ cat tr.rb

require "ffi"
module Tr
extend FFI::Library
ffi_lib "#{Dir.pwd}/libtinyrb.so"
attach_function :new, :TrVM_new, [ ], :pointer
attach_function :eval, :TrVM_eval, [ :pointer, :string, :string ], :long
end
ffi = "module LibC; attach_function :cputs, :puts, [ :string ], :int;end; LibC.cputs 'Hello via TinyRuby FFI'"
vm = Tr.new
Tr.eval(vm, "puts \"Hello, World via TinyRuby puts\"", "<eval>")
Tr.eval(vm, ffi, "<eval>")

$ ruby1.9 tr.rb
Hello, World via TinyRuby puts
Hello via TinyRuby FFI


Yep, thats using FFI from ruby 1.9 to call TinyRB and having it eval some code - including some FFI code of its own.

Saturday, November 1, 2008

More on Ruby FFI

Charlie announced FFI for Ruby. I did the implementation, but I hate writing documentation ... or announcements, so I left that part up to him. This is mostly a response to some of the comments.

Why implement a new API instead of the existing DL one? Mostly because we (JRuby) wanted to run the syslog and zlib modules from Rubinius, and they were written using their FFI syntax. There also seemed to be a dearth of software that actually used DL, and it had somewhat ... arcane syntax.

The Rubinius API on the other hand was DSL-like, and relatively easy to implement, so it was pretty much a no-brainer. This doesn't mean we won't ever implement DL, the infrastructure developed for FFI is sufficient that a DL api could be layered over the top, most likely all in ruby. We already have the beginnings of Win32::API implemented this way.

What about sharing with other implementations? Its unlikely there will be much code sharing - there is already a good chunk of higher-level implementation shared between JRuby-ffi, ruby-ffi, and rubinius, but this is more by necessity than design.

The code is constantly evolving, and trying to define some sort of implementation layer would be like nailing jelly to the wall. The ruby-ffi implementation is all BSD licensed, so that shouldn't be an obstacle.

A better way is what we're going to be doing - defining the FFI API and developing enough specs that it should be easy for an alternative implementation to correctly implement the FFI API.

I haven't really been collaborating with other implementations besides Rubinius yet, because I didn't want to get bogged down in design-by-committee. The rough goal with ruby-ffi is to make it the definitive implementation where the API, specs and documentation are kept, and where new features are designed & implemented.

Whats performance like? For MRI, its a bit unknown. It shouldn't be too bad, but I wanted to get a release out and have people using it and giving feedback, so I didn't bother to tune things at all. There are a couple of places where there is ruby code in the call path, since I needed to munge things around, and couldn't work out how to do what I needed using the C API. Those things are pretty small, and can be fixed up as we go along.

The JRuby implementation on the other hand is very fast. Calling some posix functions via FFI is as fast as calling via jna-posix - and in some cases faster. The path from calling a ruby method, to marshalling the arguments, and calling the C function is very streamlined - the call path is all java, so it gets JIT'd easily, and it avoids boxing parameters where possible.

Basically all the performance tricks JRuby has to implement ruby methods as fast as it can, apply to FFI methods. The JRuby implementation also supports pluggable backends, which means that a faster implementation might be possible on other JVMs (e.g. Maxine, or IKVM.net) and loaded at runtime.

What is the future? For ruby-ffi, in the short term, I'm going to be expanding the specs to cover as much as possible, and waiting to see how people are actually using it in the real world.

I have also started working on jaffl (Java Abstracted Foreign Function Layer), which takes the pluggable backend idea from JRuby's FFI implementation and repurposes it for java code. The idea is that in future, jna-posix and friends will use jaffl as their java FFI layer, with a JNA, JFFI or some future backend. Eventually, JRuby's FFI will also switch over to use jaffl.

Sunday, September 7, 2008

Google Code Project Hosting Considered Harmful

I used to like google code project hosting - it was quick and easy to throw up a project, it had a straightforward, minimalist interface, and their bug/issue tracker was easy for people to use - compared to the overcomplicated behemoths like sourceforge, it was refreshing. And I'm a google-slut - I use gmail, google reader, google groups, google apps, blogger, you name it - centralising on google just makes life easier.

For the last few days, however, I have been getting "403 Forbidden" errors when trying to access any project hosted on google code hosting - not just my own. Even the main code.google.com/hosting page returns a 403 error. Initially, I thought it was a temporary glitch, but when it lasted a few days, and no one else in the universe was complaining, I investigated further.

It appears that my IP address - or given it changes every time I connect, the block of IP addresses my ISP uses - has been blocked from google code hosting. I can access my projects again by using an SSH tunnel to another web host and pointing firefox at the tunnel. Yay.

So, the google overlords have made some sort of cockup - should be easy to get fixed, right?

Here comes the dangerous part - there's no way to get this fixed. In typical google fashion, there is no contact address. All problems with google code hosting are dealt with via a google group. You post to the google group, which is moderated, and ... nothing happens. You wait a few days - the post never appears.

God. Damn. It.

Getting something simple like this fixed should not be that hard.

At least I can use an ssh tunnel to get access to my projects, so I can move them somewhere else - inconvenient, but not fatal.

Now, I'm wondering what to do if the same thing happens to gmail, or apps or ...

Friday, August 29, 2008

FFI callback hotness

I just finished implementing FFI callbacks for JRuby, and it has some slick syntax:

module LibC
extend FFI::Library
callback :qsort_cmp, [ :pointer, :pointer ], :int
attach_function :qsort, [ :pointer, :int, :int, :qsort_cmp ], :int
end

p = MemoryPointer.new(:int, 2)
p.put_array_of_int32(0, [ 1 , 2 ])

cmp = proc do |p1, p2|
i1 = p1.get_int32(0)
i2 = p2.get_int32(0)
i1 < i2 ? -1 : i1 > i2 ? 1 : 0
end
LibC.qsort(p, 2, 4, cmp)

Functions that take a callback as the last argument are able to automatically use a block if it is present instead of passing a proc, so qsort can be called like this:

LibC.qsort(p, 2, 4) do |p1, p2|
i1 = p1.get_int32(0)
i2 = p2.get_int32(0)
i1 < i2 ? -1 : i1 > i2 ? 1 : 0
end

Wednesday, August 27, 2008

FFI on MRI ruby

And so it begins...

$ cat samples/hello.rb
require 'ffi'
module Foo
extend FFI::Library
attach_function("cputs", "puts", [ :string ], :int)
end
Foo.cputs("Hello, World via libc puts using FFI on MRI ruby")

$ ruby -Ilib samples/hello.rb
Hello, World via libc puts using FFI on MRI ruby

Monday, August 25, 2008

Reason #1 to use FFI on JRuby - its faster

When running the FFI benchmarks on JRuby, I noticed something:

Benchmark File.umask(0777) performance, 100000x
0.998000 0.000000 0.998000 ( 0.998576)

Benchmark FFI File.umask(0777) performance, 100000x
0.942000 0.000000 0.942000 ( 0.941961)
FFI File.umask implements the same logic as File.umask - but the FFI version is pure ruby - and File.umask is implemented in java.

Why? Well, although both FFI and jna-posix currently both use JNA to call native functions, the FFI path avoids boxing up the arguments and calling via a java reflection Proxy.

The savings are enough that we can throw some ruby code into the method to make it behave exactly like File.umask should, and still be faster than the java implemented File.umask.

Thats with the same native backend. If we switch out the backend to use JFFI instead of JNA, we get:

Benchmark FFI File.umask(0777) performance, 100000x
0.382000 0.000000 0.382000 ( 0.381865)

Twice as fast, no code changes required.

Noice.

Here is the code for the FFI File.umask implementation:

module FFIFile
extend FFI::Library
if JRuby::FFI::Platform::IS_WINDOWS
attach_function :_umask, '_umask', [ :int ], :int
else
attach_function :_umask, 'umask', [ :int ], :int
end
def self.umask(mask = nil)
if mask
_umask(mask)
else
old = _umask(0)
_umask(old)
old
end
end
end