~VMObjectArray() {
#ifdef GC_STOPGAP
if (deferring) {
for (int i = 0; i < _size; i++) {
defer.push(_array[i]);
}
delete[] _array;
} else {
deferring = true;
for (int i = 0; i < _size; i++) {
defer.push(_array[i]);
}
delete[] _array;
while (!defer.empty()) {
defer.pop();
}
deferring = false;
}
#else
delete[] _array;
#endif
}
Saturday, July 27, 2024
Thread local Stopgap
I made the stopgap solution thread local. It's still expensive.
Tuesday, July 16, 2024
Stopgap solution to Cascading Free
Egel runtime objects are instances of C++'s std::shared_ptr. Because egel objects can be deeply nested, it is not uncommon for large structures to go out of scope and result in cascading frees that blow up the C stack.
I.e., the following short egel program would core dump:
# rewrite a list of a million numbers to none
from_to 0 1000000 |> [_ -> none]
I implemented a costly stopgap solution. First, we need a collector for trash. It has an in queue and an out queue, and its sole intention is to retain objects for a while such that they don't go out of scope recursively and blow up the C stack.
I'ld rather convince C++ developers that this is their bug; for all I know cascading frees are a normal thing in reference counted garbage collection and their implementation should be able to handle that.
class GC {
public:
GC() {
}
void push(const VMObjectPtr &o) {
std::lock_guard<std::mutex> lock(mtx_in);
_in.push(o);
}
void copy() {
std::scoped_lock lock(mtx_in, mtx_out);
while (!_in.empty()) {
_out.push(_in.top());
_in.pop();
}
}
bool done() {
std::scoped_lock lock(mtx_in, mtx_out);
return _in.empty() && _out.empty();
}
void empty() {
std::lock_guard<std::mutex> lock(mtx_out);
while (!_out.empty()) _out.pop();
}
void clear() {
while (!done()) {
copy();
empty();
}
}
private:
mutable std::mutex mtx_in;
mutable std::mutex mtx_out;
std::stack<vmobjectptr> _in;
std::stack<vmobjectptr> _out;
};
inline GC garbage_collector;
Now when an array goes out of scope, we place its members in the collector. ~VMObjectArray() {
for (int i = 0; i < _size; i++) {
garbage_collector.push(_array[i]);
}
delete[] _array;
}
And during rewrites we then empty the queues. while ((trampoline != nullptr) && (*run != HALTED)) {
if (*run == RUNNING) {
trampoline = f->reduce(trampoline);
garbage_collector.clear();
} else if (*run == SLEEPING) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else { // *run == HALTED
}
}
I am not happy with this solution. First, I consider it a std::shared_ptr bug that it can blow up the C stack, and second, the performance cost is way too steep for my taste.I'ld rather convince C++ developers that this is their bug; for all I know cascading frees are a normal thing in reference counted garbage collection and their implementation should be able to handle that.
Subscribe to:
Posts (Atom)