#define ANNOTATE(property) __attribute__((tag(property))) struct Cell { int f; } ANNOTATE("GC Thing"); struct RootedCell { RootedCell(Cell*) {} } ANNOTATE("Rooted Pointer"); class AutoSuppressGC_Base { public: AutoSuppressGC_Base() {} ~AutoSuppressGC_Base() {} } ANNOTATE("Suppress GC"); class AutoSuppressGC_Child : public AutoSuppressGC_Base { public: AutoSuppressGC_Child() : AutoSuppressGC_Base() {} }; class AutoSuppressGC { AutoSuppressGC_Child helpImBeingSuppressed; public: AutoSuppressGC() {} }; extern void GC() ANNOTATE("GC Call"); extern void invisible(); void GC() { // If the implementation is too trivial, the function body won't be emitted at all. asm(""); invisible(); } extern void usecell(Cell*); void suppressedFunction() { GC(); // Calls GC, but is always called within AutoSuppressGC } void halfSuppressedFunction() { GC(); // Calls GC, but is sometimes called within AutoSuppressGC } void unsuppressedFunction() { GC(); // Calls GC, never within AutoSuppressGC } volatile static int x = 3; volatile static int* xp = &x; struct GCInDestructor { ~GCInDestructor() { invisible(); asm(""); *xp = 4; GC(); } }; Cell* f() { GCInDestructor kaboom; Cell cell; Cell* cell1 = &cell; Cell* cell2 = &cell; Cell* cell3 = &cell; Cell* cell4 = &cell; { AutoSuppressGC nogc; suppressedFunction(); halfSuppressedFunction(); } usecell(cell1); halfSuppressedFunction(); usecell(cell2); unsuppressedFunction(); { // Old bug: it would look from the first AutoSuppressGC constructor it // found to the last destructor. This statement *should* have no effect. AutoSuppressGC nogc; } usecell(cell3); Cell* cell5 = &cell; usecell(cell5); // Hazard in return value due to ~GCInDestructor Cell* cell6 = &cell; return cell6; } Cell* copy_and_gc(Cell* src) { GC(); return reinterpret_cast(88); } void use(Cell* cell) { static int x = 0; if (cell) x++; } struct CellContainer { Cell* cell; CellContainer() { asm(""); } }; void loopy() { Cell cell; // No hazard: haz1 is not live during call to copy_and_gc. Cell* haz1; for (int i = 0; i < 10; i++) { haz1 = copy_and_gc(haz1); } // No hazard: haz2 is live up to just before the GC, and starting at the // next statement after it, but not across the GC. Cell* haz2 = &cell; for (int j = 0; j < 10; j++) { use(haz2); GC(); haz2 = &cell; } // Hazard: haz3 is live from the final statement in one iteration, across // the GC in the next, to the use in the 2nd statement. Cell* haz3; for (int k = 0; k < 10; k++) { GC(); use(haz3); haz3 = &cell; } // Hazard: haz4 is live across a GC hidden in a loop. Cell* haz4 = &cell; for (int i2 = 0; i2 < 10; i2++) { GC(); } use(haz4); // Hazard: haz5 is live from within a loop across a GC. Cell* haz5; for (int i3 = 0; i3 < 10; i3++) { haz5 = &cell; } GC(); use(haz5); // No hazard: similar to the haz3 case, but verifying that we do not get // into an infinite loop. Cell* haz6; for (int i4 = 0; i4 < 10; i4++) { GC(); haz6 = &cell; } // No hazard: haz7 is constructed within the body, so it can't make a // hazard across iterations. Note that this requires CellContainer to have // a constructor, because otherwise the analysis doesn't see where // variables are declared. (With the constructor, it knows that // construction of haz7 obliterates any previous value it might have had. // Not that that's possible given its scope, but the analysis doesn't get // that information.) for (int i5 = 0; i5 < 10; i5++) { GC(); CellContainer haz7; use(haz7.cell); haz7.cell = &cell; } // Hazard: make sure we *can* see hazards across iterations involving // CellContainer; CellContainer haz8; for (int i6 = 0; i6 < 10; i6++) { GC(); use(haz8.cell); haz8.cell = &cell; } }