| 1 | #include <inttypes.h>
 | 
| 2 | #include <limits.h>  // HOST_NAME_MAX
 | 
| 3 | #include <string.h>  // memcpy()
 | 
| 4 | #include <time.h>    // strftime()
 | 
| 5 | #include <unistd.h>  // gethostname()
 | 
| 6 | 
 | 
| 7 | #include <new>  // placement new
 | 
| 8 | 
 | 
| 9 | #include "mycpp/common.h"  // log()
 | 
| 10 | #include "mycpp/gc_obj.h"  // ObjHeader
 | 
| 11 | #include "vendor/greatest.h"
 | 
| 12 | 
 | 
| 13 | namespace demo {
 | 
| 14 | 
 | 
| 15 | // Could put this in ./configure, although glibc seems to have it
 | 
| 16 | bool IsLittleEndian() {
 | 
| 17 |   int i = 42;
 | 
| 18 |   int* pointer_i = &i;
 | 
| 19 |   char c = *(reinterpret_cast<char*>(pointer_i));
 | 
| 20 | 
 | 
| 21 |   // Should be 42 on little endian, 0 on big endian
 | 
| 22 |   return c == 42;
 | 
| 23 | }
 | 
| 24 | 
 | 
| 25 | #define GC_NEW(T, ...)                                                \
 | 
| 26 |   LayoutGc* untyped = gHeap.Allocate();                               \
 | 
| 27 |   T* obj = new (untyped.place)(...) untyped.header = T::obj_header(); \
 | 
| 28 |   untyped.header.obj_id = gHeap.mark_set_.NextObjectId() return obj
 | 
| 29 | 
 | 
| 30 | class Point {
 | 
| 31 |  public:
 | 
| 32 |   int x;
 | 
| 33 |   int y;
 | 
| 34 | 
 | 
| 35 |   int vtag() {
 | 
| 36 |     char* p = reinterpret_cast<char*>(this);
 | 
| 37 |     ObjHeader* header = reinterpret_cast<ObjHeader*>(p - sizeof(ObjHeader));
 | 
| 38 |     return header->type_tag;
 | 
| 39 |   }
 | 
| 40 | 
 | 
| 41 |   static constexpr ObjHeader obj_header() {
 | 
| 42 |     // type_tag is 42
 | 
| 43 |     return ObjHeader::Global(42);
 | 
| 44 |   }
 | 
| 45 | };
 | 
| 46 | 
 | 
| 47 | // Alloc<T> allocates an instance of T and fills in the GC header BEFORE the
 | 
| 48 | // object, and BEFORE any vtable (for classes with virtual functions).
 | 
| 49 | //
 | 
| 50 | // Alloc<T> is nicer for static dispatch; GC_NEW(T, ...) would be awkward.
 | 
| 51 | 
 | 
| 52 | // Steps to initialize a GC object:
 | 
| 53 | //
 | 
| 54 | // 1. Allocate untyped data
 | 
| 55 | // 2. Fill in constexpr header data
 | 
| 56 | // 3. Fill in DYNAMIC header data, like object ID
 | 
| 57 | // 4. Invoke constructor with placement new
 | 
| 58 | 
 | 
| 59 | struct LayoutGc {
 | 
| 60 |   ObjHeader header;
 | 
| 61 |   uint8_t place[1];  // flexible array, for placement new
 | 
| 62 | };
 | 
| 63 | 
 | 
| 64 | template <typename T>
 | 
| 65 | T* Alloc() {
 | 
| 66 |   // TODO: use gHeap.Allocate()
 | 
| 67 | 
 | 
| 68 |   LayoutGc* untyped =
 | 
| 69 |       static_cast<LayoutGc*>(malloc(sizeof(ObjHeader) + sizeof(T)));
 | 
| 70 |   untyped->header = T::obj_header();
 | 
| 71 | 
 | 
| 72 |   // TODO: assign proper object ID from MarkSweepHeap
 | 
| 73 |   untyped->header.obj_id = 124;  // dynamic, but can be done by allocator
 | 
| 74 |   return new (untyped->place) T();
 | 
| 75 | }
 | 
| 76 | 
 | 
| 77 | TEST gc_header_test() {
 | 
| 78 |   Point* p = Alloc<Point>();
 | 
| 79 |   log("p = %p", p);
 | 
| 80 | 
 | 
| 81 |   log("p->vtag() = %d", p->vtag());
 | 
| 82 |   ASSERT_EQ_FMT(42, p->vtag(), "%d");
 | 
| 83 | 
 | 
| 84 |   PASS();
 | 
| 85 | }
 | 
| 86 | 
 | 
| 87 | class Node {
 | 
| 88 |  public:
 | 
| 89 |   Node() {
 | 
| 90 |   }
 | 
| 91 | 
 | 
| 92 |   virtual int Method() {
 | 
| 93 |     return 42;
 | 
| 94 |   }
 | 
| 95 | 
 | 
| 96 |   static constexpr ObjHeader obj_header() {
 | 
| 97 |     return ObjHeader::ClassFixed(field_mask(), sizeof(Node));
 | 
| 98 |   }
 | 
| 99 | 
 | 
| 100 |   int x;
 | 
| 101 |   // max is either 24 or 30 bits, so use unsigned int
 | 
| 102 |   static constexpr unsigned int field_mask() {
 | 
| 103 |     return 0x0f;
 | 
| 104 |   }
 | 
| 105 | };
 | 
| 106 | 
 | 
| 107 | class Derived : public Node {
 | 
| 108 |  public:
 | 
| 109 |   Derived() : Node() {
 | 
| 110 |   }
 | 
| 111 | 
 | 
| 112 |   virtual int Method() {
 | 
| 113 |     return 43;
 | 
| 114 |   }
 | 
| 115 | 
 | 
| 116 |   int x;
 | 
| 117 | 
 | 
| 118 |   static constexpr unsigned field_mask() {
 | 
| 119 |     return Node::field_mask() | 0x30;
 | 
| 120 |   }
 | 
| 121 | 
 | 
| 122 |   static constexpr ObjHeader obj_header() {
 | 
| 123 |     return ObjHeader::ClassFixed(field_mask(), sizeof(Derived));
 | 
| 124 |   }
 | 
| 125 | };
 | 
| 126 | 
 | 
| 127 | class NoVirtual {
 | 
| 128 |  public:
 | 
| 129 |   NoVirtual() {
 | 
| 130 |   }
 | 
| 131 | 
 | 
| 132 |   static constexpr ObjHeader obj_header() {
 | 
| 133 |     return ObjHeader::ClassFixed(field_mask(), sizeof(NoVirtual));
 | 
| 134 |   }
 | 
| 135 | 
 | 
| 136 |   int i;
 | 
| 137 | 
 | 
| 138 |   static constexpr unsigned field_mask() {
 | 
| 139 |     return 0xf0;
 | 
| 140 |   }
 | 
| 141 | };
 | 
| 142 | 
 | 
| 143 | // TODO: Put this in mycpp/portability_test.cc and distribute to users
 | 
| 144 | 
 | 
| 145 | TEST endian_test() {
 | 
| 146 |   log("little endian? %d", IsLittleEndian());
 | 
| 147 | 
 | 
| 148 |   Derived* derived = Alloc<Derived>();
 | 
| 149 |   log("sizeof(Node) = %d", sizeof(Node));
 | 
| 150 |   log("sizeof(Derived) = %d", sizeof(Derived));
 | 
| 151 | 
 | 
| 152 |   ObjHeader* header = ObjHeader::FromObject(derived);
 | 
| 153 |   log("Derived is GC object? %d", header->type_tag & 0x1);
 | 
| 154 | 
 | 
| 155 |   NoVirtual* n2 = Alloc<NoVirtual>();
 | 
| 156 |   ObjHeader* header2 = ObjHeader::FromObject(n2);
 | 
| 157 |   log("NoVirtual is GC object? %d", header2->type_tag & 0x1);
 | 
| 158 | 
 | 
| 159 |   Node* n = Alloc<Node>();
 | 
| 160 |   ObjHeader* h = ObjHeader::FromObject(n);
 | 
| 161 |   FIELD_MASK(*h) = 0b11;
 | 
| 162 |   log("field mask %d", FIELD_MASK(*h));
 | 
| 163 |   log("num pointers %d", NUM_POINTERS(*h));
 | 
| 164 | 
 | 
| 165 |   PASS();
 | 
| 166 | }
 | 
| 167 | 
 | 
| 168 | //
 | 
| 169 | // Union test
 | 
| 170 | //
 | 
| 171 | 
 | 
| 172 | // This is 8 bytes!  Unions and bitfields don't work together?
 | 
| 173 | struct UnionBitfield {
 | 
| 174 |   unsigned heap_tag : 2;
 | 
| 175 | 
 | 
| 176 |   union Trace {
 | 
| 177 |     // Note: the string length is NOT used by the GC, so it doesn't really have
 | 
| 178 |     // to be here.  But this makes the BigStr object smaller.
 | 
| 179 | 
 | 
| 180 |     unsigned str_len : 30;       // HeapTag::Opaque
 | 
| 181 |     unsigned num_pointers : 30;  // HeapTag::Scanned
 | 
| 182 |     unsigned field_mask : 30;    // HeapTag::Fixed
 | 
| 183 |   } trace;
 | 
| 184 | };
 | 
| 185 | 
 | 
| 186 | //
 | 
| 187 | // header_.is_header  // 1 bit
 | 
| 188 | // header_.type_tag   // 7 bits
 | 
| 189 | //
 | 
| 190 | // #ifdef MARK_SWEEP
 | 
| 191 | //   header_.obj_id
 | 
| 192 | // #else
 | 
| 193 | //   header_.field_mask
 | 
| 194 | // #endif
 | 
| 195 | //
 | 
| 196 | // header_.heap_tag
 | 
| 197 | //
 | 
| 198 | // #ifdef MARK_SWEEP
 | 
| 199 | //   union {
 | 
| 200 | //     header_.field_mask.val
 | 
| 201 | //     header_.str_len.val
 | 
| 202 | //     header_.num_pointers.val
 | 
| 203 | //   }
 | 
| 204 | // #else
 | 
| 205 | //   header_.obj_len
 | 
| 206 | // #endif
 | 
| 207 | 
 | 
| 208 | union ObjHeader2 {
 | 
| 209 |   // doesn't work: initializations for multiple members?
 | 
| 210 |   // ObjHeader2() : raw_bytes(0), is_header(1) {
 | 
| 211 | 
 | 
| 212 |   // also doesn't work
 | 
| 213 |   // ObjHeader2() : is_header(1), type_tag(1) {
 | 
| 214 | 
 | 
| 215 |   ObjHeader2() : raw_bytes(0) {
 | 
| 216 |   }
 | 
| 217 | 
 | 
| 218 |   uint64_t raw_bytes;
 | 
| 219 | 
 | 
| 220 |   class _is_header {
 | 
| 221 |    public:
 | 
| 222 |     _is_header(int val) : val(val) {
 | 
| 223 |     }
 | 
| 224 |     unsigned val : 1;
 | 
| 225 |     unsigned _1 : 31;
 | 
| 226 |     unsigned _2 : 32;
 | 
| 227 |   } is_header;
 | 
| 228 | 
 | 
| 229 |   struct _type_tag {
 | 
| 230 |     _type_tag(int val) : val(val) {
 | 
| 231 |     }
 | 
| 232 |     unsigned _1 : 1;
 | 
| 233 |     unsigned val : 7;
 | 
| 234 |     unsigned _2 : 24;
 | 
| 235 |     unsigned _3 : 32;
 | 
| 236 |   } type_tag;
 | 
| 237 | 
 | 
| 238 |   struct _obj_id {
 | 
| 239 |     unsigned _1 : 8;
 | 
| 240 |     unsigned val : 24;
 | 
| 241 |     unsigned _2 : 32;
 | 
| 242 |   } obj_id;
 | 
| 243 | 
 | 
| 244 |   struct _heap_tag {
 | 
| 245 |     unsigned _1 : 32;
 | 
| 246 |     unsigned val : 2;
 | 
| 247 |     unsigned _2 : 30;
 | 
| 248 |   } heap_tag;
 | 
| 249 | 
 | 
| 250 |   // These three share the same bigs
 | 
| 251 |   struct _field_mask {
 | 
| 252 |     unsigned _1 : 32;
 | 
| 253 |     unsigned _2 : 2;
 | 
| 254 |     unsigned val : 30;
 | 
| 255 |   } field_mask;
 | 
| 256 | 
 | 
| 257 |   struct _str_len {
 | 
| 258 |     unsigned _1 : 32;
 | 
| 259 |     unsigned _2 : 2;
 | 
| 260 |     unsigned val : 30;
 | 
| 261 |   } str_len;
 | 
| 262 | 
 | 
| 263 |   struct _num_pointers {
 | 
| 264 |     unsigned _1 : 32;
 | 
| 265 |     unsigned _2 : 2;
 | 
| 266 |     unsigned val : 30;
 | 
| 267 |   } num_pointers;
 | 
| 268 | 
 | 
| 269 |   // Hand-written classes, including fixed size List and Dict headers
 | 
| 270 |   void InitFixedClass(int field_mask) {
 | 
| 271 |     this->is_header.val = 1;
 | 
| 272 |     this->heap_tag.val = HeapTag::FixedSize;
 | 
| 273 |     this->field_mask.val = field_mask;
 | 
| 274 |   }
 | 
| 275 | 
 | 
| 276 |   // - Slab<List*>, Slab<BigStr> (might be HeapStr*)
 | 
| 277 |   // - Generated classes without inheritance
 | 
| 278 |   // - all ASDL types
 | 
| 279 |   void InitScanned(int num_pointers) {
 | 
| 280 |     this->is_header.val = 1;
 | 
| 281 |     this->heap_tag.val = HeapTag::Scanned;
 | 
| 282 |     this->num_pointers.val = num_pointers;
 | 
| 283 |   }
 | 
| 284 | 
 | 
| 285 |   void InitStr(int str_len) {
 | 
| 286 |     this->is_header.val = 1;
 | 
| 287 |     this->heap_tag.val = HeapTag::Opaque;
 | 
| 288 |     this->str_len.val = str_len;
 | 
| 289 |   }
 | 
| 290 | 
 | 
| 291 |   void InitAsdlVariant(int type_tag, int num_pointers) {
 | 
| 292 |     this->is_header.val = 1;
 | 
| 293 | 
 | 
| 294 |     this->type_tag.val = type_tag;
 | 
| 295 | 
 | 
| 296 |     this->heap_tag.val = HeapTag::Scanned;
 | 
| 297 |     this->num_pointers.val = num_pointers;
 | 
| 298 |   }
 | 
| 299 | 
 | 
| 300 |   // Other:
 | 
| 301 |   // - HeapTag::Global is for GlobalBigStr*, GLOBAL_LIST, ...
 | 
| 302 |   //
 | 
| 303 |   // All the variants of value_e get their own type tag?
 | 
| 304 |   // - Boxed value.{Bool,Int,Float}
 | 
| 305 |   // - And "boxless" / "tagless" BigStr, List, Dict
 | 
| 306 | };
 | 
| 307 | 
 | 
| 308 | class Token {
 | 
| 309 |  public:
 | 
| 310 |   Token() {
 | 
| 311 |   }
 | 
| 312 | 
 | 
| 313 |   ObjHeader2 header_;
 | 
| 314 | };
 | 
| 315 | 
 | 
| 316 | TEST union_test() {
 | 
| 317 |   log("sizeof(UnionBitfield) = %d", sizeof(UnionBitfield));
 | 
| 318 | 
 | 
| 319 |   Token t;
 | 
| 320 | 
 | 
| 321 |   t.header_.is_header.val = 1;
 | 
| 322 | 
 | 
| 323 |   t.header_.type_tag.val = 127;
 | 
| 324 |   t.header_.obj_id.val = 12345678;  // max 16 Mi
 | 
| 325 | 
 | 
| 326 |   t.header_.heap_tag.val = HeapTag::Scanned;
 | 
| 327 |   t.header_.field_mask.val = 0xff;
 | 
| 328 | 
 | 
| 329 |   log("is_header %d", t.header_.is_header.val);
 | 
| 330 | 
 | 
| 331 |   log("type_tag %d", t.header_.type_tag.val);
 | 
| 332 |   log("obj_id %d", t.header_.obj_id.val);
 | 
| 333 | 
 | 
| 334 |   log("heap_tag %d", t.header_.heap_tag.val);
 | 
| 335 | 
 | 
| 336 |   log("field_mask %d", t.header_.field_mask.val);
 | 
| 337 |   log("str_len %d", t.header_.str_len.val);
 | 
| 338 |   log("num_pointers %d", t.header_.num_pointers.val);
 | 
| 339 | 
 | 
| 340 |   // Garbage collector
 | 
| 341 | 
 | 
| 342 |   // First check check SmallStr.is_present_ - it might not be a pointer at all
 | 
| 343 |   ObjHeader2* obj = reinterpret_cast<ObjHeader2*>(&t);
 | 
| 344 | 
 | 
| 345 |   // Then check for vtable - it might not be a pointer to an ObjHeader2
 | 
| 346 |   if (!obj->is_header.val) {
 | 
| 347 |     // advance 8 bytes and assert header.is_header
 | 
| 348 |     log("Not a header");
 | 
| 349 |   }
 | 
| 350 | 
 | 
| 351 |   PASS();
 | 
| 352 | }
 | 
| 353 | 
 | 
| 354 | }  // namespace demo
 | 
| 355 | 
 | 
| 356 | GREATEST_MAIN_DEFS();
 | 
| 357 | 
 | 
| 358 | int main(int argc, char** argv) {
 | 
| 359 |   // gHeap.Init();
 | 
| 360 | 
 | 
| 361 |   GREATEST_MAIN_BEGIN();
 | 
| 362 | 
 | 
| 363 |   RUN_TEST(demo::gc_header_test);
 | 
| 364 |   RUN_TEST(demo::endian_test);
 | 
| 365 |   RUN_TEST(demo::union_test);
 | 
| 366 | 
 | 
| 367 |   // gHeap.CleanProcessExit();
 | 
| 368 | 
 | 
| 369 |   GREATEST_MAIN_END();
 | 
| 370 |   return 0;
 | 
| 371 | }
 |