1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson AB 2018. All Rights Reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * %CopyrightEnd%
19  */
20 
21 /*
22  * Purpose:  High performance atomics.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #  include "config.h"
27 #endif
28 
29 #include <stddef.h> /* offsetof */
30 
31 #include "sys.h"
32 #include "export.h"
33 #include "bif.h"
34 #include "erl_threads.h"
35 #include "big.h"
36 #include "erl_binary.h"
37 #include "erl_bif_unique.h"
38 #include "erl_map.h"
39 
40 typedef struct
41 {
42     int is_signed;
43     UWord vlen;
44     erts_atomic64_t v[1];
45 }AtomicsRef;
46 
atomics_destructor(Binary * unused)47 static int atomics_destructor(Binary *unused)
48 {
49     return 1;
50 }
51 
52 #define OPT_SIGNED (1 << 0)
53 
erts_internal_atomics_new_2(BIF_ALIST_2)54 BIF_RETTYPE erts_internal_atomics_new_2(BIF_ALIST_2)
55 {
56     AtomicsRef* p;
57     Binary* mbin;
58     UWord i, cnt, opts;
59     Uint bytes;
60     Eterm* hp;
61 
62     if (!term_to_UWord(BIF_ARG_1, &cnt)) {
63         BIF_ERROR(BIF_P, cnt);
64     }
65     if (cnt == 0) {
66         BIF_ERROR(BIF_P, BADARG);
67     }
68     if (!term_to_UWord(BIF_ARG_2, &opts)) {
69         BIF_ERROR(BIF_P, opts);
70     }
71 
72     if (cnt > (ERTS_UWORD_MAX / sizeof(p->v[0])))
73         BIF_ERROR(BIF_P, SYSTEM_LIMIT);
74 
75     bytes = offsetof(AtomicsRef, v) + cnt*sizeof(p->v[0]);
76     mbin = erts_create_magic_binary_x(bytes,
77                                       atomics_destructor,
78                                       ERTS_ALC_T_ATOMICS,
79                                       0);
80     p = ERTS_MAGIC_BIN_DATA(mbin);
81     p->is_signed = opts & OPT_SIGNED;
82     p->vlen = cnt;
83     for (i=0; i < cnt; i++)
84         erts_atomic64_init_nob(&p->v[i], 0);
85     hp = HAlloc(BIF_P, ERTS_MAGIC_REF_THING_SIZE);
86     return erts_mk_magic_ref(&hp, &MSO(BIF_P), mbin);
87 }
88 
get_ref(Eterm ref,AtomicsRef ** pp)89 static ERTS_INLINE int get_ref(Eterm ref, AtomicsRef** pp)
90 {
91     Binary* mbin;
92     if (!is_internal_magic_ref(ref))
93         return 0;
94 
95     mbin = erts_magic_ref2bin(ref);
96     if (ERTS_MAGIC_BIN_DESTRUCTOR(mbin) != atomics_destructor)
97         return 0;
98     *pp = ERTS_MAGIC_BIN_DATA(mbin);
99     return 1;
100 }
101 
get_ref_ix(Eterm ref,Eterm ix,AtomicsRef ** pp,UWord * ixp)102 static ERTS_INLINE int get_ref_ix(Eterm ref, Eterm ix,
103                                   AtomicsRef** pp, UWord* ixp)
104 {
105     return (get_ref(ref, pp)
106             && term_to_UWord(ix, ixp)
107             && --(*ixp) < (*pp)->vlen);
108 }
109 
get_value(AtomicsRef * p,Eterm term,erts_aint64_t * valp)110 static ERTS_INLINE int get_value(AtomicsRef* p, Eterm term, erts_aint64_t *valp)
111 {
112     return (p->is_signed ?
113             term_to_Sint64(term, (Sint64*)valp) :
114             term_to_Uint64(term, (Uint64*)valp));
115 }
116 
get_incr(AtomicsRef * p,Eterm term,erts_aint64_t * valp)117 static ERTS_INLINE int get_incr(AtomicsRef* p, Eterm term, erts_aint64_t *valp)
118 {
119     return (term_to_Sint64(term, (Sint64*)valp)
120             || term_to_Uint64(term, (Uint64*)valp));
121 }
122 
bld_atomic(Process * proc,AtomicsRef * p,erts_aint64_t val)123 static ERTS_INLINE Eterm bld_atomic(Process* proc, AtomicsRef* p,
124                                     erts_aint64_t val)
125 {
126     if (p->is_signed) {
127         if (IS_SSMALL(val))
128             return make_small((Sint) val);
129         else {
130             Uint hsz = ERTS_SINT64_HEAP_SIZE(val);
131             Eterm* hp = HAlloc(proc, hsz);
132             return erts_sint64_to_big(val, &hp);
133         }
134     }
135     else {
136         if ((Uint64)val <= MAX_SMALL)
137             return make_small((Sint) val);
138         else {
139             Uint hsz = ERTS_UINT64_HEAP_SIZE((Uint64)val);
140             Eterm* hp = HAlloc(proc, hsz);
141             return erts_uint64_to_big(val, &hp);
142         }
143     }
144 }
145 
atomics_put_3(BIF_ALIST_3)146 BIF_RETTYPE atomics_put_3(BIF_ALIST_3)
147 {
148     AtomicsRef* p;
149     UWord ix;
150     erts_aint64_t val;
151 
152     if (!get_ref_ix(BIF_ARG_1, BIF_ARG_2, &p, &ix)
153         || !get_value(p, BIF_ARG_3, &val)) {
154         BIF_ERROR(BIF_P, BADARG);
155     }
156     erts_atomic64_set_mb(&p->v[ix], val);
157     return am_ok;
158 }
159 
atomics_get_2(BIF_ALIST_2)160 BIF_RETTYPE atomics_get_2(BIF_ALIST_2)
161 {
162     AtomicsRef* p;
163     UWord ix;
164 
165     if (!get_ref_ix(BIF_ARG_1, BIF_ARG_2, &p, &ix)) {
166         BIF_ERROR(BIF_P, BADARG);
167     }
168     return bld_atomic(BIF_P, p, erts_atomic64_read_mb(&p->v[ix]));
169 }
170 
atomics_add_3(BIF_ALIST_3)171 BIF_RETTYPE atomics_add_3(BIF_ALIST_3)
172 {
173     AtomicsRef* p;
174     UWord ix;
175     erts_aint64_t incr;
176 
177     if (!get_ref_ix(BIF_ARG_1, BIF_ARG_2, &p, &ix)
178         || !get_incr(p, BIF_ARG_3, &incr)) {
179         BIF_ERROR(BIF_P, BADARG);
180     }
181     erts_atomic64_add_mb(&p->v[ix], incr);
182     return am_ok;
183 }
184 
atomics_add_get_3(BIF_ALIST_3)185 BIF_RETTYPE atomics_add_get_3(BIF_ALIST_3)
186 {
187     AtomicsRef* p;
188     UWord ix;
189     erts_aint64_t incr;
190 
191     if (!get_ref_ix(BIF_ARG_1, BIF_ARG_2, &p, &ix)
192         || !get_incr(p, BIF_ARG_3, &incr)) {
193         BIF_ERROR(BIF_P, BADARG);
194     }
195     return bld_atomic(BIF_P, p, erts_atomic64_add_read_mb(&p->v[ix], incr));
196 }
197 
atomics_exchange_3(BIF_ALIST_3)198 BIF_RETTYPE atomics_exchange_3(BIF_ALIST_3)
199 {
200     AtomicsRef* p;
201     UWord ix;
202     erts_aint64_t desired, was;
203 
204     if (!get_ref_ix(BIF_ARG_1, BIF_ARG_2, &p, &ix)
205         || !get_value(p, BIF_ARG_3, &desired)) {
206         BIF_ERROR(BIF_P, BADARG);
207     }
208     was = erts_atomic64_xchg_mb(&p->v[ix], desired);
209     return bld_atomic(BIF_P, p, was);
210 }
211 
atomics_compare_exchange_4(BIF_ALIST_4)212 BIF_RETTYPE atomics_compare_exchange_4(BIF_ALIST_4)
213 {
214     AtomicsRef* p;
215     UWord ix;
216     erts_aint64_t expected, desired, was;
217 
218     if (!get_ref_ix(BIF_ARG_1, BIF_ARG_2, &p, &ix)
219         || !get_value(p, BIF_ARG_3, &expected)
220         || !get_value(p, BIF_ARG_4, &desired)) {
221         BIF_ERROR(BIF_P, BADARG);
222     }
223     was = erts_atomic64_cmpxchg_mb(&p->v[ix], desired, expected);
224     return was == expected ? am_ok : bld_atomic(BIF_P, p, was);
225 }
226 
atomics_info_1(BIF_ALIST_1)227 BIF_RETTYPE atomics_info_1(BIF_ALIST_1)
228 {
229     AtomicsRef* p;
230     Uint hsz = MAP4_SZ;
231     Eterm *hp;
232     Uint64 max;
233     Sint64 min;
234     UWord memory;
235     Eterm max_val, min_val, sz_val, mem_val;
236 
237     if (!get_ref(BIF_ARG_1, &p))
238         BIF_ERROR(BIF_P, BADARG);
239 
240     max = p->is_signed ? ERTS_SINT64_MAX : ERTS_UINT64_MAX;
241     min = p->is_signed ? ERTS_SINT64_MIN : 0;
242     memory = erts_magic_ref2bin(BIF_ARG_1)->orig_size;
243 
244     erts_bld_uint64(NULL, &hsz, max);
245     erts_bld_sint64(NULL, &hsz, min);
246     erts_bld_uword(NULL, &hsz, p->vlen);
247     erts_bld_uword(NULL, &hsz, memory);
248 
249     hp = HAlloc(BIF_P, hsz);
250     max_val = erts_bld_uint64(&hp, NULL, max);
251     min_val = erts_bld_sint64(&hp, NULL, min);
252     sz_val  = erts_bld_uword(&hp, NULL, p->vlen);
253     mem_val = erts_bld_uword(&hp, NULL, memory);
254 
255     return MAP4(hp, am_max, max_val,
256                 am_memory, mem_val,
257                 am_min, min_val,
258                 am_size, sz_val);
259 }
260