summaryrefslogtreecommitdiff
path: root/libguile/whippet/src/simple-worklist.h
blob: 61f92a31d6284d9435e6a05c49cb9754f38e5575 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#ifndef SIMPLE_WORKLIST_H
#define SIMPLE_WORKLIST_H

#include "assert.h"
#include "debug.h"
#include "gc-inline.h"
#include "gc-ref.h"
#include "gc-platform.h"

struct simple_worklist {
  size_t size;
  size_t read;
  size_t write;
  struct gc_ref *buf;
};

static const size_t simple_worklist_max_size =
  (1ULL << (sizeof(struct gc_ref) * 8 - 1)) / sizeof(struct gc_ref);
static const size_t simple_worklist_release_byte_threshold = 1 * 1024 * 1024;

static struct gc_ref *
simple_worklist_alloc(size_t size) {
  void *mem = gc_platform_acquire_memory(size * sizeof(struct gc_ref), 0);
  if (!mem) {
    perror("Failed to grow trace queue");
    DEBUG("Failed to allocate %zu bytes", size);
    return NULL;
  }
  return mem;
}

static int
simple_worklist_init(struct simple_worklist *q) {
  q->size = gc_platform_page_size() / sizeof(struct gc_ref);
  q->read = 0;
  q->write = 0;
  q->buf = simple_worklist_alloc(q->size);
  return !!q->buf;
}
  
static inline struct gc_ref
simple_worklist_get(struct simple_worklist *q, size_t idx) {
  return q->buf[idx & (q->size - 1)];
}

static inline void
simple_worklist_put(struct simple_worklist *q, size_t idx, struct gc_ref x) {
  q->buf[idx & (q->size - 1)] = x;
}

static int simple_worklist_grow(struct simple_worklist *q) GC_NEVER_INLINE;

static int
simple_worklist_grow(struct simple_worklist *q) {
  size_t old_size = q->size;
  struct gc_ref *old_buf = q->buf;
  if (old_size >= simple_worklist_max_size) {
    DEBUG("trace queue already at max size of %zu bytes", old_size);
    return 0;
  }

  size_t new_size = old_size * 2;
  struct gc_ref *new_buf = simple_worklist_alloc(new_size);
  if (!new_buf)
    return 0;

  size_t old_mask = old_size - 1;
  size_t new_mask = new_size - 1;

  for (size_t i = q->read; i < q->write; i++)
    new_buf[i & new_mask] = old_buf[i & old_mask];

  munmap(old_buf, old_size * sizeof(struct gc_ref));

  q->size = new_size;
  q->buf = new_buf;
  return 1;
}
  
static inline void
simple_worklist_push(struct simple_worklist *q, struct gc_ref p) {
  if (UNLIKELY(q->write - q->read == q->size)) {
    if (!simple_worklist_grow(q))
      GC_CRASH();
  }
  simple_worklist_put(q, q->write++, p);
}

static inline void
simple_worklist_push_many(struct simple_worklist *q, struct gc_ref *pv,
                          size_t count) {
  while (q->size - (q->write - q->read) < count) {
    if (!simple_worklist_grow(q))
      GC_CRASH();
  }
  for (size_t i = 0; i < count; i++)
    simple_worklist_put(q, q->write++, pv[i]);
}

static inline struct gc_ref
simple_worklist_pop(struct simple_worklist *q) {
  if (UNLIKELY(q->read == q->write))
    return gc_ref_null();
  return simple_worklist_get(q, q->read++);
}

static void
simple_worklist_release(struct simple_worklist *q) {
  size_t byte_size = q->size * sizeof(struct gc_ref);
  if (byte_size >= simple_worklist_release_byte_threshold)
    madvise(q->buf, byte_size, MADV_DONTNEED);
  q->read = q->write = 0;
}

static void
simple_worklist_destroy(struct simple_worklist *q) {
  size_t byte_size = q->size * sizeof(struct gc_ref);
  munmap(q->buf, byte_size);
}

#endif // SIMPLE_WORKLIST_H