1 // Copyright (c) 2014 Johannes Huning <mail@johanneshuning.com>
2 // Copyright (c) 2015 Christian Lundgren, Chris de Vries, and Jon Elverkilde
3 //
4 // Permission is hereby granted, free of charge, to any person
5 // obtaining a copy of this software and associated documentation
6 // files (the "Software"), to deal in the Software without
7 // restriction, including without limitation the rights to use, copy,
8 // modify, merge, publish, distribute, sublicense, and/or sell copies
9 // of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 // SOFTWARE.
23 //
24 // Format this file with
25 // indent -kr -i8 -sob c_src/hyper_carray.c
26 // before committing.
27
28 #include <stdint.h>
29 #include <stdio.h>
30 #include <string.h>
31
32 #include "erl_nif.h"
33
34 /*
35 * Erlang NIF resource type used to 'tag' hyper_carrays.
36 */
37 static ErlNifResourceType *carray_resource;
38
39 /*
40 * Single resource produced and consumed by these NIFs.
41 * Header placed directly in front of the items it points to.
42 */
43 struct hyper_carray {
44 /*
45 * Precision = log_2(size). Handy to have it.
46 */
47 unsigned int precision;
48 /*
49 * Number of items.
50 */
51 unsigned int size;
52 /*
53 * Array of items each one byte in size.
54 */
55 uint8_t *items;
56 };
57
58 #ifdef _WIN32
59 typedef struct hyper_carray *carray_ptr;
60 #else
61 typedef struct hyper_carray *restrict carray_ptr;
62 #endif
63
64 #define HYPER_CARRAY_SIZE sizeof(struct hyper_carray)
65
66 /*
67 * Attempts to read a hyper_carray from _term into _varname.
68 * Returns badarg on failure to do so.
69 */
70 #define HYPER_CARRAY_OR_BADARG(_term, _varname) \
71 void* _varname_res = NULL; \
72 if (!enif_get_resource(env, _term, carray_resource, &_varname_res)) \
73 return enif_make_badarg(env); \
74 _varname = _varname_res;
75
76 /*
77 * Allocate a new hyper_carray for use as an Erlang NIF resource.
78 */
carray_alloc(unsigned int precision,carray_ptr * arr)79 static void carray_alloc(unsigned int precision, carray_ptr * arr)
80 {
81 unsigned int nitems = 0x01 << precision;
82 size_t header_size = HYPER_CARRAY_SIZE;
83 size_t res_size = header_size + nitems;
84
85 void *res = enif_alloc_resource(carray_resource, res_size);
86 *arr = res;
87
88 memset(*arr, 0, header_size);
89 (*arr)->precision = precision;
90 (*arr)->size = nitems;
91 (*arr)->items = (uint8_t *) res + header_size;
92 }
93
94 /*
95 * Given an hyper_carray and a valid index, set the value at that index to
96 * max(current value, given value).
97 */
98 #ifdef _WIN32
carray_merge_item(carray_ptr arr,unsigned int index,unsigned int value)99 static void carray_merge_item(carray_ptr arr,
100 unsigned int index,
101 unsigned int value)
102 #else
103 static inline void carray_merge_item(carray_ptr arr,
104 unsigned int index,
105 unsigned int value)
106 #endif
107 {
108 uint8_t *item = arr->items + index;
109 *item = (value > *item) ? value : *item;
110 }
111
112 /*
113 * Create a new hyper_carray resource with all items set to 0.
114 */
new_hyper_carray(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])115 static ERL_NIF_TERM new_hyper_carray(ErlNifEnv * env, int argc,
116 const ERL_NIF_TERM argv[])
117 {
118 unsigned int precision = 0;
119 if (!enif_get_uint(env, argv[0], &precision))
120 return enif_make_badarg(env);
121
122 carray_ptr arr = NULL;
123 carray_alloc(precision, &arr);
124 memset(arr->items, 0, arr->size);
125
126 ERL_NIF_TERM erl_res = enif_make_resource(env, arr);
127 enif_release_resource(arr);
128 return erl_res;
129 }
130
131 /*
132 * NIF variant of carray_merge_item (see above).
133 */
set(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])134 static ERL_NIF_TERM set(ErlNifEnv * env, int argc,
135 const ERL_NIF_TERM argv[])
136 {
137 carray_ptr arr = NULL;
138 HYPER_CARRAY_OR_BADARG(argv[2], arr);
139
140 unsigned int index = 0;
141 unsigned int new_value = 0;
142 if (!enif_get_uint(env, argv[0], &index)
143 || !enif_get_uint(env, argv[1], &new_value))
144 return enif_make_badarg(env);
145
146 // Validate bounds
147 if (index > arr->size - 1)
148 return enif_make_badarg(env);
149
150 carray_merge_item(arr, index, new_value);
151
152 return argv[2];
153 }
154
155 void dtor(ErlNifEnv * env, void *obj);
156
157 /*
158 * Given a list of at least 1 hyper_carrays [A,B,...], merge into a single new
159 * hyper_carray N. Where the i-ths item N[i] is max(A[i], B[i], ...).
160 * A, B, and so on are assumed to be _different_ hyper_carrays.
161 */
max_merge(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])162 static ERL_NIF_TERM max_merge(ErlNifEnv * env, int argc,
163 const ERL_NIF_TERM argv[])
164 {
165 unsigned int narrays = 0;
166 ERL_NIF_TERM head;
167 ERL_NIF_TERM tail;
168
169 if (!enif_get_list_length(env, argv[0], &narrays)
170 || !enif_get_list_cell(env, argv[0], &head, &tail))
171 return enif_make_badarg(env);
172
173 if (narrays < 1)
174 return enif_make_badarg(env);
175
176 carray_ptr first = NULL;
177 HYPER_CARRAY_OR_BADARG(head, first);
178 const unsigned int nitems = first->size;
179
180 carray_ptr acc = NULL;
181 carray_alloc(first->precision, &acc);
182 memcpy(acc->items, first->items, acc->size);
183
184 // Merge arrays
185 for (int i = 1; i < narrays; ++i) {
186 carray_ptr curr = NULL;
187
188 if (!enif_get_list_cell(env, tail, &head, &tail)
189 || !enif_get_resource(env, head, carray_resource,
190 (void *) &curr))
191 goto dealloc_and_badarg;
192
193 // Require uniform precision.
194 if (curr->precision != acc->precision)
195 goto dealloc_and_badarg;
196
197 for (uint8_t * accitem = acc->items, *item = curr->items,
198 *enditem = curr->items + nitems;
199 item != enditem; ++item, ++accitem) {
200 *accitem = (*item > *accitem) ? *item : *accitem;
201 }
202
203 continue;
204
205 dealloc_and_badarg:
206 dtor(env, acc);
207 return enif_make_badarg(env);
208 }
209
210 ERL_NIF_TERM erl_res = enif_make_resource(env, acc);
211 enif_release_resource(acc);
212 return erl_res;
213 }
214
215 /*
216 * Return the total number of bytes allocated for the given hyper_carray.
217 * Includes the header's size.
218 */
bytes(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])219 static ERL_NIF_TERM bytes(ErlNifEnv * env, int argc,
220 const ERL_NIF_TERM argv[])
221 {
222 carray_ptr arr = NULL;
223 HYPER_CARRAY_OR_BADARG(argv[0], arr);
224 return enif_make_int(env, HYPER_CARRAY_SIZE + arr->size);
225 }
226
227 /*
228 * Sum over 2^-X where X is the value of each item in the given hyper_carray.
229 */
register_sum(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])230 static ERL_NIF_TERM register_sum(ErlNifEnv * env, int argc,
231 const ERL_NIF_TERM argv[])
232 {
233 carray_ptr arr = NULL;
234 HYPER_CARRAY_OR_BADARG(argv[0], arr);
235
236 int currval = 0;
237 double sum = 0.0;
238 unsigned int size = arr->size;
239
240 for (int i = 0; i < size; ++i) {
241 currval = arr->items[i];
242 sum += 1.0 / (double) (0x01 << currval);
243 }
244
245 return enif_make_double(env, sum);
246 }
247
248 /*
249 * Number of items with a 0 as value;
250 */
zero_count(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])251 static ERL_NIF_TERM zero_count(ErlNifEnv * env, int argc,
252 const ERL_NIF_TERM argv[])
253 {
254 carray_ptr arr = NULL;
255 HYPER_CARRAY_OR_BADARG(argv[0], arr);
256
257 unsigned int nzeros = 0;
258 unsigned int size = arr->size;
259
260 for (int i = 0; i < size; ++i) {
261 if (arr->items[i] == 0)
262 ++nzeros;
263 }
264
265 return enif_make_int(env, nzeros);
266 }
267
268 /*
269 * Encode the given hyper_carray as an Erlang binary.
270 */
encode_registers(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])271 static ERL_NIF_TERM encode_registers(ErlNifEnv * env, int argc,
272 const ERL_NIF_TERM argv[])
273 {
274 carray_ptr arr = NULL;
275 HYPER_CARRAY_OR_BADARG(argv[0], arr);
276
277 size_t nbytes = arr->size;
278
279 ERL_NIF_TERM bin;
280 unsigned char *buf = enif_make_new_binary(env, nbytes, &bin);
281 memcpy(buf, arr->items, nbytes);
282
283 return bin;
284 }
285
286 /*
287 * Decode the given serialized hyper_carray into a new resource.
288 */
decode_registers(ErlNifEnv * env,int argc,const ERL_NIF_TERM argv[])289 static ERL_NIF_TERM decode_registers(ErlNifEnv * env, int argc,
290 const ERL_NIF_TERM argv[])
291 {
292 unsigned int precision = 0;
293 ErlNifBinary bin;
294
295 if (!enif_get_uint(env, argv[1], &precision)
296 || !enif_inspect_binary(env, argv[0], &bin))
297 return enif_make_badarg(env);
298
299 carray_ptr arr = NULL;
300 carray_alloc(precision, &arr);
301 memcpy(arr->items, bin.data, arr->size);
302
303 ERL_NIF_TERM erl_res = enif_make_resource(env, arr);
304 enif_release_resource(arr);
305
306 return erl_res;
307 }
308
309 /*
310 * Map of funs to NIFs.
311 */
312 static ErlNifFunc niftable[] = {
313 {"new", 1, new_hyper_carray},
314 {"set", 3, set},
315 {"max_merge", 1, max_merge},
316 {"bytes", 1, bytes},
317 {"register_sum", 1, register_sum},
318 {"zero_count", 1, zero_count},
319 {"encode_registers", 1, encode_registers},
320 {"decode_registers", 2, decode_registers}
321 };
322
323 /*
324 * Destructor for hyper_carray resources.
325 */
dtor(ErlNifEnv * env,void * obj)326 void dtor(ErlNifEnv * env, void *obj)
327 {
328 enif_release_resource(obj);
329 }
330
331 /*
332 * Creates or opens the hyper_carray resource _type_.
333 * Registers dtor to be called on garbage collection of hyper_carrays.
334 * Please see http://www.erlang.org/doc/man/erl_nif.html.
335 */
load(ErlNifEnv * env,void ** priv_data,ERL_NIF_TERM load_info)336 static int load(ErlNifEnv * env, void **priv_data, ERL_NIF_TERM load_info)
337 {
338 carray_resource =
339 enif_open_resource_type(env, NULL, "hyper_carray", &dtor,
340 ERL_NIF_RT_CREATE |
341 ERL_NIF_RT_TAKEOVER, 0);
342 return 0;
343 }
344
345 /*
346 * Called when the NIF library is loaded and there is old code of this module
347 * with a loaded NIF library.
348 */
upgrade(ErlNifEnv * env,void ** priv,void ** old_priv,ERL_NIF_TERM load_info)349 static int upgrade(ErlNifEnv * env, void **priv, void **old_priv,
350 ERL_NIF_TERM load_info)
351 {
352 *priv = *old_priv;
353 return 0;
354 }
355
356 /*
357 * Initialize the NIF library.
358 */
359 ERL_NIF_INIT(hyper_carray, niftable, &load, NULL, &upgrade, NULL);
360