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