This tutorial requires a fairly recent snapshot of Erlang. I'm using the snapshot from November 22nd, 2009. The official release containing the required functionality is slated to be out on November 25th, 2009. You'll see I haven't actually installed the snapshot build in case you're like me and want to wait for an official release.
No more NIF's! The daily snapshot page was updated and the new tarball for today doesn't include the new NIF functions. Luckily there are public mirrors of the code.
$ git clone git://github.com/janl/erlang0d.git otp_src_R13B03
$ cd otp_src_R13B03
$ git checkout origin/R13B03-20091122225501
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. Then I saw another message form @dizzyco that was more specific. 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 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 <stdio.h>
#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)
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 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}).
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.
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.