| 1 | #ifndef MYCPP_GC_OBJ_H
|
| 2 | #define MYCPP_GC_OBJ_H
|
| 3 |
|
| 4 | #include <stdint.h> // uint8_t
|
| 5 |
|
| 6 | #include "mycpp/common.h"
|
| 7 |
|
| 8 | namespace HeapTag {
|
| 9 | const int Global = 0; // Don't mark or sweep.
|
| 10 | const int Opaque = 1; // e.g. List<int>, BigStr
|
| 11 | // Mark and sweep, but don't trace children
|
| 12 | const int FixedSize = 2; // Consult field_mask for children
|
| 13 | const int Scanned = 3; // Scan a contiguous range of children
|
| 14 | }; // namespace HeapTag
|
| 15 |
|
| 16 | // These tags are mainly for debugging. Oils is a statically typed program, so
|
| 17 | // we don't need runtime types in general.
|
| 18 | // This "enum" starts from the end of the valid type_tag range.
|
| 19 | // asdl/gen_cpp.py starts from 1 for variants, or 64 for shared variants.
|
| 20 | namespace TypeTag {
|
| 21 | const int OtherClass = 127; // non-ASDL class
|
| 22 | const int BigStr = 126; // asserted in dynamic StrFormat()
|
| 23 | const int Slab = 125;
|
| 24 | const int Tuple = 124;
|
| 25 | const int List = 123;
|
| 26 | const int Dict = 122;
|
| 27 | }; // namespace TypeTag
|
| 28 |
|
| 29 | const int kNotInPool = 0;
|
| 30 | const int kInPool = 1;
|
| 31 |
|
| 32 | const unsigned kZeroMask = 0; // for types with no pointers
|
| 33 |
|
| 34 | const int kMaxObjId = (1 << 28) - 1; // 28 bits means 512 Mi objects per pool
|
| 35 | const int kIsGlobal = kMaxObjId; // for debugging, not strictly needed
|
| 36 |
|
| 37 | const int kUndefinedId = 0; // Uninitialized object ID
|
| 38 |
|
| 39 | // Every GC-managed object is preceded in memory by an ObjHeader.
|
| 40 | // TODO: ./configure could detect endian-ness, and reorder the fields in
|
| 41 | // ObjHeader. See mycpp/demo/gc_header.cc.
|
| 42 | struct ObjHeader {
|
| 43 | unsigned type_tag : 8; // TypeTag, ASDL variant / shared variant
|
| 44 | // Depending on heap_tag, up to 24 fields or 2**24 = 16 Mi pointers to scan
|
| 45 | unsigned u_mask_npointers : 24;
|
| 46 |
|
| 47 | unsigned heap_tag : 2; // HeapTag::Opaque, etc.
|
| 48 | unsigned pool_id : 2; // 0 for malloc(), or 1 2 3 for fixed sized pools
|
| 49 | unsigned obj_id : 28; // 1 Gi unique objects
|
| 50 |
|
| 51 | // Returns the address of the GC managed object associated with this header.
|
| 52 | // Note: this relies on there being no padding between the header and the
|
| 53 | // object. See Alloc<T>() and GcGlobal<T> for relevant static_assert()s.
|
| 54 | void* ObjectAddress() {
|
| 55 | return reinterpret_cast<void*>(reinterpret_cast<char*>(this) +
|
| 56 | sizeof(ObjHeader));
|
| 57 | }
|
| 58 |
|
| 59 | // Returns the header for the given GC managed object.
|
| 60 | // Note: this relies on there being no padding between the header and the
|
| 61 | // object. See Alloc<T>() and GcGlobal<T> for relevant static_assert()s.
|
| 62 | static ObjHeader* FromObject(const void* obj) {
|
| 63 | return reinterpret_cast<ObjHeader*>(
|
| 64 | static_cast<char*>(const_cast<void*>(obj)) - sizeof(ObjHeader));
|
| 65 | }
|
| 66 |
|
| 67 | // Used by hand-written and generated classes
|
| 68 | static constexpr ObjHeader ClassFixed(uint32_t field_mask, uint32_t obj_len) {
|
| 69 | return {TypeTag::OtherClass, field_mask, HeapTag::FixedSize, kNotInPool,
|
| 70 | kUndefinedId};
|
| 71 | }
|
| 72 |
|
| 73 | // Classes with no inheritance (e.g. used by mycpp)
|
| 74 | static constexpr ObjHeader ClassScanned(uint32_t num_pointers,
|
| 75 | uint32_t obj_len) {
|
| 76 | return {TypeTag::OtherClass, num_pointers, HeapTag::Scanned, kNotInPool,
|
| 77 | kUndefinedId};
|
| 78 | }
|
| 79 |
|
| 80 | // Used by frontend/flag_gen.py. TODO: Sort fields and use GC_CLASS_SCANNED
|
| 81 | static constexpr ObjHeader Class(uint8_t heap_tag, uint32_t field_mask,
|
| 82 | uint32_t obj_len) {
|
| 83 | return {TypeTag::OtherClass, field_mask, heap_tag, kNotInPool,
|
| 84 | kUndefinedId};
|
| 85 | }
|
| 86 |
|
| 87 | // Used by ASDL.
|
| 88 | static constexpr ObjHeader AsdlClass(uint8_t type_tag,
|
| 89 | uint32_t num_pointers) {
|
| 90 | return {type_tag, num_pointers, HeapTag::Scanned, kNotInPool, kUndefinedId};
|
| 91 | }
|
| 92 |
|
| 93 | static constexpr ObjHeader BigStr() {
|
| 94 | return {TypeTag::BigStr, kZeroMask, HeapTag::Opaque, kNotInPool,
|
| 95 | kUndefinedId};
|
| 96 | }
|
| 97 |
|
| 98 | static constexpr ObjHeader Slab(uint8_t heap_tag, uint32_t num_pointers) {
|
| 99 | return {TypeTag::Slab, num_pointers, heap_tag, kNotInPool, kUndefinedId};
|
| 100 | }
|
| 101 |
|
| 102 | static constexpr ObjHeader Tuple(uint32_t field_mask, uint32_t obj_len) {
|
| 103 | return {TypeTag::Tuple, field_mask, HeapTag::FixedSize, kNotInPool,
|
| 104 | kUndefinedId};
|
| 105 | }
|
| 106 |
|
| 107 | // Used by GLOBAL_STR, GLOBAL_LIST, GLOBAL_DICT
|
| 108 | static constexpr ObjHeader Global(uint8_t type_tag) {
|
| 109 | return {type_tag, kZeroMask, HeapTag::Global, kNotInPool, kIsGlobal};
|
| 110 | }
|
| 111 | };
|
| 112 |
|
| 113 | inline int ObjectId(void* obj) {
|
| 114 | ObjHeader* h = ObjHeader::FromObject(obj);
|
| 115 |
|
| 116 | // pool_id is 2 bits, so shift the 28 bit obj_id past it.
|
| 117 | return (h->obj_id << 2) + h->pool_id;
|
| 118 | }
|
| 119 |
|
| 120 | #define FIELD_MASK(header) (header).u_mask_npointers
|
| 121 | #define NUM_POINTERS(header) (header).u_mask_npointers
|
| 122 |
|
| 123 | // A RawObject* is like a void*. We use it to represent GC managed objects.
|
| 124 | struct RawObject;
|
| 125 |
|
| 126 | //
|
| 127 | // Compile-time computation of GC field masks.
|
| 128 | //
|
| 129 |
|
| 130 | // maskbit(field_offset) returns a bit in mask that you can bitwise-or (|) with
|
| 131 | // other bits.
|
| 132 | //
|
| 133 | // - Note that we only call maskbit() on offsets of pointer fields, which must
|
| 134 | // be POINTER-ALIGNED.
|
| 135 |
|
| 136 | constexpr int maskbit(size_t offset) {
|
| 137 | return 1 << (offset / sizeof(void*));
|
| 138 | }
|
| 139 |
|
| 140 | // A wrapper for a GC object and its header. For creating global GC objects,
|
| 141 | // like GlobalStr.
|
| 142 | // TODO: Make this more ergonomic by automatically initializing header
|
| 143 | // with T::obj_header() and providing a forwarding constructor for obj.
|
| 144 | template <typename T>
|
| 145 | class GcGlobalImpl {
|
| 146 | public:
|
| 147 | ObjHeader header;
|
| 148 | T obj;
|
| 149 |
|
| 150 | // This class only exists to write the static_assert. If you try to put the
|
| 151 | // static_assert directly in the outer class you get a compiler error that
|
| 152 | // taking the offsets is an 'invalid use of incomplete type'. Doing it this
|
| 153 | // way means the type gets completed before the assert.
|
| 154 | struct Internal {
|
| 155 | using type = GcGlobalImpl<T>;
|
| 156 | static_assert(offsetof(type, obj) - sizeof(ObjHeader) ==
|
| 157 | offsetof(type, header),
|
| 158 | "ObjHeader doesn't fit");
|
| 159 | };
|
| 160 |
|
| 161 | DISALLOW_COPY_AND_ASSIGN(GcGlobalImpl);
|
| 162 | };
|
| 163 |
|
| 164 | // Refer to `Internal::type` to force Internal to be instantiated.
|
| 165 | template <typename T>
|
| 166 | using GcGlobal = typename GcGlobalImpl<T>::Internal::type;
|
| 167 |
|
| 168 | // The "homogeneous" layout of objects with HeapTag::FixedSize. LayoutFixed is
|
| 169 | // for casting; it isn't a real type.
|
| 170 |
|
| 171 | // TODO: we could determine the max of all objects statically!
|
| 172 | const int kFieldMaskBits = 24;
|
| 173 |
|
| 174 | struct LayoutFixed {
|
| 175 | // only the entries denoted in field_mask will be valid
|
| 176 | RawObject* children_[kFieldMaskBits];
|
| 177 | };
|
| 178 |
|
| 179 | #endif // MYCPP_GC_OBJ_H
|