| 1 | // gc_alloc.h: Functions that wrap gHeap.Allocate()
|
| 2 |
|
| 3 | #ifndef MYCPP_GC_ALLOC_H
|
| 4 | #define MYCPP_GC_ALLOC_H
|
| 5 |
|
| 6 | #include <string.h> // strlen
|
| 7 |
|
| 8 | #include <new> // placement new
|
| 9 | #include <utility> // std::forward
|
| 10 |
|
| 11 | #include "mycpp/gc_obj.h" // for RawObject, ObjHeader
|
| 12 | #include "mycpp/gc_slab.h" // for NewSlab()
|
| 13 | #include "mycpp/gc_str.h" // for NewStr()
|
| 14 |
|
| 15 | #if defined(BUMP_LEAK)
|
| 16 | #include "mycpp/bump_leak_heap.h"
|
| 17 | extern BumpLeakHeap gHeap;
|
| 18 | #elif defined(MARK_SWEEP)
|
| 19 | #include "mycpp/mark_sweep_heap.h"
|
| 20 | extern MarkSweepHeap gHeap;
|
| 21 | #endif
|
| 22 |
|
| 23 | #define VALIDATE_ROOTS 0
|
| 24 |
|
| 25 | #if VALIDATE_ROOTS
|
| 26 | static void ValidateRoot(const RawObject* obj) {
|
| 27 | if (obj == nullptr) {
|
| 28 | return;
|
| 29 | }
|
| 30 |
|
| 31 | ObjHeader* header = ObjHeader::FromObject(obj);
|
| 32 | // log("obj %p header %p", obj, header);
|
| 33 |
|
| 34 | switch (header->heap_tag) {
|
| 35 | case HeapTag::Global:
|
| 36 | case HeapTag::Opaque:
|
| 37 | case HeapTag::Scanned:
|
| 38 | case HeapTag::FixedSize:
|
| 39 | break;
|
| 40 |
|
| 41 | default:
|
| 42 | log("root %p heap %d type %d mask %d len %d", obj, header->heap_tag,
|
| 43 | header->type_tag, header->u_mask_npointers);
|
| 44 | FAIL(kShouldNotGetHere);
|
| 45 | break;
|
| 46 | }
|
| 47 | }
|
| 48 | #endif
|
| 49 |
|
| 50 | // mycpp generates code that keeps track of the root set
|
| 51 | class StackRoot {
|
| 52 | public:
|
| 53 | StackRoot(void* root) {
|
| 54 | RawObject** obj = reinterpret_cast<RawObject**>(root);
|
| 55 | #if VALIDATE_ROOTS
|
| 56 | ValidateRoot(*obj);
|
| 57 | #endif
|
| 58 | gHeap.PushRoot(obj);
|
| 59 | }
|
| 60 |
|
| 61 | ~StackRoot() {
|
| 62 | gHeap.PopRoot();
|
| 63 | }
|
| 64 | };
|
| 65 |
|
| 66 | // sugar for tests
|
| 67 | class StackRoots {
|
| 68 | public:
|
| 69 | // Note: void** seems logical, because these are pointers to pointers, but
|
| 70 | // the C++ compiler doesn't like it.
|
| 71 | StackRoots(std::initializer_list<void*> roots) {
|
| 72 | n_ = roots.size();
|
| 73 |
|
| 74 | #if VALIDATE_ROOTS
|
| 75 | int i = 0;
|
| 76 | #endif
|
| 77 |
|
| 78 | for (auto root : roots) { // can't use roots[i]
|
| 79 | RawObject** obj = reinterpret_cast<RawObject**>(root);
|
| 80 | #if VALIDATE_ROOTS
|
| 81 | ValidateRoot(*obj);
|
| 82 | i++;
|
| 83 | #endif
|
| 84 |
|
| 85 | gHeap.PushRoot(obj);
|
| 86 | }
|
| 87 | }
|
| 88 |
|
| 89 | ~StackRoots() {
|
| 90 | for (int i = 0; i < n_; ++i) {
|
| 91 | gHeap.PopRoot();
|
| 92 | }
|
| 93 | }
|
| 94 |
|
| 95 | private:
|
| 96 | int n_;
|
| 97 | };
|
| 98 |
|
| 99 | // Note:
|
| 100 | // - This function causes code bloat due to template expansion on hundreds of
|
| 101 | // types. Could switch to a GC_NEW() macro
|
| 102 | // - GCC generates slightly larger code if you factor out void* place and new
|
| 103 | // (place) T()
|
| 104 | //
|
| 105 | // Variadic templates:
|
| 106 | // https://eli.thegreenplace.net/2014/variadic-templates-in-c/
|
| 107 | template <typename T, typename... Args>
|
| 108 | T* Alloc(Args&&... args) {
|
| 109 | // Alloc() allocates space for both a header and object and guarantees that
|
| 110 | // they're adjacent in memory (so that they're at known offsets from one
|
| 111 | // another). However, this means that the address that the object is
|
| 112 | // constructed at is offset from the address returned by the memory allocator
|
| 113 | // (by the size of the header), and therefore may not be sufficiently aligned.
|
| 114 | // Here we assert that the object will be sufficiently aligned by making the
|
| 115 | // equivalent assertion that zero padding would be required to align it.
|
| 116 | // Note: the required padding is given by the following (according to
|
| 117 | // https://en.wikipedia.org/wiki/Data_structure_alignment):
|
| 118 | // `padding = -offset & (align - 1)`.
|
| 119 | static_assert((-sizeof(ObjHeader) & (alignof(T) - 1)) == 0,
|
| 120 | "Expected no padding");
|
| 121 |
|
| 122 | DCHECK(gHeap.is_initialized_);
|
| 123 |
|
| 124 | constexpr size_t num_bytes = sizeof(ObjHeader) + sizeof(T);
|
| 125 | #if MARK_SWEEP
|
| 126 | int obj_id;
|
| 127 | int pool_id;
|
| 128 | void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
|
| 129 | #else
|
| 130 | void* place = gHeap.Allocate(num_bytes);
|
| 131 | #endif
|
| 132 | ObjHeader* header = new (place) ObjHeader(T::obj_header());
|
| 133 | #if MARK_SWEEP
|
| 134 | header->obj_id = obj_id;
|
| 135 | #ifndef NO_POOL_ALLOC
|
| 136 | header->pool_id = pool_id;
|
| 137 | #endif
|
| 138 | #endif
|
| 139 | void* obj = header->ObjectAddress();
|
| 140 | // mycpp doesn't generated constructors that initialize every field
|
| 141 | memset(obj, 0, sizeof(T));
|
| 142 | return new (obj) T(std::forward<Args>(args)...);
|
| 143 | }
|
| 144 |
|
| 145 | //
|
| 146 | // String "Constructors". We need these because of the "flexible array"
|
| 147 | // pattern. I don't think "new BigStr()" can do that, and placement new would
|
| 148 | // require mycpp to generate 2 statements everywhere.
|
| 149 | //
|
| 150 |
|
| 151 | inline BigStr* NewStr(int len) {
|
| 152 | if (len == 0) { // e.g. BufLineReader::readline() can use this optimization
|
| 153 | return kEmptyString;
|
| 154 | }
|
| 155 |
|
| 156 | int obj_len = kStrHeaderSize + len + 1; // NUL terminator
|
| 157 | const size_t num_bytes = sizeof(ObjHeader) + obj_len;
|
| 158 | #if MARK_SWEEP
|
| 159 | int obj_id;
|
| 160 | int pool_id;
|
| 161 | void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
|
| 162 | #else
|
| 163 | void* place = gHeap.Allocate(num_bytes);
|
| 164 | #endif
|
| 165 | ObjHeader* header = new (place) ObjHeader(BigStr::obj_header());
|
| 166 |
|
| 167 | auto s = new (header->ObjectAddress()) BigStr();
|
| 168 |
|
| 169 | s->data_[len] = '\0'; // NUL terminate
|
| 170 | s->len_ = len;
|
| 171 | s->hash_ = 0;
|
| 172 | s->is_hashed_ = 0;
|
| 173 |
|
| 174 | #if MARK_SWEEP
|
| 175 | header->obj_id = obj_id;
|
| 176 | #ifndef NO_POOL_ALLOC
|
| 177 | header->pool_id = pool_id;
|
| 178 | #endif
|
| 179 | #endif
|
| 180 | return s;
|
| 181 | }
|
| 182 |
|
| 183 | // Call OverAllocatedStr() when you don't know the length of the string up
|
| 184 | // front, e.g. with snprintf(). CALLER IS RESPONSIBLE for calling
|
| 185 | // s->MaybeShrink() afterward!
|
| 186 | inline BigStr* OverAllocatedStr(int len) {
|
| 187 | int obj_len = kStrHeaderSize + len + 1; // NUL terminator
|
| 188 | const size_t num_bytes = sizeof(ObjHeader) + obj_len;
|
| 189 | #if MARK_SWEEP
|
| 190 | int obj_id;
|
| 191 | int pool_id;
|
| 192 | void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
|
| 193 | #else
|
| 194 | void* place = gHeap.Allocate(num_bytes);
|
| 195 | #endif
|
| 196 | ObjHeader* header = new (place) ObjHeader(BigStr::obj_header());
|
| 197 | auto s = new (header->ObjectAddress()) BigStr();
|
| 198 | s->hash_ = 0;
|
| 199 | s->is_hashed_ = 0;
|
| 200 |
|
| 201 | #if MARK_SWEEP
|
| 202 | header->obj_id = obj_id;
|
| 203 | #ifndef NO_POOL_ALLOC
|
| 204 | header->pool_id = pool_id;
|
| 205 | #endif
|
| 206 | #endif
|
| 207 | return s;
|
| 208 | }
|
| 209 |
|
| 210 | // Copy C string into the managed heap.
|
| 211 | inline BigStr* StrFromC(const char* data, int len) {
|
| 212 | // Optimization that could be taken out once we have SmallStr
|
| 213 | if (len == 0) {
|
| 214 | return kEmptyString;
|
| 215 | }
|
| 216 | BigStr* s = NewStr(len);
|
| 217 | memcpy(s->data_, data, len);
|
| 218 | DCHECK(s->data_[len] == '\0'); // should be true because Heap was zeroed
|
| 219 |
|
| 220 | return s;
|
| 221 | }
|
| 222 |
|
| 223 | inline BigStr* StrFromC(const char* data) {
|
| 224 | return StrFromC(data, strlen(data));
|
| 225 | }
|
| 226 |
|
| 227 | // Create a slab with a number of entries of a certain type.
|
| 228 | // Note: entries will be zero'd because we use calloc(). TODO: Consider
|
| 229 | // zeroing them separately.
|
| 230 | template <typename T>
|
| 231 | inline Slab<T>* NewSlab(int len) {
|
| 232 | int obj_len = len * sizeof(T);
|
| 233 | const size_t num_bytes = sizeof(ObjHeader) + obj_len;
|
| 234 | #if MARK_SWEEP
|
| 235 | int obj_id;
|
| 236 | int pool_id;
|
| 237 | void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
|
| 238 | #else
|
| 239 | void* place = gHeap.Allocate(num_bytes);
|
| 240 | #endif
|
| 241 | ObjHeader* header = new (place) ObjHeader(Slab<T>::obj_header(len));
|
| 242 | void* obj = header->ObjectAddress();
|
| 243 | if (std::is_pointer<T>()) {
|
| 244 | memset(obj, 0, obj_len);
|
| 245 | }
|
| 246 | auto slab = new (obj) Slab<T>(len);
|
| 247 | #if MARK_SWEEP
|
| 248 | header->obj_id = obj_id;
|
| 249 | #ifndef NO_POOL_ALLOC
|
| 250 | header->pool_id = pool_id;
|
| 251 | #endif
|
| 252 | #endif
|
| 253 | return slab;
|
| 254 | }
|
| 255 |
|
| 256 | #endif // MYCPP_GC_ALLOC_H
|