Paul Joseph Davis

Erlang NIF Test
===============

    *NOTE* This tutorial will only work for Erlang R13B03. R13B04 requires a
    newer syntax which I'll get around to blogging soonish, but you can find
    an example at [1] if you need it nowish.

Erlang NIF's
------------

I've been waiting excitedly for the new Natively Implemented Function (NIF)
interface to land in the next Erlang release since I first saw them announced
[2]. Then I saw another message [3] form @dizzyco [4] that was more specific
[5]. So I did what any normal person would do. Read the test suite and wrote a
minimal NIF to figure out the compiling and call semantics.


The NIF Module C API
--------------------

The first thing to note is that a NIF module has four callbacks that are used
for bookkeeping with loading the shared library code: load, reload, upgrade, and
unload. Each function gets an ErlNifEnv* argument, a pointer to some driver
specific data, and (except unload) an ERL_NIF_TERM load_info argument. The
environment and private data pointers are pretty standard for this sort of
thing. I'm not entirely certain what load_info is for. The method for
initializing NIF modules takes a second parameter which may be what this is for,
but I haven't investigated to find out for certain.

After defining each of those four methods, to actually implement the NIF
functions we define a function that takes an ErlNifEnv* argument and zero or
more positional parameters of type ERL_NIF_TERM. These functions will show up in
the Erlang side and can be called as expected.

The code for our minimal NIF module looks like this:

    // mynif.c
    #include 
    #include "erl_nif.h"
    
    static int
    load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
    {
        return 0;
    }
    
    static int
    reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
    {
        return 0;
    }
    
    static int
    upgrade(ErlNifEnv* env, void** priv, void** old_priv,
              ERL_NIF_TERM load_info)
    {
        return 0;
    }
    
    static void
    unload(ErlNifEnv* env, void* priv)
    {
        return;
    }
    
    static ERL_NIF_TERM
    do_something(ErlNifEnv* env, ERL_NIF_TERM a1)
    {
        unsigned long val;
        if(!enif_get_ulong(env, a1, &val)) {
            return enif_make_badarg(env);
        } else {
            return enif_make_ulong(env, val*2);
        }
    }
    
    static ErlNifFunc mynif_funcs[] =
    {
        {"do_something", 1, do_something}
    };
    
    ERL_NIF_INIT(mynif, mynif_funcs, load, reload, upgrade, unload)
    // EOF

That's all fairly straight forward. Define the four required functions and just
return 0 to indicate no error. The ErlNifFunc structure appears to be a triple
of {name_in_erlang, arity, name_in_c} calls. There's an example in the
source on having the same Erlang name and different arities. As you'd expect,
you just specify the same string, and change the second value.

The implementation of do_something shows a basic error when the argument is not
an unsigned long. We'll test that this works as expected later.

The Erlang API
--------------

The Erlang side is pretty simple as well. To load a NIF module we just call
erlang:load_nif/2. The first parameter is the path to the shared object to
load. The second parameter I just specify as 0 to follow the test code, I've not
investigated its use though I assume it shows up in the load_info argument in
the module API.

Another thing to note is that the NIF module and its corresponding Erlang module
have overlapping function namespaces. When we define a function in the NIF
module, it shows up in our Erlang module. The tests use a pattern to throw an
error if the Erlang function gets called. In other words, when we load the NIF
module it replaces the Erlang definition, so if we hit the Erlang definition we
report an error.

Our Erlang code looks like this:

    // mynif.erl
    -module(mynif).
    -export([start/0, do_something/1]).
    
    start() ->
        erlang:load_nif("mynif", 0).
    
    do_something(_Val) ->
        nif_error(?LINE).    
    
    nif_error(Line) ->
        exit({nif_not_loaded,module,?MODULE,line,Line}).
    // EOF

Building the Modules
--------------------

Building appears to just be the standard shared object style. I happened to have
an example lying around from my earlier work on an EEP0018 module (which I'll
definitely be revisiting now). The linker dark magic is outside this simple
example, but there are plenty of places that will explain this. I haven't tested
the Linux flags, but they should work just fine.

    // Makefile
    OTPROOT=/Users/davisp/tmp/otp_src_R13B03/
    INCLUDES = -I$(OTPROOT)/erts/emulator/beam/
    
    # OS X flags.
    GCCFLAGS = -O3 -fPIC -bundle -flat_namespace -undefined suppress -fno-common -Wall
    
    # Linux Flags
    #GCCFLAGS = -O3 -fPIC -shared -fno-common -Wall
    
    CFLAGS = $(GCCFLAGS) $(INCLUDES)
    LDFLAGS = $(GCCFLAGS) $(LIBS)
    
    OBJECTS = mynif.o
    
    DRIVER = mynif.so
    BEAM = mynif.beam
    
    all: $(DRIVER) $(BEAM)
    
    clean: 
    	rm -f *.o *.beam $(DRIVER)
      
    $(DRIVER): $(OBJECTS)
    	gcc -o $@ $^ $(LDFLAGS)
      
    $(BEAM): mynif.erl
    	erlc $^
    # EOF

With all three of those files in your $CWD you should be able to just run `make`
and have the proper output in the same directory.

Running the Example
-------------------

A sample console log to show that it behaves as expected:

    $ ~/tmp/otp_src_R13B03/bin/erl
    Erlang R13B03 (erts-5.7.4) [source] [smp:2:2] [rq:2] [async-threads:0] [kernel-poll:false]

    Eshell V5.7.4  (abort with ^G)
    1> mynif:start().
    ok
    2> mynif:do_something(0).
    0
    3> mynif:do_something(2).
    4
    4> mynif:do_something(nil).
    ** exception error: bad argument
         in function  mynif:do_something/1
            called as mynif:do_something(nil)
    5> mynif:do_something(2.3). 
    ** exception error: bad argument
         in function  mynif:do_something/1
            called as mynif:do_something(2.3)

And there you have it. This is fairly exciting stuff. I've already got a list of
projects I'm going to play with integrating into the NIF API to see what type of
speedups I can get.

References
----------

[1]:  http://github.com/davisp/emonk.git
[2]:  http://twitter.com/FrancescoC/status/5651602607
[3]:  http://twitter.com/dizzyco/status/5889832914
[4]:  http://twitter.com/dizzyco/status/5891652969
[5]:  http://twitter.com/dizzyco

Copyright Notice
----------------

Copyright 2008-2010 Paul Joseph Davis

License
-------

http://creativecommons.org/licenses/by/3.0/