Compiling to C
hkc
is a command line tool to compiler Hakaru programs to C. HKC was
created with portability and speed in mind. More recently, OpenMP support is
being added to gain more performance on multi-core machines. Basic command line
usage of HKC is much like other compilers:
hkc foo.hk -o foo.c
It is possible to go straight to an executable with the --make ARG
flag, where
the argument is the C compiler you would like to use.
Type Conversions
The types available in Hakaru programs are the following: nat
, int
, real
,
prob
, array(<type>)
, measure(<type>)
, and datum like true
and false
.
nat
and int
have a trivial mapping to the C int
type. real
becomes a C
double
. The prob
type in Hakaru is stored in the log-domain to avoid
underflow. In C this corresponds to a double
, but we first take the log of it
before storing it, so we have to take the exp of it to bring it back to the real
numbers.
Arrays become structs that contain the size and a pointer to data stored within. The structs are generated at compile time, but there are only four which are named after the type they contain. Here they all are:
struct arrayNat {
int size; int * data;
};
struct arrayInt {
int size; int * data;
};
struct arrayReal {
int size; double * data;
};
struct arrayProb {
int size; double * data;
};
Measures
Measures compile to C functions that take a location for a sample, return the
weight of the measure and store a sample in the location is was given. A simple
example is uniform(0,1)
a measure over type real
.
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
double measure(double * s_a)
{
*s_a = ((double)0) + ((double)rand()) / ((double)RAND_MAX) * ((double)1) - ((double)0);
return 0;
}
int main()
{
double sample;
while (1)
{
measure(&sample);
printf("%.17f\n",sample);
}
return 0;
}
Recall that weights have type prob
and are stored in the log-domain. This
example has a weight of 1.
Calling hkc
on a measure will create a function like the one above and also a
main function that infinitely takes samples. Using hkc -F ARG
will produce
just the function with the name of its argument.
Lambdas
Lambdas compile to functions in C:
fn x array(real):
(summate i from 0 to size(x): x[i])
*
prob2real(recip(nat2prob((size(x) + 1))))
Becomes:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
struct arrayReal {
int size; double * data;
};
double fn_a(struct arrayReal x_b)
{
unsigned int i_c;
double acc_d;
double p_e;
double _f;
double r_g;
acc_d = 0;
for (i_c = 0; i_c < x_b.size; i_c++)
{
acc_d += *(x_b.data + i_c);
}
p_e = log1p(((1 + x_b.size) - 1));
_f = -p_e;
r_g = (expm1(_f) + 1);
return (r_g * acc_d);
}
Using the -F
flag will allow the user to add their own name to a function,
otherwise the name is chosen automatically as fn_<unique identifier>
.
Computations
When compiling a computation, HKC just creates a main function to compute the value and print it. For example:
summate i from 1 to 100000000:
nat2real(i) / nat2real(i)
becomes:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int main()
{
double result;
int i_a;
double acc_b;
double _c;
acc_b = 0;
for (i_a = 1; i_a < 100000000; i_a++)
{
_c = (1 / ((double)i_a));
acc_b += (_c * ((double)i_a));
}
result = acc_b;
printf("%.17f\n",result);
return 0;
}
Parallel Programs
Calling HKC with the -j
flag will generate the code with parallel regions to
compute the value. The parallel code uses OpenMP directives. To check if you’re
compiler supports OpenMP, check here.
For example, GCC requires the -fopenmp
flag for OpenMP support:
hkc -j foo.hk -o foo.c
gcc -lm -fopenmp foo.c -o foo.bin