• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Alyssa Rosenzweig
3  * Copyright 2019 Collabora, Ltd.
4  * SPDX-License-Identifier: MIT
5  */
6 
7 #include "agx_bo.h"
8 #include <inttypes.h>
9 #include <stdlib.h>
10 #include "util/hash_table.h"
11 #include "util/ralloc.h"
12 #include "agx_device.h"
13 #include "decode.h"
14 
15 /* Helper to calculate the bucket index of a BO */
16 static unsigned
agx_bucket_index(size_t size)17 agx_bucket_index(size_t size)
18 {
19    /* Round down to POT to compute a bucket index */
20    unsigned bucket_index = util_logbase2(size);
21 
22    /* Clamp to supported buckets. Huge allocations use the largest bucket */
23    bucket_index = CLAMP(bucket_index, MIN_BO_CACHE_BUCKET, MAX_BO_CACHE_BUCKET);
24 
25    /* Reindex from 0 */
26    return (bucket_index - MIN_BO_CACHE_BUCKET);
27 }
28 
29 static struct list_head *
agx_bucket(struct agx_device * dev,size_t size)30 agx_bucket(struct agx_device *dev, size_t size)
31 {
32    return &dev->bo_cache.buckets[agx_bucket_index(size)];
33 }
34 
35 static void
agx_bo_cache_remove_locked(struct agx_device * dev,struct agx_bo * bo)36 agx_bo_cache_remove_locked(struct agx_device *dev, struct agx_bo *bo)
37 {
38    simple_mtx_assert_locked(&dev->bo_cache.lock);
39    list_del(&bo->bucket_link);
40    list_del(&bo->lru_link);
41    dev->bo_cache.size -= bo->size;
42 }
43 
44 /* Tries to fetch a BO of sufficient size with the appropriate flags from the
45  * BO cache. If it succeeds, it returns that BO and removes the BO from the
46  * cache. If it fails, it returns NULL signaling the caller to allocate a new
47  * BO. */
48 
49 struct agx_bo *
agx_bo_cache_fetch(struct agx_device * dev,size_t size,size_t align,uint32_t flags,const bool dontwait)50 agx_bo_cache_fetch(struct agx_device *dev, size_t size, size_t align,
51                    uint32_t flags, const bool dontwait)
52 {
53    simple_mtx_lock(&dev->bo_cache.lock);
54    struct list_head *bucket = agx_bucket(dev, size);
55    struct agx_bo *bo = NULL;
56 
57    /* Iterate the bucket looking for something suitable */
58    list_for_each_entry_safe(struct agx_bo, entry, bucket, bucket_link) {
59       if (entry->size < size || entry->flags != flags)
60          continue;
61 
62       /* Do not return more than 2x oversized BOs. */
63       if (entry->size > 2 * size)
64          continue;
65 
66       if (align > entry->align)
67          continue;
68 
69       /* This one works, use it */
70       agx_bo_cache_remove_locked(dev, entry);
71       bo = entry;
72       break;
73    }
74    simple_mtx_unlock(&dev->bo_cache.lock);
75 
76    return bo;
77 }
78 
79 static void
agx_bo_cache_evict_stale_bos(struct agx_device * dev,unsigned tv_sec)80 agx_bo_cache_evict_stale_bos(struct agx_device *dev, unsigned tv_sec)
81 {
82    struct timespec time;
83 
84    clock_gettime(CLOCK_MONOTONIC, &time);
85    list_for_each_entry_safe(struct agx_bo, entry, &dev->bo_cache.lru,
86                             lru_link) {
87       /* We want all entries that have been used more than 1 sec ago to be
88        * dropped, others can be kept.  Note the <= 2 check and not <= 1. It's
89        * here to account for the fact that we're only testing ->tv_sec, not
90        * ->tv_nsec.  That means we might keep entries that are between 1 and 2
91        * seconds old, but we don't really care, as long as unused BOs are
92        * dropped at some point.
93        */
94       if (time.tv_sec - entry->last_used <= 2)
95          break;
96 
97       agx_bo_cache_remove_locked(dev, entry);
98       agx_bo_free(dev, entry);
99    }
100 }
101 
102 static void
agx_bo_cache_put_locked(struct agx_device * dev,struct agx_bo * bo)103 agx_bo_cache_put_locked(struct agx_device *dev, struct agx_bo *bo)
104 {
105    struct list_head *bucket = agx_bucket(dev, bo->size);
106    struct timespec time;
107 
108    /* Add us to the bucket */
109    list_addtail(&bo->bucket_link, bucket);
110 
111    /* Add us to the LRU list and update the last_used field. */
112    list_addtail(&bo->lru_link, &dev->bo_cache.lru);
113    clock_gettime(CLOCK_MONOTONIC, &time);
114    bo->last_used = time.tv_sec;
115 
116    /* Update statistics */
117    dev->bo_cache.size += bo->size;
118 
119    if (0) {
120       printf("BO cache: %zu KiB (+%zu KiB from %s, hit/miss %" PRIu64
121              "/%" PRIu64 ")\n",
122              DIV_ROUND_UP(dev->bo_cache.size, 1024),
123              DIV_ROUND_UP(bo->size, 1024), bo->label,
124              p_atomic_read(&dev->bo_cache.hits),
125              p_atomic_read(&dev->bo_cache.misses));
126    }
127 
128    /* Update label for debug */
129    bo->label = "Unused (BO cache)";
130 
131    /* Let's do some cleanup in the BO cache while we hold the lock. */
132    agx_bo_cache_evict_stale_bos(dev, time.tv_sec);
133 }
134 
135 /* Tries to add a BO to the cache. Returns if it was successful */
136 static bool
agx_bo_cache_put(struct agx_device * dev,struct agx_bo * bo)137 agx_bo_cache_put(struct agx_device *dev, struct agx_bo *bo)
138 {
139    if (bo->flags & AGX_BO_SHARED) {
140       return false;
141    } else {
142       simple_mtx_lock(&dev->bo_cache.lock);
143       agx_bo_cache_put_locked(dev, bo);
144       simple_mtx_unlock(&dev->bo_cache.lock);
145 
146       return true;
147    }
148 }
149 
150 void
agx_bo_cache_evict_all(struct agx_device * dev)151 agx_bo_cache_evict_all(struct agx_device *dev)
152 {
153    simple_mtx_lock(&dev->bo_cache.lock);
154    for (unsigned i = 0; i < ARRAY_SIZE(dev->bo_cache.buckets); ++i) {
155       struct list_head *bucket = &dev->bo_cache.buckets[i];
156 
157       list_for_each_entry_safe(struct agx_bo, entry, bucket, bucket_link) {
158          agx_bo_cache_remove_locked(dev, entry);
159          agx_bo_free(dev, entry);
160       }
161    }
162    simple_mtx_unlock(&dev->bo_cache.lock);
163 }
164 
165 void
agx_bo_reference(struct agx_bo * bo)166 agx_bo_reference(struct agx_bo *bo)
167 {
168    if (bo) {
169       ASSERTED int count = p_atomic_inc_return(&bo->refcnt);
170       assert(count != 1);
171    }
172 }
173 
174 struct label_stat {
175    const char *label;
176    uint32_t count;
177    size_t alloc_B;
178    size_t mapped_B;
179 };
180 
181 static void
account_bo(struct label_stat * stat,struct agx_bo * bo)182 account_bo(struct label_stat *stat, struct agx_bo *bo)
183 {
184    stat->count++;
185    stat->alloc_B += bo->size;
186 
187    if (bo->_map != NULL)
188       stat->mapped_B += bo->size;
189 }
190 
191 static void
print_size(FILE * fp,size_t size_B)192 print_size(FILE *fp, size_t size_B)
193 {
194    if (size_B >= (1024 * 1024 * 1024)) {
195       fprintf(fp, "%.1f GiB", (double)size_B / (double)(1024 * 1024 * 1024));
196    } else if (size_B >= (1024 * 1024)) {
197       fprintf(fp, "%.1f MiB", (double)size_B / (double)(1024 * 1024));
198    } else if (size_B >= 1024) {
199       fprintf(fp, "%zu KiB", DIV_ROUND_UP(size_B, 1024));
200    } else {
201       fprintf(fp, "%zu B", size_B);
202    }
203 }
204 
205 static void
print_stat(FILE * fp,struct label_stat * stat)206 print_stat(FILE *fp, struct label_stat *stat)
207 {
208    const char *BOLD = "\e[1m";
209    const char *RESET = "\e[0m";
210 
211    fprintf(fp, "%s%s%s: ", BOLD, stat->label, RESET);
212    print_size(fp, stat->alloc_B);
213 
214    if (stat->mapped_B) {
215       fprintf(fp, ", mapped ");
216       print_size(fp, stat->mapped_B);
217    }
218 
219    fprintf(fp, ", %u BOs\n", stat->count);
220 }
221 
222 static int
compare_size(const void * a_,const void * b_)223 compare_size(const void *a_, const void *b_)
224 {
225    const struct label_stat *const *label_a = a_;
226    const struct label_stat *const *label_b = b_;
227 
228    size_t a = (*label_a)->alloc_B;
229    size_t b = (*label_b)->alloc_B;
230 
231    return (a > b) ? 1 : (a < b) ? -1 : 0;
232 }
233 
234 static void
agx_bo_dump_all(struct agx_device * dev)235 agx_bo_dump_all(struct agx_device *dev)
236 {
237    struct label_stat accum = {.label = "Total"};
238    struct hash_table *totals = _mesa_string_hash_table_create(NULL);
239    bool verbose = dev->debug & AGX_DBG_BODUMPVERBOSE;
240 
241    if (verbose)
242       fprintf(stderr, "---\n");
243 
244    for (uint32_t handle = 0; handle < dev->max_handle; handle++) {
245       struct agx_bo *bo = agx_lookup_bo(dev, handle);
246       if (!bo->size)
247          continue;
248 
249       if (verbose) {
250          fprintf(stderr, "%u: %s %zu KiB\n", handle, bo->label,
251                  bo->size / 1024);
252       }
253 
254       account_bo(&accum, bo);
255 
256       assert(bo->label != NULL && "Everything must be labeled");
257 
258       struct hash_entry *ent = _mesa_hash_table_search(totals, bo->label);
259       struct label_stat *ls;
260       if (ent != NULL) {
261          ls = ent->data;
262       } else {
263          ls = rzalloc(totals, struct label_stat);
264          ls->label = bo->label;
265          _mesa_hash_table_insert(totals, bo->label, ls);
266       }
267 
268       account_bo(ls, bo);
269    }
270 
271    if (verbose) {
272       fprintf(stderr, "\n");
273    }
274 
275    unsigned nr_labels = _mesa_hash_table_num_entries(totals);
276    struct label_stat **stats =
277       rzalloc_array(totals, struct label_stat *, nr_labels);
278    unsigned i = 0;
279 
280    hash_table_foreach(totals, ent) {
281       assert(i < nr_labels);
282       stats[i++] = ent->data;
283    }
284 
285    assert(i == nr_labels);
286 
287    /* Sort labels in ascending order of size */
288    qsort(stats, nr_labels, sizeof(struct label_stat *), compare_size);
289 
290    for (i = 0; i < nr_labels; ++i) {
291       print_stat(stderr, stats[i]);
292    }
293 
294    print_stat(stderr, &accum);
295 
296    if (verbose)
297       fprintf(stderr, "---\n\n");
298    else
299       fprintf(stderr, "\n");
300 
301    ralloc_free(totals);
302 }
303 
304 static void
agx_bo_dump_all_periodic(struct agx_device * dev)305 agx_bo_dump_all_periodic(struct agx_device *dev)
306 {
307    if (likely(!(dev->debug & (AGX_DBG_BODUMP | AGX_DBG_BODUMPVERBOSE))))
308       return;
309 
310    static time_t agx_last_dumped_time = 0;
311 
312    time_t now = time(NULL);
313    if (now == agx_last_dumped_time)
314       return;
315 
316    agx_bo_dump_all(dev);
317    agx_last_dumped_time = now;
318 }
319 
320 void
agx_bo_unreference(struct agx_device * dev,struct agx_bo * bo)321 agx_bo_unreference(struct agx_device *dev, struct agx_bo *bo)
322 {
323    if (!bo)
324       return;
325 
326    /* Don't return to cache if there are still references */
327    if (p_atomic_dec_return(&bo->refcnt))
328       return;
329 
330    pthread_mutex_lock(&dev->bo_map_lock);
331 
332    /* Someone might have imported this BO while we were waiting for the
333     * lock, let's make sure it's still not referenced before freeing it.
334     */
335    if (p_atomic_read(&bo->refcnt) == 0) {
336       assert(!p_atomic_read_relaxed(&bo->writer));
337 
338       if (dev->debug & AGX_DBG_TRACE)
339          agxdecode_track_free(dev->agxdecode, bo);
340 
341       if (!agx_bo_cache_put(dev, bo))
342          agx_bo_free(dev, bo);
343    }
344 
345    agx_bo_dump_all_periodic(dev);
346 
347    pthread_mutex_unlock(&dev->bo_map_lock);
348 }
349 
350 struct agx_bo *
agx_bo_create(struct agx_device * dev,size_t size,unsigned align,enum agx_bo_flags flags,const char * label)351 agx_bo_create(struct agx_device *dev, size_t size, unsigned align,
352               enum agx_bo_flags flags, const char *label)
353 {
354    struct agx_bo *bo;
355    assert(size > 0);
356 
357    /* BOs are allocated in pages */
358    size = ALIGN_POT(size, (size_t)dev->params.vm_page_size);
359    align = MAX2(align, dev->params.vm_page_size);
360 
361    /* See if we have a BO already in the cache */
362    bo = agx_bo_cache_fetch(dev, size, align, flags, true);
363 
364    /* Update stats based on the first attempt to fetch */
365    if (bo != NULL)
366       p_atomic_inc(&dev->bo_cache.hits);
367    else
368       p_atomic_inc(&dev->bo_cache.misses);
369 
370    /* Otherwise, allocate a fresh BO. If allocation fails, we can try waiting
371     * for something in the cache. But if there's no nothing suitable, we should
372     * flush the cache to make space for the new allocation.
373     */
374    if (!bo)
375       bo = dev->ops.bo_alloc(dev, size, align, flags);
376    if (!bo)
377       bo = agx_bo_cache_fetch(dev, size, align, flags, false);
378    if (!bo) {
379       agx_bo_cache_evict_all(dev);
380       bo = dev->ops.bo_alloc(dev, size, align, flags);
381    }
382 
383    if (!bo) {
384       fprintf(stderr, "BO creation failed\n");
385       return NULL;
386    }
387 
388    bo->label = label;
389    p_atomic_set(&bo->refcnt, 1);
390 
391    if (dev->debug & AGX_DBG_TRACE) {
392       agx_bo_map(bo);
393       agxdecode_track_alloc(dev->agxdecode, bo);
394    }
395 
396    agx_bo_dump_all_periodic(dev);
397    return bo;
398 }
399