#include "stdafx.h"
#include "GcCanary.h"

static size_t canaryCounter = 0;

// Clear out data on the stack to avoid the GC retaining things.
NOINLINE void clearStack(size_t toClear) {
	// Needs to be surprisingly large. Probably since there are a couple of function calls from when
	// we call gc.collect() to when we actually get into the GC itself.
#ifdef WINDOWS
	size_t *stackBuffer = (size_t *)alloca(sizeof(size_t) * toClear);
#else
	size_t stackBuffer[toClear];
#endif
	for (size_t i = 0; i < toClear; i++)
		writeOnce(stackBuffer[i], size_t(0));
}

static void canaryFinalizer(void *object, os::Thread *thread) {
	(void)thread;
	canaryCounter += *reinterpret_cast<size_t *>(object);
}

static const GcType canaryType = {
	GcType::tFixed,
	null,
	&canaryFinalizer,
	sizeof(size_t),
	0, {}
};

// Marked as noinline to avoid the compiler storing some result in registers that spill into the
// parent stack frame.
NOINLINE void canaryCreate(Engine &e, size_t value) {
	void *alloc = runtime::allocRaw(e, &canaryType);
	*reinterpret_cast<size_t *>(alloc) = value;
}

size_t canaryFinalized() {
	return atomicRead(canaryCounter);
}

void canaryCollect(Engine &e, size_t original, size_t count) {
	size_t times = 0;
	while (canaryFinalized() - original < count) {
		e.gc.collect();

		if (++times > 1000) {
			STORM_FAIL_S("Failed to cause the GC to finalize objects. Are you using a debug collector?");
		}
	}
}

void runFinalizers(Engine &e) {
	size_t original = canaryFinalized();
	canaryCreate(e);
	clearStack();
	canaryCollect(e, original);
}
