From d4fb0a452827633c4817768bca0663467edaa5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Fri, 29 Nov 2024 11:11:46 -0300 Subject: [PATCH 01/26] vm-cpp: move allocator files to a separate dir --- runtime/cpp/{ => Allocator}/GCHeap.cpp | 0 runtime/cpp/{ => Allocator}/GCHeap.h | 0 runtime/cpp/{ => Allocator}/GCSpace.cpp | 0 runtime/cpp/{ => Allocator}/GCSpace.h | 0 runtime/cpp/{ => Allocator}/Memory.h | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename runtime/cpp/{ => Allocator}/GCHeap.cpp (100%) rename runtime/cpp/{ => Allocator}/GCHeap.h (100%) rename runtime/cpp/{ => Allocator}/GCSpace.cpp (100%) rename runtime/cpp/{ => Allocator}/GCSpace.h (100%) rename runtime/cpp/{ => Allocator}/Memory.h (100%) diff --git a/runtime/cpp/GCHeap.cpp b/runtime/cpp/Allocator/GCHeap.cpp similarity index 100% rename from runtime/cpp/GCHeap.cpp rename to runtime/cpp/Allocator/GCHeap.cpp diff --git a/runtime/cpp/GCHeap.h b/runtime/cpp/Allocator/GCHeap.h similarity index 100% rename from runtime/cpp/GCHeap.h rename to runtime/cpp/Allocator/GCHeap.h diff --git a/runtime/cpp/GCSpace.cpp b/runtime/cpp/Allocator/GCSpace.cpp similarity index 100% rename from runtime/cpp/GCSpace.cpp rename to runtime/cpp/Allocator/GCSpace.cpp diff --git a/runtime/cpp/GCSpace.h b/runtime/cpp/Allocator/GCSpace.h similarity index 100% rename from runtime/cpp/GCSpace.h rename to runtime/cpp/Allocator/GCSpace.h diff --git a/runtime/cpp/Memory.h b/runtime/cpp/Allocator/Memory.h similarity index 100% rename from runtime/cpp/Memory.h rename to runtime/cpp/Allocator/Memory.h From 1c463dc2b85a9edf3215798aaf2a4658f658c60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Fri, 29 Nov 2024 11:15:35 -0300 Subject: [PATCH 02/26] vm-cpp: fix paths after moving files --- runtime/cpp/CMakeLists.txt | 2 +- runtime/cpp/Egg.cpp | 2 +- runtime/cpp/Evaluator/Runtime.cpp | 2 +- runtime/cpp/ImageSegment.cpp | 5 +++-- runtime/cpp/Posix/Memory.cpp | 6 +++--- runtime/cpp/Windows/Memory.cpp | 6 +++--- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/runtime/cpp/CMakeLists.txt b/runtime/cpp/CMakeLists.txt index f6264305..0d14afb3 100644 --- a/runtime/cpp/CMakeLists.txt +++ b/runtime/cpp/CMakeLists.txt @@ -34,7 +34,7 @@ find_package(cxxopts REQUIRED) # ************************************************* -file(GLOB ALL_SRC "*.cpp" "Evaluator/*.cpp" "${PLATFORM}/*.cpp") +file(GLOB ALL_SRC "*.cpp" "Evaluator/*.cpp" "Allocator/*.cpp" "${PLATFORM}/*.cpp") add_executable(${EXE} ${ALL_SRC}) target_link_libraries(${EXE} libffi::libffi cxxopts::cxxopts) # link agains libffi diff --git a/runtime/cpp/Egg.cpp b/runtime/cpp/Egg.cpp index 3debb531..51b189ec 100644 --- a/runtime/cpp/Egg.cpp +++ b/runtime/cpp/Egg.cpp @@ -1,5 +1,5 @@ #include "Egg.h" -#include "Memory.h" +#include "Allocator/Memory.h" #include "Evaluator/Runtime.h" #include "Evaluator/Evaluator.h" #include "Evaluator/EvaluationContext.h" diff --git a/runtime/cpp/Evaluator/Runtime.cpp b/runtime/cpp/Evaluator/Runtime.cpp index 65a1105f..b7307641 100644 --- a/runtime/cpp/Evaluator/Runtime.cpp +++ b/runtime/cpp/Evaluator/Runtime.cpp @@ -4,7 +4,7 @@ #include #include "Evaluator.h" -#include "GCHeap.h" +#include "Allocator/GCHeap.h" #include "SAbstractMessage.h" #include "KnownConstants.h" diff --git a/runtime/cpp/ImageSegment.cpp b/runtime/cpp/ImageSegment.cpp index 66c937b6..2d7751a1 100644 --- a/runtime/cpp/ImageSegment.cpp +++ b/runtime/cpp/ImageSegment.cpp @@ -14,7 +14,8 @@ #include "Util.h" #include "ImageSegment.h" -#include "Memory.h" + +#include "Allocator/Memory.h" namespace Egg { @@ -213,4 +214,4 @@ void ImageSegment::readExports(std::istream *data) } -} // namespace Egg \ No newline at end of file +} // namespace Egg diff --git a/runtime/cpp/Posix/Memory.cpp b/runtime/cpp/Posix/Memory.cpp index 379d8961..57b4152d 100644 --- a/runtime/cpp/Posix/Memory.cpp +++ b/runtime/cpp/Posix/Memory.cpp @@ -11,9 +11,9 @@ #include -#include "../Memory.h" -#include "../Egg.h" -#include "../KnownConstants.h" +#include "Allocator/Memory.h" +#include "Egg.h" +#include "KnownConstants.h" using namespace Egg; diff --git a/runtime/cpp/Windows/Memory.cpp b/runtime/cpp/Windows/Memory.cpp index 033c76b4..ae54361e 100644 --- a/runtime/cpp/Windows/Memory.cpp +++ b/runtime/cpp/Windows/Memory.cpp @@ -7,9 +7,9 @@ #include #include -#include "../Memory.h" -#include "../Egg.h" -#include "../KnownConstants.h" +#include "Allocator/Memory.h" +#include "Egg.h" +#include "KnownConstants.h" using namespace Egg; From bba0fbb7c2918e1648e79ef3a37cd14a65900b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sat, 22 Mar 2025 17:45:49 -0300 Subject: [PATCH 03/26] [cppvm] use nullptr instead of 0 for pointer init --- runtime/cpp/Evaluator/Runtime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/cpp/Evaluator/Runtime.cpp b/runtime/cpp/Evaluator/Runtime.cpp index b7307641..947ba3b2 100644 --- a/runtime/cpp/Evaluator/Runtime.cpp +++ b/runtime/cpp/Evaluator/Runtime.cpp @@ -10,7 +10,7 @@ using namespace Egg; -Runtime *Egg::debugRuntime = 0; +Runtime *Egg::debugRuntime = nullptr; void Runtime::initializeEvaluator() { _heap = new GCHeap; From ba771f731bfe398f6c5be3846dde4a15b7c8cb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sat, 22 Mar 2025 18:01:40 -0300 Subject: [PATCH 04/26] [cppvm] fix a couple of compilation warnings --- runtime/cpp/Evaluator/Evaluator.h | 4 ++-- runtime/cpp/Evaluator/SArgumentBinding.h | 2 +- runtime/cpp/Evaluator/SCascadeMessage.h | 2 +- runtime/cpp/Evaluator/SMessage.h | 2 +- runtime/cpp/ImageSegment.cpp | 9 ++++++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/runtime/cpp/Evaluator/Evaluator.h b/runtime/cpp/Evaluator/Evaluator.h index d4f29cc5..5145d0d5 100644 --- a/runtime/cpp/Evaluator/Evaluator.h +++ b/runtime/cpp/Evaluator/Evaluator.h @@ -161,8 +161,8 @@ class Evaluator : public SExpressionVisitor { void visitOpPushR(SOpPushR *anSOpPushR) override; void popFrameAndPrepare(); - virtual void visitOpReturn(SOpReturn *anSOpReturn); - virtual void visitOpNonLocalReturn(SOpNonLocalReturn *anSOpNonLocalReturn); + virtual void visitOpReturn(SOpReturn *anSOpReturn) override; + virtual void visitOpNonLocalReturn(SOpNonLocalReturn *anSOpNonLocalReturn) override; private: void evaluate(); diff --git a/runtime/cpp/Evaluator/SArgumentBinding.h b/runtime/cpp/Evaluator/SArgumentBinding.h index d03567bf..6c8ea4b1 100644 --- a/runtime/cpp/Evaluator/SArgumentBinding.h +++ b/runtime/cpp/Evaluator/SArgumentBinding.h @@ -16,7 +16,7 @@ class SArgumentBinding : public SLocalBinding { ASSERT(false); } - bool isArgument() const { + bool isArgument() const override { return true; } diff --git a/runtime/cpp/Evaluator/SCascadeMessage.h b/runtime/cpp/Evaluator/SCascadeMessage.h index bcd59618..7fd5c8f4 100644 --- a/runtime/cpp/Evaluator/SCascadeMessage.h +++ b/runtime/cpp/Evaluator/SCascadeMessage.h @@ -33,7 +33,7 @@ class SCascadeMessage : public SAbstractMessage { return true; } - SExpression* receiver() { + SExpression* receiver() override { return _cascade ? _cascade->receiver() : nullptr; } diff --git a/runtime/cpp/Evaluator/SMessage.h b/runtime/cpp/Evaluator/SMessage.h index bfcf1b8d..3fedf698 100644 --- a/runtime/cpp/Evaluator/SMessage.h +++ b/runtime/cpp/Evaluator/SMessage.h @@ -22,7 +22,7 @@ class SMessage : public SAbstractMessage { } bool isInlined() const { return _inlined; } - SExpression* receiver () { return _receiver; } + SExpression* receiver () override { return _receiver; } }; } // namespace Egg diff --git a/runtime/cpp/ImageSegment.cpp b/runtime/cpp/ImageSegment.cpp index 2d7751a1..c6093a2c 100644 --- a/runtime/cpp/ImageSegment.cpp +++ b/runtime/cpp/ImageSegment.cpp @@ -54,7 +54,10 @@ ImageSegment::load(std::istream *data) void ImageSegment::fixPointerSlots(const std::vector& imports) { intptr_t delta = this->_currentBase - this->header.baseAddress; - uintptr_t oldBehaviorBase = this->header.baseAddress & (-1 << 32); // discards lower 32 bits + + constexpr auto mask = static_cast(-1LL << 32); // discards lower 32 bits + uintptr_t oldBehaviorBase = this->header.baseAddress & mask; + auto spaceStart = this->spaceStart(); auto current = ((HeapObject::ObjectHeader*)spaceStart)->object(); auto end = (HeapObject*)this->spaceEnd(); @@ -152,7 +155,7 @@ void ImageSegment::readImportStrings(std::istream *data) data->read((char*)&stringSize, sizeof(stringSize)); if (stringSize > bufferSize) { - delete buffer; + delete[] buffer; bufferSize = std::max(stringSize, bufferSize * 2); buffer = new char[bufferSize]; } @@ -160,7 +163,7 @@ void ImageSegment::readImportStrings(std::istream *data) _importStrings.push_back(std::string(buffer, stringSize)); } - delete buffer; + delete[] buffer; } void ImageSegment::readImportDescriptors(std::istream *data) From fd0005c0d09bef97d97d0f34d3cd137ebda1698d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:06:07 -0300 Subject: [PATCH 05/26] [cpp:gc] add most new code/lasses of old-space compactor --- runtime/cpp/Allocator/AllocationZone.cpp | 240 ++++++++++++ runtime/cpp/Allocator/AllocationZone.h | 64 ++++ runtime/cpp/Allocator/G1GC.cpp | 425 +++++++++++++++++++++ runtime/cpp/Allocator/G1GC.h | 82 ++++ runtime/cpp/Allocator/GCHeap.cpp | 134 +++++-- runtime/cpp/Allocator/GCHeap.h | 60 ++- runtime/cpp/Allocator/GCSafepoint.h | 36 ++ runtime/cpp/Allocator/GCSpace.cpp | 132 ++++++- runtime/cpp/Allocator/GCSpace.h | 50 ++- runtime/cpp/Allocator/GarbageCollector.cpp | 294 ++++++++++++++ runtime/cpp/Allocator/GarbageCollector.h | 74 ++++ runtime/cpp/Allocator/Memory.h | 2 + runtime/cpp/Evaluator/PlatformCode.cpp | 21 + runtime/cpp/Evaluator/PlatformCode.h | 21 + runtime/cpp/GCedRef.cpp | 43 +++ runtime/cpp/GCedRef.h | 88 +++++ runtime/cpp/StackGCedRef.cpp | 27 ++ runtime/cpp/StackGCedRef.h | 25 ++ 18 files changed, 1760 insertions(+), 58 deletions(-) create mode 100644 runtime/cpp/Allocator/AllocationZone.cpp create mode 100644 runtime/cpp/Allocator/AllocationZone.h create mode 100644 runtime/cpp/Allocator/G1GC.cpp create mode 100644 runtime/cpp/Allocator/G1GC.h create mode 100644 runtime/cpp/Allocator/GCSafepoint.h create mode 100644 runtime/cpp/Allocator/GarbageCollector.cpp create mode 100644 runtime/cpp/Allocator/GarbageCollector.h create mode 100644 runtime/cpp/Evaluator/PlatformCode.cpp create mode 100644 runtime/cpp/Evaluator/PlatformCode.h create mode 100644 runtime/cpp/GCedRef.cpp create mode 100644 runtime/cpp/GCedRef.h create mode 100644 runtime/cpp/StackGCedRef.cpp create mode 100644 runtime/cpp/StackGCedRef.h diff --git a/runtime/cpp/Allocator/AllocationZone.cpp b/runtime/cpp/Allocator/AllocationZone.cpp new file mode 100644 index 00000000..fde08e8d --- /dev/null +++ b/runtime/cpp/Allocator/AllocationZone.cpp @@ -0,0 +1,240 @@ + +#include "Egg.h" +#include "Memory.h" +#include "AllocationZone.h" +#include "GCSpace.h" +#include "GCHeap.h" +#include "HeapObject.h" + +using namespace Egg; + +AllocationZone::AllocationZone(GCHeap* heap, uintptr_t base, uintptr_t limit) : + _heap(heap), + _base(base), + _nextFree(base), + _limit(limit) +{ + this->build(); +} + +uintptr_t AllocationZone::allocate_(uintptr_t size) +{ + auto oop = this->allocateIfPossibleBumping_(size); + if (!oop) + error_("Out of memory in old space"); + + return oop; +} + +uintptr_t AllocationZone::allocateIfPossibleCurrent_(uintptr_t size) { + //printf("allocing: %d bytes in current\n", size); + return _current->allocateIfPossible_(size); +} + +uintptr_t AllocationZone::allocateIfPossibleBumping_(uintptr_t size) +{ + //printf("allocing: %zu bytes bumping\n", size); + + auto oop = _current->allocateIfPossible_(size); + if (oop) + return oop; + this->bumpSpace(); + return _current->allocateIfPossible_(size); +} + +void AllocationZone::assureFree_(uintptr_t size) +{ + if (_current->softAvailable() < size) this->bumpSpace(); +} + +uintptr_t AllocationZone::availableBytes() +{ + return this->usableBytes() - this->committedBytes(); +} + +void AllocationZone::build() +{ + auto max = this->indexOfPointer_(_limit - 1); + _occupancy.resize(max, 0); + this->bumpSpace(); + this->bumpSpace(); +} + +GCSpace* AllocationZone::bumpSpace() +{ + _current = _next; + //if (_current) // uninitialized on first bump + // printf("bumped space - new limits: 0x%p - 0x%p\n", (void*)_current->base(), (void*)_current->reservedLimit()); + if (_emptySpaces.empty()) this->createEmptySpace(); + + _next = _emptySpaces.back(); + _emptySpaces.pop_back(); + _next->commitMemory_(_next->reservedSize()); + _next->_softLimit = _next->_committedLimit; + this->markAsFull_(_next); + return _next; +} + +uintptr_t AllocationZone::committedBytes() +{ + auto sum = 0; + for (auto space : _spaces) + sum = sum + space->committedSize(); + return sum; +} + +GCSpace *AllocationZone::createEmptySpace() +{ + if (_nextFree == _limit) { + printf("_nextFree: %" PRIxPTR ". _limit: %" PRIxPTR "\n", _nextFree, _limit); + error_("Out of space in old zone"); + } + auto start = _nextFree; + auto end = _nextFree + SPACE_SIZE; + _nextFree = end + SPACE_SIZE; + auto s = GCSpace::allocatedAt_limit_(start, end); + s->_name ="Old"; + //printf("adding old space nÂș %" PRIdPTR "\n", _spaces.size()); + _spaces.push_back(s); + _emptySpaces.push_back(s); + _heap->addSpace_(s); + + //printf("bumping old zone limit: _nextFree: %" PRIxPTR ". _limit: %" PRIxPTR "\n", _nextFree, _limit); + + return s; +} + +GCSpace *AllocationZone::currentSpace() +{ + return _current; +} + +uintptr_t AllocationZone::indexOfPointer_(uintptr_t address) +{ + return ((address - _base) >> SPACE_SIZE_SHIFT) / 2 + 1; // half of the space are forwarders +} + +uintptr_t AllocationZone::indexOfSpace_(GCSpace *space) +{ + return this->indexOfPointer_(space->base()); +} + +void AllocationZone::markAsFull_(GCSpace *space) +{ + /* + We mark spaces as fully occupied so that + the GC doesn't see them as ready to be freed. + */ + auto index = this->indexOfSpace_(space); + _occupancy[index - 1] = SPACE_SIZE; +} + +uintptr_t AllocationZone::occupiedBytes() +{ + auto sum = 0; + for (size_t i = 0; i < _spaces.size(); i++) + if (!_spaces[i]->isFree()) + sum = sum + _occupancy[i]; + return sum; +} + +void AllocationZone::recycleSpace_(GCSpace *space) +{ + _emptySpaces.push_back(space); +} + +uintptr_t AllocationZone::regionCount() +{ + return this->indexOfPointer_(_nextFree - 1); +} + +intptr_t AllocationZone::regionIndexOf_(HeapObject *object) +{ + auto pointer = (uintptr_t)object; + if (pointer < _base) return -1; + if (pointer >= _limit) return -1; + return this->indexOfPointer_(pointer); +} + +void AllocationZone::releaseEvacuated_(std::vector *evacuated) +{ + for (uint32_t i = 1; i < _spaces.size(); i++) + { + auto space = _spaces[i-1]; + auto used = _occupancy[i-1]; + bool recycle = (*evacuated)[i-1] || (used == 0 && space->committedSize() > 0); + if (recycle) + { + auto base = space->_base; + auto size = space->reservedSize(); + space->_next = base; + space->_committedLimit = base; + space->_softLimit = base; + DecommitMemory(base, size * 2); + this->recycleSpace_(space); + } + } +} + +void AllocationZone::relocate_(intptr_t anInteger) +{ + _base = _base + anInteger; + _nextFree = _nextFree + anInteger; + _limit = _limit + anInteger; +} + +uintptr_t AllocationZone::reservedBytes() +{ + return _limit - _base; +} + +void AllocationZone::resetOccupancy() +{ + std::fill(_occupancy.begin(), _occupancy.end(), 0); + this->markAsFull_(_current); + this->markAsFull_(_next); +} + +HeapObject *AllocationZone::shallowCopyCommiting_(HeapObject *object) +{ + auto copy = _current->shallowCopyCommitting_(object); + if (copy) + return copy; + + auto size = object->bodySizeInBytes(); + if (size > GCHeap::LargeThreshold) + { + auto space = _heap->createLargeSpace_(size); + return space->shallowCopyCommitting_(object); + } + this->bumpSpace(); + return _current->shallowCopyCommitting_(object); +} + +uintptr_t AllocationZone::size() +{ + return _limit - _base; +} + +void AllocationZone::updateRegionOccupancy_(HeapObject *object) +{ + auto index = this->regionIndexOf_(object); + if (index < 0) return; + + auto bytes = _occupancy[index - 1]; + _occupancy[index-1] = bytes + object->bodySizeInBytes(); +} + +uintptr_t AllocationZone::usableBytes() +{ + // Half of each reserved area is held for evacuation purposes only and not directly usable. + return this->reservedBytes() / 2; +} + +uintptr_t AllocationZone::usedBytes() +{ + auto count = 0; + for (auto s : _spaces) + count = count + s->usedSize(); + return count; +} diff --git a/runtime/cpp/Allocator/AllocationZone.h b/runtime/cpp/Allocator/AllocationZone.h new file mode 100644 index 00000000..7afb99c4 --- /dev/null +++ b/runtime/cpp/Allocator/AllocationZone.h @@ -0,0 +1,64 @@ +#ifndef _ALLOCATION_ZONE_H_ +#define _ALLOCATION_ZONE_H_ + +#include +#include "Egg.h" + +namespace Egg { + +class HeapObject; +class GCSpace; +class GCHeap; + +class AllocationZone { + GCHeap *_heap; + uintptr_t _base, _nextFree, _limit; + GCSpace *_current, *_next; + std::vector _spaces; + std::vector _emptySpaces; + std::vector _occupancy; + +public: + AllocationZone(GCHeap* heap, uintptr_t base, uintptr_t limit); + + // 256 kb spaces + const uintptr_t SPACE_SIZE_SHIFT = 18; + const uintptr_t SPACE_SIZE = 1 << SPACE_SIZE_SHIFT; + + uintptr_t base() { return _base; } + uintptr_t limit() { return _limit; } + std::vector* occupancy() { return &_occupancy; } + uintptr_t regionSize() { return SPACE_SIZE; } + std::vector& spaces() { return _spaces; } + + uintptr_t allocate_(uintptr_t size); + uintptr_t allocateIfPossibleCurrent_(uintptr_t size); + uintptr_t allocateIfPossibleBumping_(uintptr_t size); + void assureFree_(uintptr_t size); + uintptr_t availableBytes(); + void build(); + GCSpace* bumpSpace(); + uintptr_t committedBytes(); + GCSpace* createEmptySpace(); + GCSpace* currentSpace(); + uintptr_t indexOfPointer_(uintptr_t address); + uintptr_t indexOfSpace_(GCSpace *space); + void markAsFull_(GCSpace *space); + uintptr_t occupiedBytes(); + void recycleSpace_(GCSpace *space); + uintptr_t regionCount(); + intptr_t regionIndexOf_(HeapObject *object); + void releaseEvacuated_(std::vector *evacuated); + void relocate_(intptr_t anInteger); + uintptr_t reservedBytes(); + void resetOccupancy(); + HeapObject* shallowCopyCommiting_(HeapObject *object); + uintptr_t size(); + void updateRegionOccupancy_(HeapObject *object); + uintptr_t usableBytes(); + uintptr_t usedBytes(); +}; + +} // namespace Egg + +#endif // ~ _ALLOCATION_ZONE_H_ ~ diff --git a/runtime/cpp/Allocator/G1GC.cpp b/runtime/cpp/Allocator/G1GC.cpp new file mode 100644 index 00000000..73977128 --- /dev/null +++ b/runtime/cpp/Allocator/G1GC.cpp @@ -0,0 +1,425 @@ +#include + +#include "G1GC.h" + +#include + +#include "AllocationZone.h" +#include "KnownObjects.h" +#include "KnownConstants.h" + +using namespace Egg; + +G1GC::G1GC(Runtime *runtime, AllocationZone *oldZone, GCHeap *heap) : + GarbageCollector(runtime, oldZone, heap) +{ + _countdown = 1 * MB; + _forwarderOffset = _oldZone->regionSize(); + +} + +HeapObject *G1GC::copyOf_(HeapObject *anObject) +{ + auto forwarder = ((uintptr_t)anObject) + _forwarderOffset; + //printf("fetching forwarder of %#" PRIxPTR " -> %#" PRIxPTR "(orig: %s, copy: %s)\n", anObject, *(HeapObject**)forwarder, anObject->printString().c_str(), (*(HeapObject**)forwarder)->printString().c_str()); + return (*(HeapObject**)forwarder); +} + +void G1GC::determineEvacuatedSpaces() { + auto threshold = uintptr_t(_forwarderOffset * 0.8); // if space uses less than this, it is a candidate for evacuation + auto occupancy = _oldZone->occupancy(); // the occupancy was computed in marking stage of prev GC pass + auto max = _oldZone->regionCount(); + auto headroom = _oldZone->availableBytes() * 9 / 10; // avail space for evacuating + + _evacuated.resize(occupancy->size(), false); // initialize the list with all false elements + + auto current = _oldZone->currentSpace(); + for (uint32_t i = 1; i <= max; i++) + { + auto spaceOccup = (*occupancy)[i-1]; + auto space = _oldZone->spaces()[i-1]; + //if (space) printf("space size: %" PRIdPTR ", used: %" PRIdPTR "\n", space->reservedSize(), spaceOccup); + if (0 < spaceOccup && spaceOccup < threshold) + { + //auto space = _oldZone->spaces()[i-1]; + auto used = space->usedSize(); + if (space != current && headroom > used) + { + headroom = headroom - used; + auto size = space->reservedSize(); + CommitMemory(space->_base + size, size); + _evacuated[i-1] = true; + /* + printf("evacuating space: %u (%.1f%% used) - zone: 0x%" PRIxPTR " - 0x%" PRIxPTR "\n", + i - 1, + 100.0 * (float)spaceOccup / (float)_forwarderOffset, + space->base(), + space->reservedLimit()); + */ + } + } + } + + _heap->oldZone()->resetOccupancy(); // will be recomputed in this GC pass, for next pass +} + +HeapObject *G1GC::evacuate_(HeapObject *anObject) +{ + auto copy = _oldZone->shallowCopyCommiting_(anObject); + if (anObject->isRemembered()) + copy->beRemembered(); + + auto forwarder = ((uintptr_t)anObject) + _forwarderOffset; + (*(HeapObject**)forwarder)= copy; + //printf("object %#" PRIxPTR " was forwarded to %#" PRIxPTR "(orig: %s, copy: %s)\n", anObject, copy, anObject->printString().c_str(), copy->printString().c_str()); + + return copy; +} + +bool G1GC::hasToEvacuate_(HeapObject *anObject) +{ + auto index = _oldZone->regionIndexOf_(anObject); + return index > 0 && _evacuated[index-1]; +} + +void G1GC::initializeLocals() +{ + this->determineEvacuatedSpaces(); + auto current = 0; + auto evac = 0; + + for (auto space : _oldZone->spaces()) + { + current += space->usedSize(); + } + + for (auto i = 0; i < _evacuated.size(); i++) + { + if (_evacuated[i]) + evac+= _oldZone->spaces()[i]->reservedSize(); + } + + //printf("going to do GC - current old heap size: %d kb, going to free aprox %x\n", current / 1024, evac); + + _stack.reserve(64 * KB); + _largeSize = _heap->_largeSpaces.size(); + GarbageCollector::initializeLocals(); +} + +void G1GC::releaseLocals() +{ + _oldZone->releaseEvacuated_(&_evacuated); + _evacuated.clear(); + this->resetCountdown(); + this->sweepLargeSpaces(); + _stack.clear(); + _scanned = nullptr; + GarbageCollector::releaseLocals(); +} + +void G1GC::resetCountdown() +{ + /* + * Try to set an amount that is not too large, not too small. Full GC + * allocation countdown has to be relative to heap size (bigger heaps + * result in bigger countdowns). + * Rules: + * - aims for 25% of used size minus what is known to be garbage + * - if near exhaustion of mem (available is small), then make countdown smaller + */ + + intptr_t garbage = _oldZone->committedBytes() - _oldZone->occupiedBytes(); + intptr_t desired = intptr_t(_oldZone->usedBytes() * 0.25) - garbage; + intptr_t available = _oldZone->availableBytes() * 9 / 10; + + // set countdown to not less that 3*region size, not more than old zone remaining free space + _countdown = std::max((intptr_t)_oldZone->regionSize()*3, std::min(desired, available)); + + /* + GenGC unimplemented + intptr_t garbage = _oldZone->committedBytes() - _oldZone->occupiedBytes(); + intptr_t bytes = intptr_t(_oldZone->usedBytes() * 0.25) - garbage; + auto available = _oldZone->availableBytes() * 9 / 10; + auto young = _heap->eden()->reservedSize() + _heap->from()->reservedSize(); + _countdown = std::min(std::max(25 * MB, bytes), available - young); + */ +} + + +void G1GC::updateWeak_at_(HeapObject *weakContainer, uintptr_t anInteger) +{ + auto object = weakContainer->slotAt_(anInteger); + if (object->isSmallInteger()) + return; + + auto hobject = object->asHeapObject(); + + if (!hobject->hasBeenSeen()) + { + weakContainer->slotAt_(anInteger) = (Object*)_tombstone; + } + if (!this->hasToEvacuate_(hobject)) + return; + + auto updated = this->copyOf_(hobject); + weakContainer->slotAt_(anInteger) = (Object*)updated; +} + +bool G1GC::checkReachablePropertyOf_(HeapObject *ephemeron) +{ + auto key = ephemeron->slotAt_(1); + return key->isSmallInteger() || key->asHeapObject()->hasBeenSeen(); +} + + +void G1GC::doCollect() +{ + GarbageCollector::doCollect(); + // unimplemented GenGC + // this->purgeRememberedSet(); // RE-ENABLE AFTER PLUGGING BACK GENGC +} + +void G1GC::followClosure() +{ + this->followGCedRefs(); + while (!_stack.empty() || !_stacks->empty()) + { + this->followObjects(); + this->scanStacks(); + } +} + +void G1GC::followGCedRefs() +{ + _runtime->gcedRefsDo_([this](GCedRef *ref) { + this->queue_from_to_((HeapObject*)ref->getRaw(), 1, 1); + }); +} + + +template +T pop_and_return(std::vector& vec) { + T value = std::move(vec.back()); + vec.pop_back(); + return value; +} + +void G1GC::followObjects() +{ + while (!_stack.empty()) + { + _limit = (uint32_t)pop_and_return(_stack); + _index = (uint32_t)pop_and_return(_stack); + _scanned = (HeapObject*)pop_and_return(_stack); + while (_index <= _limit) + { + if (_index == 0) + this->scanBehavior(); + else + this->scanSlot(); + } + } +} + +uintptr_t G1GC::initialContainerCapacity() +{ + return this->workSpaceSize() / 1000; +} + +/* +void G1GC::purgeRememberedSet() +{ + auto rs = _memory->rememberedSet(); + auto kept = 0; + for (uint32_t i = 1; i <= rs->size(); i++) + { + auto object = rs[i-1]; + if (object->hasBeenSeen()) + { + if (this->hasToEvacuate_(object)) + object = this->copyOf_(object); + kept = kept + 1; + rs[kept] = object; + } + } + rs->size_(kept); + +} +*/ + +void G1GC::queue_from_to_(HeapObject *anObject, uintptr_t start, uintptr_t end) +{ + _stack.push_back((uintptr_t)anObject); + _stack.push_back(start); + _stack.push_back(end); +} + +void G1GC::queueCurrent() +{ + _stack.push_back((uintptr_t)_scanned); + _stack.push_back(_index + 1); + _stack.push_back(_limit); +} + +void G1GC::scan_from_to_(HeapObject *anObject, uintptr_t start, uintptr_t end) +{ + _stack.push_back((uintptr_t)anObject); + _stack.push_back(start); + _stack.push_back(end); +} + +void G1GC::scanBehavior() +{ + auto slot = _scanned->behavior(); +// printf("scanning behavior of %#" PRIxPTR " (%s)", (uintptr_t)_scanned, _scanned->printString().c_str()); +// fflush(stdout); +// printf(", which is %#" PRIxPTR "( %s)\n", (uintptr_t)slot, slot->printString().c_str()); + if (((Object*)slot)->isSmallInteger()) + { + _index = _index + 1; + return; + } + auto evacuate = this->hasToEvacuate_(slot); + if (slot->hasBeenSeen()) + { + if (evacuate) + { + slot = this->copyOf_(slot); + _scanned->behavior(slot); + } + _index = _index + 1; + return; + } + slot->beSeen(); + if (evacuate) + { + slot = this->evacuate_(slot); + _scanned->behavior(slot); + } + else + { + this->updateRegionOccupancy_(slot); + } + + if (slot->isSpecial()) + this->rememberSpecial_(slot); + + if (_index < _limit) + this->queueCurrent(); + + _index = 0; + _limit = slot->strongPointersSize(); + _scanned = slot; +} + +void G1GC::scanSlot() +{ + // scanned can be a heap object or a chunk of stack frame, we cannot use slotAt_ + auto slot = ((Object**)_scanned)[_index-1]; + auto stack = debugRuntime->_evaluator->context()->stack(); +// if ((uintptr_t)_scanned < (uintptr_t)&stack[0] || (uintptr_t)_scanned > (uintptr_t)&stack[0xFFFF]) +// printf("scanning slot %d of %#" PRIxPTR " (%s)", _index, (uintptr_t)_scanned, _scanned->printString().c_str()); +// else +// printf("scanning stack slot of frame %#" PRIxPTR " at %d" , (uintptr_t)_scanned, _index); + +// fflush(stdout); +// printf(" which is %#" PRIxPTR " (%s)\n", (uintptr_t)slot, slot->printString().c_str()); + + if (slot->isSmallInteger()) + { + _index = _index + 1; + return; + } + + auto hslot = slot->asHeapObject(); + + auto evacuate = this->hasToEvacuate_(hslot); + if (hslot->hasBeenSeen()) + { + if (evacuate) + { + hslot = this->copyOf_(hslot); + ((Object**)_scanned)[_index-1] = (Object*)hslot; + } + _index = _index + 1; + return; + } + + hslot->beSeen(); + + if (evacuate) + { + hslot = this->evacuate_(hslot); + ((Object**)_scanned)[_index-1] = (Object*)hslot; + } + else + { + this->updateRegionOccupancy_(hslot); + } + + if (hslot->isSpecial()) + this->rememberSpecial_(hslot); + + if (_index < _limit) + this->queueCurrent(); + + _index = 0; + _limit = hslot->strongPointersSize(); + _scanned = hslot; +} + +void G1GC::scanThreadLocalStorage_(HeapObject *thread) +{ + // unimplemented yet +} + +void G1GC::scanThreadLocalStorage_at_(HeapObject *thread, uint32_t i) +{ + // uimplemented yet +} + +void G1GC::scanTopSlot_(HeapObject *processStack) +{ + error("G1GC::scanTopSlot_ not implemented"); + //this->queue_from_to_(_stackWrapper->sp->asObject(), 1, 1); +} + +void G1GC::sweepLargeSpaces() +{ + auto used = 0; + auto &large = _heap->_largeSpaces; + auto ®istered = _heap->_spaces; + for (int i = 1; i <= _largeSize; i++) + { + GCSpace* s = large[i-1]; + if (s->firstObject()->hasBeenSeen()) + { + used = used + 1; + large[used] = s; + } + else + { + FreeMemory(s->base(), s->reservedSize()); + registered.erase(std::remove(registered.begin(), registered.end(), s), registered.end()); + } + } + + for (uint32_t i = _largeSize + 1; i <= large.size(); i++) + { + auto s = large[i-1]; + used = used + 1; + large[used-1] = s; + } + + for (uint32_t i = used + 1; i <= large.size(); i++) + { + large[i-1] = nullptr; + } + + large.resize(used); +} + +void G1GC::updateRegionOccupancy_(HeapObject *object) +{ + _heap->oldZone()->updateRegionOccupancy_(object); +} diff --git a/runtime/cpp/Allocator/G1GC.h b/runtime/cpp/Allocator/G1GC.h new file mode 100644 index 00000000..b23d0342 --- /dev/null +++ b/runtime/cpp/Allocator/G1GC.h @@ -0,0 +1,82 @@ +#ifndef _G1GC_H_ +#define _G1GC_H_ + +#include +#include "../HeapObject.h" +#include "GarbageCollector.h" + +/** + * This class implements a garbage-first inspired GC for the old heap. + * The main idea is that the algorithm doesn't compact all old spaces + * in the same pass, but only those which are most fragmented. + * The old heap is managed by a allocation zone object, which splits + * memory fixed size GCSpaces (larger objects that wouldn't fit are + * created in another place, outside the allocation zone). + * + * When the system decides it is time to reclaim space, the GC uses + * the fragmentation stats from last G1 pass to decide which spaces + * it will evacuate, according to an occupation threshold. + * + * This allows to do forwarding during marking phase: when the tracer + * starts, it knows all spaces that have to be evacuated. When each + * object is reached for the first time in an evacuated space, it is + * copyied to a different space using bump allocation and a forwarder + * is left. This way, the GC only does one pass through the heap. + * + * After GC pass finishes, some 'floating garbage' will still be left + * because not all spaces are compacted. However, that space should be + * a small proportion of the used memory. +*/ + +namespace Egg { + +class HeapObject; +class AllocationZone; + +class G1GC : public GarbageCollector { +public: + std::vector _stack; + uint32_t _limit, _index; + HeapObject* _scanned; + uint32_t _largeSize; + std::vector _evacuated; + uintptr_t _forwarderOffset; + intptr_t _countdown; + G1GC(Runtime *runtime, AllocationZone *oldZone, GCHeap *memory); + virtual ~G1GC() = default; + + HeapObject* copyOf_(HeapObject *anObject); + HeapObject* evacuate_(HeapObject *anObject); + bool hasReachedCountdown() { return _countdown <= 0; } + bool hasToEvacuate_(HeapObject *anObject); + virtual void initializeLocals(); + virtual void releaseLocals(); + void resetCountdown(); + void determineEvacuatedSpaces(); + void tenured_(intptr_t anInteger) { _countdown = _countdown - anInteger; } + void updateWeak_at_(HeapObject *weakContainer, uintptr_t anInteger); + + bool checkReachablePropertyOf_(HeapObject *ephemeron); + void doCollect(); + void followClosure(); + void followGCedRefs(); + void followObjects(); + uintptr_t initialContainerCapacity(); + //void purgeRememberedSet(); // RE-ENABLE AFTER PLUGGING BACK GENGC + void queue_from_to_(HeapObject *anObject, uintptr_t start, uintptr_t end); + void queueCurrent(); + void scan_from_to_(HeapObject *anObject, uintptr_t start, uintptr_t end); + void scanBehavior(); + void scanSlot(); + void scanThreadLocalStorage_(HeapObject *thread); + void scanThreadLocalStorage_at_(HeapObject *thread, uint32_t i); + void scanTopSlot_(HeapObject *processStack); + void sweepLargeSpaces(); + void updateRegionOccupancy_(HeapObject *object); + uintptr_t workSpaceSize() { return 20 * MB; } + +}; + +} // namespace Egg + +#endif // ~ _G1GC_H_ ~ diff --git a/runtime/cpp/Allocator/GCHeap.cpp b/runtime/cpp/Allocator/GCHeap.cpp index 2d692590..d286e518 100644 --- a/runtime/cpp/Allocator/GCHeap.cpp +++ b/runtime/cpp/Allocator/GCHeap.cpp @@ -1,54 +1,104 @@ +#include + #include "GCHeap.h" +#include "GCSpace.h" #include "KnownObjects.h" - -#include +#include "AllocationZone.h" +#include "G1GC.h" using namespace Egg; -Egg::GCHeap::GCHeap() +GCHeap::GCHeap(Runtime *runtime) : _runtime(runtime), _gcNeeded(false) { - eden = this->addNewSpaceSized(16*MB); + _atGCSafepoint = false; // defaults to false, only particular points enable GC + _eden = this->addNewSpaceSized_(16*MB); + _eden->_name = "Eden"; + + auto size = 128*MB; + auto address = ReserveMemory(0x100000000,size); + _oldZone = new AllocationZone(this, address, address + size); + _fullGC = new G1GC(_runtime, _oldZone, this); } -Egg::GCHeap::~GCHeap() +GCHeap::~GCHeap() { - for (auto &space : spaces) + for (auto &space : _spaces) delete space; } -GCSpace *GCHeap::addSpace(GCSpace *space) +GCSpace *GCHeap::addSpace_(GCSpace *space) { - spaces.push_back(space); + _spaces.push_back(space); return space; } -GCSpace *GCHeap::addNewSpaceSized(int size) { +GCSpace *GCHeap::addNewSpaceSized_(int size) { auto space = new GCSpace(size); - return this->addSpace(space); + return this->addSpace_(space); } -uintptr_t GCHeap::allocate(uint32_t size) { - auto result = eden->allocate(size); +GCSpace *Egg::GCHeap::createLargeSpace_(uintptr_t size) +{ + auto address = ReserveMemory(0, size); + if (!address) + error_(std::string("Not enough memory to allocate ") + std::to_string(size) + "bytes"); + + auto limit = address + size; + auto space = GCSpace::allocatedAt_limit_(address, limit); + + space->_name = "Large"; + space->_committedLimit = limit; + space->_softLimit = limit; + this->addSpace_(space); + _largeSpaces.push_back(space); + return space; +} + +/* +uintptr_t GCHeap::allocate_(uint32_t size) { + // GenGC unimplemented yet + auto result = _eden->allocateIfPossible_(size); if (result) return result; - if (size > LargeThreshold) - return this->allocateLarge(size); - - if (!GC_CRITICAL) + if (size > LargeThreshold) + return this->allocateLarge_(size); + + if (atGCSafepoint()) this->collectIfTime(); - return this->allocateCommitting(size); + return this->allocateCommitting_(size); } +*/ -uintptr_t GCHeap::allocateLarge(uint32_t size) { - auto space = this->addNewSpaceSized(size); - return space->allocate(size); +uintptr_t GCHeap::allocate_(uint32_t size) { + auto result = _oldZone->allocateIfPossibleCurrent_(size); + if (result) { + _fullGC->tenured_(size); + return result; + } + if (size > LargeThreshold) + return this->allocateLarge_(size); + + if (this->isAtGCSafepoint()) + this->collectIfTime(); + else + requestGC(); + + _fullGC->tenured_(size); + + return _oldZone->allocateIfPossibleBumping_(size); } -uintptr_t GCHeap::allocateCommitting(uint32_t size) +uintptr_t GCHeap::allocateLarge_(uint32_t size) { + auto space = this->addNewSpaceSized_(size); + return space->allocateIfPossible_(size); +} + + +uintptr_t GCHeap::allocateCommitting_(uint32_t size) { - auto result = eden->allocate(size); + auto result = _eden->allocateIfPossible_(size); if (result) return result; // should allocate committing in old here @@ -61,7 +111,7 @@ HeapObject* GCHeap::allocateSlots_(uint32_t size) { bool small = size <= HeapObject::MAX_SMALL_SIZE; auto headerSize = small ? 8 : 16; auto totalSize = headerSize + size * sizeof(uintptr_t); - auto buffer = this->allocate(totalSize); + auto buffer = this->allocate_(totalSize); std::memset((void*)buffer, 0, headerSize); HeapObject *result; @@ -89,7 +139,7 @@ HeapObject* GCHeap::allocateBytes_(uint32_t size) bool small = size <= HeapObject::MAX_SMALL_SIZE; auto headerSize = small ? 8 : 16; auto totalSize = headerSize + align(size, sizeof(uintptr_t)); - auto buffer = this->allocate(totalSize); + auto buffer = this->allocate_(totalSize); std::memset((void*)buffer, 0, totalSize); HeapObject *result; @@ -112,10 +162,11 @@ HeapObject* GCHeap::allocateBytes_(uint32_t size) return result; } -void GCHeap::collectIfTime() +/* GenGC unimplemented yet + void GCHeap::collectIfTime() { - eden->commitMemory(256*KB); - bool success = eden->increaseSoftLimit_(256 * KB); + _eden->commitMemory_(256*KB); + bool success = _eden->increaseSoftLimit_(256 * KB); if (success) return; this->collectYoung(); @@ -123,8 +174,37 @@ void GCHeap::collectIfTime() //if (fullCollector->reachedCountdown()) // this->collectOld(); } +*/ + +bool GCHeap::isAtGCSafepoint() +{ + return _atGCSafepoint; +} + +void GCHeap::collectIfTime() +{ + if (_gcNeeded || _fullGC->hasReachedCountdown()) + this->collectOld(); +} void GCHeap::collectYoung() { error("Fixme: Collect young hasn't been implemented yet"); } + +void GCHeap::collectOld() +{ + //warning("GCHeap::collectOld()\n"); + for (auto space : _spaces) { + space->unmarkAll(); + } + + //_runtime->checkCache(); + _fullGC->collect(); + //_runtime->checkCache(); + // this->rescueEphemerons(); + finishedGC(); + + //warning("GCHeap::collectOld() done\n"); + +} \ No newline at end of file diff --git a/runtime/cpp/Allocator/GCHeap.h b/runtime/cpp/Allocator/GCHeap.h index ea383b70..e15f2810 100644 --- a/runtime/cpp/Allocator/GCHeap.h +++ b/runtime/cpp/Allocator/GCHeap.h @@ -1,31 +1,65 @@ #ifndef _GCHEAP_H #define _GCHEAP_H -#include "GCSpace.h" -#include "Memory.h" #include +#include + +#include "Memory.h" +#include "Egg.h" namespace Egg { +class GCSpace; +class AllocationZone; +class G1GC; +class Runtime; + class GCHeap { - GCSpace *eden, *from, *to; - const int KB = 1024; - const int MB = 1024 * KB; - const int LargeThreshold = 64 * KB; + GCSpace *_eden, *_from, *_to; + uintptr_t _youngBase, _youngLimit; + + Runtime *_runtime; + AllocationZone *_oldZone; + G1GC *_fullGC; + + bool _atGCSafepoint; // when true, it is allowed to start GC (specially, to move objects) + bool _gcNeeded; // set when fast-path allocation fails, will be done later at GC safepoints + public: - std::vector spaces; - GCHeap(); + static const int LargeThreshold = 64 * Egg::KB; + + std::vector _spaces; + std::vector _largeSpaces; + + GCHeap(Runtime *runtime); ~GCHeap(); - GCSpace* addSpace(GCSpace *space); - GCSpace* addNewSpaceSized(int size); + AllocationZone* oldZone() { return _oldZone; } + + GCSpace* eden() { return _eden; } + GCSpace* from() { return _from; } + GCSpace* to() { return _to; } + + uintptr_t youngBase() { return _youngBase; } + uintptr_t youngLimit() { return _youngLimit; } + + GCSpace* addSpace_(GCSpace *space); + GCSpace* addNewSpaceSized_(int size); + + GCSpace* createLargeSpace_(uintptr_t size); + + uintptr_t allocate_(uint32_t size); + uintptr_t allocateLarge_(uint32_t size); + uintptr_t allocateCommitting_(uint32_t size); - uintptr_t allocate(uint32_t size); - uintptr_t allocateLarge(uint32_t size); - uintptr_t allocateCommitting(uint32_t size); + bool isAtGCSafepoint(); + void beAtGCSafepoint(bool newState) { _atGCSafepoint = newState; } + void requestGC() { _gcNeeded = true; } + void finishedGC() { _gcNeeded = false; } void collectIfTime(); void collectYoung(); + void collectOld(); HeapObject* allocateSlots_(uint32_t size); HeapObject* allocateBytes_(uint32_t size); diff --git a/runtime/cpp/Allocator/GCSafepoint.h b/runtime/cpp/Allocator/GCSafepoint.h new file mode 100644 index 00000000..213654cb --- /dev/null +++ b/runtime/cpp/Allocator/GCSafepoint.h @@ -0,0 +1,36 @@ + +#ifndef _GCSAFEPOINT_H_ +#define _GCSAFEPOINT_H_ + +#include "GCHeap.h" + +namespace Egg { + +/** + * My instances, on construction, allow the heap to be collected (i.e. to move objects). + * When destructed, we restore the previous state. + * + * Creating an instance does not automatically cause GC. Users should be aware of potentially + * GCing calls (basically, calls to methods that potentially allocate objects). At that point, + * all on-the-fly references to heap objects (raw pointers not known by GC) must be wrapped by + * GCedRefs or dead. + */ + +class GCSafepoint { + GCHeap* _heap; + bool _prevState; + + public: + GCSafepoint(GCHeap *heap) : _heap(heap) { + _prevState = _heap->isAtGCSafepoint(); + _heap->beAtGCSafepoint(true); + } + + ~GCSafepoint() { + _heap->beAtGCSafepoint(_prevState); + } +}; + +} + +#endif // ~ _GCSAFEPOINT_H_ diff --git a/runtime/cpp/Allocator/GCSpace.cpp b/runtime/cpp/Allocator/GCSpace.cpp index dfd09531..4171ccca 100644 --- a/runtime/cpp/Allocator/GCSpace.cpp +++ b/runtime/cpp/Allocator/GCSpace.cpp @@ -1,24 +1,59 @@ + +#include "Egg.h" +#include "Memory.h" #include "GCSpace.h" +#include "HeapObject.h" #include using namespace Egg; -Egg::GCSpace::GCSpace(int size) +GCSpace::GCSpace(int size) { - _base = Egg::ReserveMemory(0, size); + _base = ReserveMemory(0, size); if (!_base) - error("Failed to reserve memory."); + error_("Failed to reserve memory."); _next = _softLimit = _committedLimit = _base; _reservedLimit = _base + size; } -Egg::GCSpace::~GCSpace() +GCSpace* GCSpace::allocatedAt_limit_(uintptr_t base, uintptr_t limit, bool empty) +{ + GCSpace *result = new GCSpace(); + result->_base = base; + auto end = empty ? base : limit; + result->_next = result->_softLimit = result->_committedLimit = end; + result->_reservedLimit = limit; + + return result; +} + +GCSpace* GCSpace::allocatedAt_size_(uintptr_t base, uintptr_t size, bool empty) +{ + return allocatedAt_limit_(base, base + size, empty); +} + +GCSpace::~GCSpace() { if (_base) - Egg::FreeMemory(_base, _reservedLimit - _base); + FreeMemory(_base, _reservedLimit - _base); +} + +uintptr_t GCSpace::committedSize() +{ + return _committedLimit - _base; +} + +uintptr_t GCSpace::usedSize() +{ + return _next - _base; +} + +uintptr_t GCSpace::softAvailable() +{ + return _softLimit - _next; } uintptr_t GCSpace::reservedSize() @@ -26,14 +61,23 @@ uintptr_t GCSpace::reservedSize() return _reservedLimit - _base; } -void GCSpace::commitMemory(uint32_t delta) +bool GCSpace::commitMemoryUpTo_(uintptr_t address) { - auto newLimit = std::min(_committedLimit + delta, _reservedLimit); - Egg::CommitMemory(_committedLimit, newLimit - _base); + auto newLimit = std::min(pagealign(address), _reservedLimit); + if (newLimit < address) + return false; + + CommitMemory(_committedLimit, newLimit - _base); _committedLimit = newLimit; + return true; } -uintptr_t GCSpace::allocate(uint32_t size) { +bool GCSpace::commitMemory_(uint32_t delta) +{ + return this->commitMemoryUpTo_(_committedLimit + delta); +} + +uintptr_t GCSpace::allocateIfPossible_(uint32_t size) { auto result = _next; auto end = _next + size; if (end < _softLimit) @@ -45,7 +89,19 @@ uintptr_t GCSpace::allocate(uint32_t size) { return 0; } -bool Egg::GCSpace::increaseSoftLimit_(uint32_t delta) +uintptr_t GCSpace::allocateCommittingIfNeeded_(uint32_t size) +{ + auto answer = _next; + auto next = answer + size; + if (next <= _softLimit || this->commitMemoryUpTo_(next)) + { + _next = next; + return answer; + } + else return 0; +} + +bool GCSpace::increaseSoftLimit_(uint32_t delta) { int32_t available = _committedLimit - _softLimit; if (available < 0) return false; @@ -53,3 +109,59 @@ bool Egg::GCSpace::increaseSoftLimit_(uint32_t delta) _softLimit = _softLimit + std::min(delta, (uint32_t)available); return true; } + +HeapObject *GCSpace::firstObject() +{ + return HeapObject::ObjectHeader::at((void*)_base)->object(); +} + +bool GCSpace::includes_(HeapObject *object) +{ + return _base < (uintptr_t)object && (uintptr_t)object < _next; +} + +HeapObject *GCSpace::shallowCopy_(HeapObject *object) +{ + auto size = object->bodySizeInBytes(); + auto extra = object->headerSizeInBytes(); + auto allocation = this->allocateIfPossible_(extra + size); + if (!allocation) + return nullptr; + + auto copy = (HeapObject*)(allocation + extra); + copy->copyFrom_headerSize_bodySize_(object, (uintptr_t)extra, (uintptr_t)size); + copy->beNotRemembered(); + return copy; + +} + +HeapObject *GCSpace::shallowCopyCommitting_(HeapObject *object) +{ + auto size = object->bodySizeInBytes(); + auto extra = object->headerSizeInBytes(); + auto allocation = this->allocateCommittingIfNeeded_(extra + size); + if (!allocation) + return nullptr; + + auto copy = (HeapObject*)(allocation + extra); + copy->copyFrom_headerSize_bodySize_(object, extra, size); + copy->beNotRemembered(); + return copy; +} + +void GCSpace::unmarkAll() { + this->objectsDo_( + [](HeapObject *object) { object->beUnseen();} + ); +} + +void GCSpace::objectsDo_(const std::function &aBlock) +{ + auto header = HeapObject::ObjectHeader::at((void*)_base); + while ((uintptr_t)header < _next) + { + auto object = header->object(); + header = object->nextHeader(); + aBlock(object); + } +} diff --git a/runtime/cpp/Allocator/GCSpace.h b/runtime/cpp/Allocator/GCSpace.h index 83037032..66305ed5 100644 --- a/runtime/cpp/Allocator/GCSpace.h +++ b/runtime/cpp/Allocator/GCSpace.h @@ -1,26 +1,60 @@ #ifndef _GCSPACE_H_ #define _GCSPACE_H_ -#include "HeapObject.h" -#include "Memory.h" +#include +#include +#include namespace Egg { +class HeapObject; + class GCSpace { + GCSpace() {}; public: uintptr_t _base, _next, _softLimit, _committedLimit, _reservedLimit; - GCSpace(int size = 4096 * 8); + std::string _name; + GCSpace(int size); + GCSpace(GCSpace *copy); + + static GCSpace* allocatedAt_limit_(uintptr_t base, uintptr_t limit, bool empty = true); + static GCSpace* allocatedAt_size_(uintptr_t base, uintptr_t size, bool empty = true); + ~GCSpace(); + uintptr_t base() { return _base; } + uintptr_t next() { return _next; } + uintptr_t nextFree() { return _next; } + uintptr_t softLimit() { return _softLimit; } + uintptr_t commitedLimit() { return _committedLimit; } + uintptr_t reservedLimit() { return _reservedLimit; } + std::string& name() { return _name; } + + uintptr_t committedSize(); uintptr_t reservedSize(); - void commitMemory(uint32_t delta); - uintptr_t allocate(uint32_t size); + uintptr_t usedSize(); // the amount actually used in the committed chunk + uintptr_t isFree() { return committedSize() == 0; } + + uintptr_t softAvailable(); + + bool commitMemoryUpTo_(uintptr_t address); + bool commitMemory_(uint32_t delta); + uintptr_t allocateIfPossible_(uint32_t size); + uintptr_t allocateCommittingIfNeeded_(uint32_t size); bool increaseSoftLimit_(uint32_t delta); -}; + HeapObject* firstObject(); + + bool includes_(HeapObject *object); + + HeapObject* shallowCopy_(HeapObject *object); + HeapObject* shallowCopyCommitting_(HeapObject *object); + + void unmarkAll(); + void f(); + + void objectsDo_(const std::function& aBlock); -class GCSpaceVector { - }; } // namespace Egg diff --git a/runtime/cpp/Allocator/GarbageCollector.cpp b/runtime/cpp/Allocator/GarbageCollector.cpp new file mode 100644 index 00000000..19834622 --- /dev/null +++ b/runtime/cpp/Allocator/GarbageCollector.cpp @@ -0,0 +1,294 @@ +#include "GarbageCollector.h" +#include "KnownObjects.h" +#include "Evaluator/Runtime.h" + +#include +#include + +using namespace Egg; + +GarbageCollector::GarbageCollector(Runtime *runtime, AllocationZone *oldZone, GCHeap *heap) : + _runtime(runtime), + _oldZone(oldZone), + _heap(heap) +{ +} + +bool GarbageCollector::checkEphemerons() +{ + auto done = true; + while (!_uncheckedEphemerons->empty()) + { + HeapObject *ephemeron = _uncheckedEphemerons->back(); + _uncheckedEphemerons->pop_back(); + if (this->checkReachablePropertyOf_(ephemeron)) + { + this->followEphemeronWeaks_(ephemeron); + done = false; + } + else + _unreachedEphemerons->push_back(ephemeron); + } + return done; +} + +void GarbageCollector::collect() +{ + this->initializeLocals(); + this->doCollect(); + this->releaseLocals(); +} + +void GarbageCollector::doCollect() +{ + this->followRoots(); + this->strengthenAndMigrateEphemerons(); + this->scanWeakContainers(); +} + +void GarbageCollector::followEphemerons() +{ + bool done; + do { + this->followLiveEphemerons(); + done = this->rescueUnreachableEphemerons(); + } while (!done); +} + +void GarbageCollector::followEphemeronWeaks_(HeapObject *ephemeron) +{ + this->scan_from_to_(ephemeron, 1, ephemeron->size()); + this->followClosure(); +} + +void GarbageCollector::followLiveEphemerons() +{ + bool done; + do { + done = this->checkEphemerons(); + if (done) + break; + this->swapUncheckedWithUnreached(); + } while (true); +} + +void GarbageCollector::followRoots() +{ + //this->scanStack_(_activeStack); + this->scanCurrentContext(); + //this->scanThreadLocalStorages(); + this->followClosure(); + this->followEphemerons(); +} + +void GarbageCollector::initializeLocals() +{ + auto size = this->initialContainerCapacity(); + + _weakContainers = new std::vector(); + _uncheckedEphemerons = new std::vector(); + _unreachedEphemerons = new std::vector(); + _rescuedEphemerons = new std::vector(); + _stacks = new std::vector(); + + _weakContainers->reserve(size); + _uncheckedEphemerons->reserve(size); + _unreachedEphemerons->reserve(size); + _rescuedEphemerons->reserve(size); + _stacks->reserve(size); +} + +void GarbageCollector::heap_(GCHeap *aGCHeap) +{ + _heap = aGCHeap; + this->postInitialize(); +} + +void GarbageCollector::postInitialize() +{ + _oldZone = _heap->oldZone(); + +} + +void Egg::GarbageCollector::releaseLocals() +{ + //warning("queueEphemerons must be implemented"); + //_memory->queueEphemerons_(_rescuedEphemerons); + + delete _weakContainers; + delete _uncheckedEphemerons; + delete _unreachedEphemerons; + delete _rescuedEphemerons; + delete _stacks; + + _weakContainers = nullptr; + _uncheckedEphemerons = nullptr; + _unreachedEphemerons = nullptr; + _rescuedEphemerons = nullptr; + _stacks = nullptr; + //ActiveProcess stack unlock +} + +void GarbageCollector::rememberSpecial_(HeapObject *anObject) +{ + auto klass = _runtime->speciesOf_((Object*)anObject); + + std::vector *collection; + if (klass == _runtime->_ephemeronClass) + { + this->checkEphemeron_(anObject); + collection = _uncheckedEphemerons; + } + else + { + collection = klass == _runtime->_processStackClass ? _stacks : _weakContainers; + } + collection->push_back(anObject); +} + +void GarbageCollector::rescueEphemeron_(HeapObject *ephemeron) +{ + this->followEphemeronWeaks_(ephemeron); + _rescuedEphemerons->push_back(ephemeron); +} + +bool GarbageCollector::rescueUnreachableEphemerons() +{ + auto done = _unreachedEphemerons->empty(); + for (auto ephemeron : *_unreachedEphemerons) + { + this->rescueEphemeron_(ephemeron); + } + _unreachedEphemerons->clear(); + return done; +} + +void GarbageCollector::scanNativeStackFrame_sized_(uintptr_t *framePointer, uintptr_t size) +{ + // all code is pinned for now + //self fixReturnAddressIn: framePointer _asObject sized: size. + + this->scanStackFrameObjects_sized_(framePointer, size); +} + +void GarbageCollector::scanStackFrameObjects_sized_(uintptr_t *framePointer, uintptr_t size) { + //for (uintptr_t i = 0; i < size; i++) + // printf("adding %s to queue\n", ((Object*)framePointer[i])->printString().c_str()); + this->scan_from_to_((HeapObject*)framePointer, 1, size); +} + +void GarbageCollector::scanSpecialSlots_(HeapObject *special) +{ + this->scan_from_to_(special, 1, special->size()); +} + +void GarbageCollector::nativeFramesStartingAt_bp_do_(uintptr_t **stack, uintptr_t sp, uintptr_t bp, std::function block) { + auto start = sp; + auto end = bp; + while (end != 0) { + auto size = end - start; + block((uintptr_t*)&stack[start-1], size); + start = end + 2; // next frame stars after bp and retaddr slots + end = (uintptr_t)stack[end-1]; + } +} + +void GarbageCollector::scanFirstStackChunk_(HeapObject *aProcessVMStack) { + /** + * Scanning needs to fetch a chain of stack (frame) pointers. The head + * of the chain is either of two cases: + * - The active process. + * In that case, the top of the stack is a common frame (probably a call + * to a primitive). No special action needs to be done. + * - A sleeping (native) process. + * In that case, the top of the stack is that process' env, followed + * by a retaddr. The GC has to scan that addr and then continue normally. + * (TODO: make env an instvar of the ProcessVMStack) + */ + //if (aProcessVMStack != runtime->_activeProcessStack) + // this->scanTopSlot_(aProcessVMStack); + + auto firstSP = _runtime->processVMStackSP_(aProcessVMStack) + 2; + auto firstBP = _runtime->processVMStackBP_(aProcessVMStack); + auto stack = (uintptr_t**)nullptr; //_runtime->processVMStackContext_(aProcessVMStack)->stack(); + this->nativeFramesStartingAt_bp_do_(stack, firstSP, firstBP, + [this](uintptr_t *frame, uintptr_t size) { + this->scanNativeStackFrame_sized_(frame, size); + }); +} + +/* only for use until we have context switches */ +void GarbageCollector::scanCurrentContext() { + auto firstSP = _runtime->_evaluator->context()->stackPointer(); + auto firstBP = _runtime->_evaluator->context()->framePointer(); + auto stack = (uintptr_t**)_runtime->_evaluator->context()->stack(); + this->nativeFramesStartingAt_bp_do_(stack, firstSP, firstBP, + [this](uintptr_t *frame, uintptr_t size) { + this->scanNativeStackFrame_sized_(frame, size); + }); +} + +void GarbageCollector::scanStack_(HeapObject *aProcessVMStack) +{ + //auto context = _runtime->processVMStackContext_(aProcessVMStack); + + // skip this stack if it corresponds to active process, which has already been scanned + //if (context == _runtime->_evaluator->context()) + return; + + auto process = _runtime->processVMStackProcess_(aProcessVMStack); + if (_runtime->processStackIsValid_(process)) + this->scanFirstStackChunk_(aProcessVMStack); + + /* unimplemented GC in callbacks + this->stackFramesBeneathCallbackIn_Do_(aProcessVMStack, + [this](uintptr_t frame, uintptr_t nativeSize) { + this->scanNativeStackFrame_sized_(frame, nativeSize); + }); + */ +} + +void GarbageCollector::scanStacks() +{ + while (!_stacks->empty()) + { + auto s = _stacks->back(); + _stacks->pop_back(); + this->scanSpecialSlots_(s); + this->scanStack_(s); + } +} + +void GarbageCollector::scanWeakContainers() +{ + for (auto weakContainer : *_weakContainers) + { + this->updateWeakReferencesOf_(weakContainer); + } + _weakContainers->clear(); +} + +void GarbageCollector::strengthenAndMigrateEphemerons() +{ + // No need to move ephemerons anywhere now, they will be added to the + // list of unreachable on GC release. That list is iterated after GC + // to finalize each of them. + + for (auto ephemeron : *_rescuedEphemerons) + { + ephemeron->beNotSpecial(); + } +} + +void GarbageCollector::swapUncheckedWithUnreached() +{ + auto aux = this->_uncheckedEphemerons; + this->_uncheckedEphemerons = this->_unreachedEphemerons; + this->_unreachedEphemerons = aux; +} + +void GarbageCollector::updateWeakReferencesOf_(HeapObject *weakContainer) +{ + for (uint32_t i = 0; i < weakContainer->size(); i++) + this->updateWeak_at_(weakContainer, i); +} diff --git a/runtime/cpp/Allocator/GarbageCollector.h b/runtime/cpp/Allocator/GarbageCollector.h new file mode 100644 index 00000000..747b7d43 --- /dev/null +++ b/runtime/cpp/Allocator/GarbageCollector.h @@ -0,0 +1,74 @@ +#ifndef _GARBAGECOLLECTOR_H_ +#define _GARBAGECOLLECTOR_H_ + +#include "GCSpace.h" +#include "GCHeap.h" +#include "Memory.h" +#include + +namespace Egg { + +class Runtime; +class AllocationZone; + +class GarbageCollector { +protected: + Runtime *_runtime; + AllocationZone *_oldZone; + + std::vector *_weakContainers; + std::vector *_uncheckedEphemerons, *_unreachedEphemerons, *_rescuedEphemerons; + HeapObject *_tombstone; + + GCHeap *_heap; + std::vector *_stacks; + uintptr_t _currentSP; + + const int LargeThreshold = 64 * KB; + +public: + GarbageCollector(Runtime *runtime, AllocationZone *oldZone, GCHeap *heap); + + virtual ~GarbageCollector() = default; + + bool checkEphemerons(); + virtual void checkEphemeron_(HeapObject *ephemeron) {} + virtual bool checkReachablePropertyOf_(HeapObject *ephemeron) = 0; + + void collect(); + virtual void doCollect(); + virtual void followClosure() = 0; + void followEphemerons(); + void followEphemeronWeaks_(HeapObject *ephemeron); + void followLiveEphemerons(); + void followRoots(); + virtual uintptr_t initialContainerCapacity() = 0; + virtual void initializeLocals(); + void heap_(GCHeap *aGCHeap); + virtual void postInitialize(); + virtual void releaseLocals(); + void rememberSpecial_(HeapObject *anObject); + void rescueEphemeron_(HeapObject *ephemeron); + bool rescueUnreachableEphemerons(); + virtual void scan_from_to_(HeapObject *current, uintptr_t start, uintptr_t limit) = 0; + void scanNativeStackFrame_sized_(uintptr_t *framePointer, uintptr_t size); + void scanStackFrameObjects_sized_(uintptr_t *framePointer, uintptr_t size); + void scanSpecialSlots_(HeapObject *special); + + void nativeFramesStartingAt_bp_do_(uintptr_t **stack, uintptr_t sp, uintptr_t bp, std::function block); + void scanFirstStackChunk_(HeapObject * aProcessVMStack); + void scanCurrentContext(); + void scanStack_(HeapObject *aProcessVMStack); + void scanStacks(); + virtual void scanThreadLocalStorage_(HeapObject *tread) = 0; + void scanWeakContainers(); + void strengthenAndMigrateEphemerons(); + void swapUncheckedWithUnreached(); + virtual void updateWeak_at_(HeapObject *weakContainer, uintptr_t index) = 0; + void updateWeakReferencesOf_(HeapObject *weakContainer); + +}; + +} // namespace Egg + +#endif // ~ _GARBAGECOLLECTOR_H_ ~ diff --git a/runtime/cpp/Allocator/Memory.h b/runtime/cpp/Allocator/Memory.h index 52d0cd9d..4f083aa1 100644 --- a/runtime/cpp/Allocator/Memory.h +++ b/runtime/cpp/Allocator/Memory.h @@ -8,6 +8,7 @@ #include #include "Util.h" +#include "Egg.h" namespace Egg { @@ -15,6 +16,7 @@ namespace Egg void InitializeMemory(); uintptr_t ReserveMemory(uintptr_t base, uintptr_t size); void CommitMemory(uintptr_t base, uintptr_t size); +void DecommitMemory(uintptr_t base, uintptr_t size); void FreeMemory(uintptr_t base, uintptr_t size); diff --git a/runtime/cpp/Evaluator/PlatformCode.cpp b/runtime/cpp/Evaluator/PlatformCode.cpp new file mode 100644 index 00000000..6f3acb89 --- /dev/null +++ b/runtime/cpp/Evaluator/PlatformCode.cpp @@ -0,0 +1,21 @@ + +#include "PlatformCode.h" + +Egg::PlatformCode* Egg::newPlatformCode() +{ + void* rawmem = static_cast(std::aligned_alloc(alignof(void*), sizeof(PlatformCode))); + if (!rawmem) { + throw std::bad_alloc(); + } + + // Construct the object in the allocated memory + return new (rawmem) PlatformCode(); +} + +void Egg::deletePlatformCode(Egg::PlatformCode* platformCode) +{ + platformCode->~PlatformCode(); + + // Free the raw memory + std::free(platformCode); +} \ No newline at end of file diff --git a/runtime/cpp/Evaluator/PlatformCode.h b/runtime/cpp/Evaluator/PlatformCode.h new file mode 100644 index 00000000..6d0a9e48 --- /dev/null +++ b/runtime/cpp/Evaluator/PlatformCode.h @@ -0,0 +1,21 @@ +#ifndef _PLATFORM_CODE_H_ +#define _PLATFORM_CODE_H_ + +#include +#include + +/** +* A simple typedef for wrapping a buffer that can be used as a heap object, +* by encoding it as a tagged small integer. +*/ + +namespace Egg { + class SExpression; + + typedef std::vector PlatformCode; + PlatformCode *newPlatformCode(); + void deletePlatformCode(PlatformCode *platformCode); + +} // namespace Egg + +#endif // ~ _PLATFORM_CODE ~ diff --git a/runtime/cpp/GCedRef.cpp b/runtime/cpp/GCedRef.cpp new file mode 100644 index 00000000..d622c5c5 --- /dev/null +++ b/runtime/cpp/GCedRef.cpp @@ -0,0 +1,43 @@ + +#include "GCedRef.h" +#include "Evaluator/Runtime.h" +#include "HeapObject.h" + +using namespace Egg; + +class Runtime; + +GCedRef::GCedRef(HeapObject* object, uintptr_t index) + : _object(object), _index(index) +{} + +GCedRef::GCedRef(HeapObject* object) + : _object(object), _index(debugRuntime->assignGCedRefIndex()) +{ + debugRuntime->registerGCedRef_(this); +} +//GCedRef::GCedRef(GCedRef &other) +// : _runtime(other._runtime), _object(other.get()), index(_runtime->assignRef(other.get())) +//{} + +GCedRef::~GCedRef() { + debugRuntime->releaseGCedRef_(_index); +} + +uintptr_t GCedRef::Comparator::hash(const GCedRef *obj) const { + return debugRuntime->hashFor_((Object*)obj->get()); +} + +uintptr_t GCedRef::Comparator::hash(const HeapObject *obj) const { + return debugRuntime->hashFor_((Object*)obj); +} + +HeapObject* GCedRef::get() +{ + return _object; +} + +uintptr_t GCedRef::index() { + return _index; +} + diff --git a/runtime/cpp/GCedRef.h b/runtime/cpp/GCedRef.h new file mode 100644 index 00000000..3e0c0278 --- /dev/null +++ b/runtime/cpp/GCedRef.h @@ -0,0 +1,88 @@ + +#ifndef _GCEDREF_H_ +#define _GCEDREF_H_ + +#include "Egg.h" + +namespace Egg { + +class Runtime; + +class GCedRef +{ +public: + /* Create a new NULL reference. */ + GCedRef(HeapObject *object, uintptr_t index); + GCedRef(HeapObject *object); + + /* Create a new reference from another reference */ + GCedRef(GCedRef &other); + + ~GCedRef(); + + HeapObject *get(); + const HeapObject* get() const { return _object; }; + uintptr_t index(); + + HeapObject **getRaw() { return &_object; } + + // Comparator for GCedRef* that allows comparisons with HeapObject* + struct Comparator { + using is_transparent = void; + uintptr_t hash(const GCedRef *obj) const; + uintptr_t hash(const HeapObject *obj) const; + + bool operator()(const GCedRef* lhs, const GCedRef* rhs) const { + return hash(lhs) <= hash(rhs); + } + + bool operator()(const GCedRef* lhs, const HeapObject* rhs) const { + return hash(lhs) <= hash(rhs); + } + + bool operator()(const HeapObject* lhs, const GCedRef* rhs) const { + return hash(lhs) <= hash(rhs); + } + + // comparisons for pairs of GCedRef* + bool operator()(const std::pair& lhs, const std::pair& rhs) const { + auto lhs1 = hash(lhs.first), lhs2 = hash(lhs.second); + auto rhs1 = hash(rhs.first), rhs2 = hash(rhs.second); + return std::tie(lhs1, lhs2) <= std::tie(rhs1, rhs2); + } + + // Compare two std::pair + bool operator()(const std::pair& lhs, const std::pair& rhs) const { + auto lhs1 = hash(lhs.first), lhs2 = hash(lhs.second); + auto rhs1 = hash(rhs.first), rhs2 = hash(rhs.second); + return std::tie(lhs1, lhs2) <= std::tie(rhs1, rhs2); + } + + // Compare pairs of HeapObject with pairs of GCedRef + bool operator()(const std::pair& lhs, + const std::pair& rhs) const { + auto lhs1 = hash(lhs.first), lhs2 = hash(lhs.second); + auto rhs1 = hash(rhs.first), rhs2 = hash(rhs.second); + return std::tie(lhs1, lhs2) <= std::tie(rhs1, rhs2); + } + + bool operator()(const std::pair& lhs, + const std::pair& rhs) const { + auto lhs1 = hash(lhs.first), lhs2 = hash(lhs.second); + auto rhs1 = hash(rhs.first), rhs2 = hash(rhs.second); + return std::tie(lhs1, lhs2) <= std::tie(rhs1, rhs2); + } + }; + +private: + GCedRef(const GCedRef &other) = delete; // not allowed, to prevent aliasing + GCedRef& operator=(const GCedRef &other) = delete; + + HeapObject *_object; + uintptr_t _index; + //static Runtime *_runtime; +}; + +} + +#endif // ~ _GCEDREF_H_ ~ diff --git a/runtime/cpp/StackGCedRef.cpp b/runtime/cpp/StackGCedRef.cpp new file mode 100644 index 00000000..24450ad9 --- /dev/null +++ b/runtime/cpp/StackGCedRef.cpp @@ -0,0 +1,27 @@ + +#include "StackGCedRef.h" +#include "Evaluator/EvaluationContext.h" + +using namespace Egg; +StackGCedRef::StackGCedRef(EvaluationContext* context, Object* object) : + _context(context) +{ + _context->push_(object); + _index = _context->stackPointer(); +} + +StackGCedRef::~StackGCedRef() +{ + ASSERT(_context->stackPointer() == _index); + _context->pop(); +} + +Object* StackGCedRef::asObject() const +{ + return _context->stackAt_(_index); +} + +HeapObject* StackGCedRef::asHeapObject() +{ + return asObject()->asHeapObject(); +} \ No newline at end of file diff --git a/runtime/cpp/StackGCedRef.h b/runtime/cpp/StackGCedRef.h new file mode 100644 index 00000000..d24c3dec --- /dev/null +++ b/runtime/cpp/StackGCedRef.h @@ -0,0 +1,25 @@ + +#ifndef _STACKGCEDREF_H_ +#define _STACKGCEDREF_H_ + +#include "Egg.h" + +namespace Egg { + +class EvaluationContext; + +class StackGCedRef { + EvaluationContext* _context; + uintptr_t _index; + + public: + StackGCedRef(EvaluationContext* context, Object* object); + ~StackGCedRef(); + + Object* asObject() const; + HeapObject* asHeapObject(); +}; + +} + +#endif // ~ _STACKGCEDREF_H_ ~ From a151882d10551367e4b0bf39bc8fa074929b1eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:08:49 -0300 Subject: [PATCH 06/26] [cppvm] add a couple globally available debug methods --- runtime/cpp/Egg.cpp | 7 +++++++ runtime/cpp/Egg.h | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/runtime/cpp/Egg.cpp b/runtime/cpp/Egg.cpp index 51b189ec..9018c4c4 100644 --- a/runtime/cpp/Egg.cpp +++ b/runtime/cpp/Egg.cpp @@ -20,6 +20,13 @@ void Egg::error_(const std::string &message) { error(message.c_str()); } +void Egg::warning(const char *message) { + std::cerr << "WARNING: " << message << std::endl; +} + +void Egg::warning_(const std::string &message) { + warning(message.c_str()); +} void Egg::Initialize() { InitializeMemory(); diff --git a/runtime/cpp/Egg.h b/runtime/cpp/Egg.h index 815c05c3..6ce534e5 100644 --- a/runtime/cpp/Egg.h +++ b/runtime/cpp/Egg.h @@ -21,9 +21,9 @@ void Initialize(); void osError(); void error(const char *message); void error_(const std::string &message); -void debug(const char *message); - extern bool GC_CRITICAL; +void warning(const char *message); +void warning_(const std::string &message); } From 08b6dceda6eb8ac33de721c55ad3392bf5fcc83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:10:23 -0300 Subject: [PATCH 07/26] [cppvm] remove GC_CRITICAL --- runtime/cpp/Egg.cpp | 2 -- runtime/cpp/Egg.h | 1 - 2 files changed, 3 deletions(-) diff --git a/runtime/cpp/Egg.cpp b/runtime/cpp/Egg.cpp index 9018c4c4..548a6229 100644 --- a/runtime/cpp/Egg.cpp +++ b/runtime/cpp/Egg.cpp @@ -7,8 +7,6 @@ #include #include -bool Egg::GC_CRITICAL = false; - void Egg::error(const char *message) { if (debugRuntime) std::cerr << debugRuntime->_evaluator->context()->backtrace() << std::endl << std::endl; diff --git a/runtime/cpp/Egg.h b/runtime/cpp/Egg.h index 6ce534e5..e17a6e28 100644 --- a/runtime/cpp/Egg.h +++ b/runtime/cpp/Egg.h @@ -21,7 +21,6 @@ void Initialize(); void osError(); void error(const char *message); void error_(const std::string &message); -extern bool GC_CRITICAL; void warning(const char *message); void warning_(const std::string &message); From 26a410098189c3d12926f25bdb2d9fdf7e77e751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:11:36 -0300 Subject: [PATCH 08/26] [cppvm] make Egg.h include basic int types, convert WORD_SIZE to a constexpr instead of define --- runtime/cpp/Egg.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/runtime/cpp/Egg.h b/runtime/cpp/Egg.h index e17a6e28..43bfeecd 100644 --- a/runtime/cpp/Egg.h +++ b/runtime/cpp/Egg.h @@ -2,17 +2,21 @@ #define _EGG_H_ #include +#include #include typedef unsigned long ulong; typedef unsigned short ushort; typedef unsigned char uchar; -#define WORD_SIZE 8 -#define WORD_SIZE_SHIFT 3 - namespace Egg { +constexpr uintptr_t WORD_SIZE = sizeof(uintptr_t); +constexpr uintptr_t WORD_SIZE_SHIFT = WORD_SIZE == 4 ? 2 : 3; + +constexpr uintptr_t KB = 1024; +constexpr uintptr_t MB = 1024 * 1024; + struct Object; struct HeapObject; struct SmallInteger; From 0de1dba4d26b7a0a01bc30bd31649b73228fa6a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:13:01 -0300 Subject: [PATCH 09/26] [cppvm] after loading an image-segment, relocate the pointer to the module object --- runtime/cpp/ImageSegment.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runtime/cpp/ImageSegment.cpp b/runtime/cpp/ImageSegment.cpp index c6093a2c..e86c8348 100644 --- a/runtime/cpp/ImageSegment.cpp +++ b/runtime/cpp/ImageSegment.cpp @@ -87,6 +87,8 @@ void ImageSegment::fixPointerSlots(const std::vector& imports) } current = current->nextObject(); } + + header.module = relocatedAddress_(header.module); } uintptr_t ImageSegment::spaceStart() From a4980867560dc8017217b930e32f28c6519804b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:17:11 -0300 Subject: [PATCH 10/26] [cppvm:gc] make sexpressions store GCedRefs instead of raw pointers where needed --- runtime/cpp/Evaluator/SAbstractMessage.h | 30 +++++++++---------- .../cpp/Evaluator/SExpressionLinearizer.cpp | 8 ++--- runtime/cpp/Evaluator/SExpressionLinearizer.h | 7 +++-- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/runtime/cpp/Evaluator/SAbstractMessage.h b/runtime/cpp/Evaluator/SAbstractMessage.h index 5604e1a5..c48d1fa4 100644 --- a/runtime/cpp/Evaluator/SAbstractMessage.h +++ b/runtime/cpp/Evaluator/SAbstractMessage.h @@ -5,13 +5,14 @@ #include "SExpression.h" #include "SExpressionVisitor.h" #include "../HeapObject.h" +#include "GCedRef.h" namespace Egg { class SAbstractMessage : public SExpression { - HeapObject* _selector; + GCedRef _selector; std::vector _arguments; - std::vector _cache; + std::vector _cache; public: using UndermessagePointer = Object* (Evaluator::*)(Object *, std::vector &args); @@ -19,7 +20,8 @@ class SAbstractMessage : public SExpression { // this is a hack to make C++ type system stop crying struct UndermessageWrapper { public: - UndermessageWrapper(UndermessagePointer ptr) : _undermessage(ptr) {} + UndermessageWrapper(UndermessagePointer ptr) : + _undermessage(ptr) {} UndermessagePointer _undermessage; }; @@ -42,12 +44,12 @@ class SAbstractMessage : public SExpression { } void cache_when_(HeapObject* anSCompiledMethod, HeapObject* type) { - _cache.push_back(type); - _cache.push_back(anSCompiledMethod); + _cache.push_back(new GCedRef(type)); + _cache.push_back(new GCedRef(anSCompiledMethod)); } void cacheUndermessage_(UndermessagePointer anUndermessage) { - _cache.push_back(reinterpret_cast(new UndermessageWrapper(anUndermessage))); + _cache.push_back(reinterpret_cast(new UndermessageWrapper(anUndermessage))); } UndermessagePointer cachedUndermessage() const { @@ -66,9 +68,9 @@ class SAbstractMessage : public SExpression { HeapObject* methodFor_(HeapObject *behavior) const { for (size_t i = 0; i < _cache.size(); i += 2) { - HeapObject *cached = _cache[i]; - if (cached == behavior) { - return _cache[i + 1]; + GCedRef *cached = _cache[i]; + if (cached->get() == behavior) { + return _cache[i + 1]->get(); } } @@ -79,16 +81,12 @@ class SAbstractMessage : public SExpression { void registerCacheWith_(Runtime* runtime) { if (_cache.empty()) { - runtime->registerCache_for_(this, _selector); + runtime->registerCache_for_(this, _selector.get()); } } - HeapObject* selector() const { - return _selector; - } - - void selector_(HeapObject* aSymbol) { - _selector = aSymbol; + HeapObject* selector() { + return _selector.get(); } }; diff --git a/runtime/cpp/Evaluator/SExpressionLinearizer.cpp b/runtime/cpp/Evaluator/SExpressionLinearizer.cpp index ced4d719..3ce888b6 100644 --- a/runtime/cpp/Evaluator/SExpressionLinearizer.cpp +++ b/runtime/cpp/Evaluator/SExpressionLinearizer.cpp @@ -364,7 +364,7 @@ void SExpressionLinearizer::pushR() { } void SExpressionLinearizer::reset() { - this->_operations = new std::vector; + this->_operations = newPlatformCode(); this->_inBlock = false; } @@ -436,7 +436,7 @@ void SExpressionLinearizer::visitBlock(SBlock *anSBlock) { auto prevStackTop = _stackTop; this->_stackTop = _runtime->blockTempCount_(anSBlock->compiledCode()); this->_inBlock = true; - this->_operations = new std::vector; + this->_operations = newPlatformCode(); auto statements = anSBlock->statements(); for (auto node : statements) { node->acceptVisitor_(this); @@ -451,8 +451,8 @@ void SExpressionLinearizer::visitBlock(SBlock *anSBlock) { if (!anSBlock->isInlined()) { - auto code = _runtime->newExecutableCodeFor_with_(anSBlock->_compiledCode, reinterpret_cast(this->_operations)); - _runtime->blockExecutableCode_put_(anSBlock->compiledCode(), (Object*)code); + auto code = _runtime->newExecutableCodeFor_with_(anSBlock->_compiledCode, this->_operations); + _runtime->blockExecutableCode_put_(anSBlock->compiledCode(), code); } this->_stackTop = prevStackTop; diff --git a/runtime/cpp/Evaluator/SExpressionLinearizer.h b/runtime/cpp/Evaluator/SExpressionLinearizer.h index 52d249e7..7fd4b091 100644 --- a/runtime/cpp/Evaluator/SExpressionLinearizer.h +++ b/runtime/cpp/Evaluator/SExpressionLinearizer.h @@ -5,10 +5,11 @@ #include #include "../HeapObject.h" #include "SExpressionVisitor.h" +#include "Runtime.h" +#include "PlatformCode.h" namespace Egg { -class Runtime; class Evaluator; class SExpressionLinearizer : public SExpressionVisitor { @@ -17,7 +18,7 @@ class SExpressionLinearizer : public SExpressionVisitor { bool _inBlock; bool _dropsArguments; size_t _stackTop; - std::vector *_operations; + PlatformCode *_operations; using PrimitivePointer = Object* (Evaluator::*)(); std::map _primitives; @@ -58,7 +59,7 @@ class SExpressionLinearizer : public SExpressionVisitor { this->_orNot = nullptr; } - std::vector* operations() { + PlatformCode* operations() { return this->_operations; } From 19c3869721a2d3051df67a163bfdc8f1c5a5d30c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:18:06 -0300 Subject: [PATCH 11/26] [cppvm:gc] add an object copying method for compactor --- runtime/cpp/HeapObject.cpp | 9 +++++++++ runtime/cpp/HeapObject.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/runtime/cpp/HeapObject.cpp b/runtime/cpp/HeapObject.cpp index c2b2d53b..31eec620 100644 --- a/runtime/cpp/HeapObject.cpp +++ b/runtime/cpp/HeapObject.cpp @@ -358,3 +358,12 @@ std::string Egg::HeapObject::printString() { return debugRuntime->print_(this); } + +void HeapObject::copyFrom_headerSize_bodySize_(HeapObject *object, uintptr_t headerSize, uintptr_t bodySize) +{ + auto srcbase = ((uintptr_t)object) - headerSize; + auto srcend = ((uintptr_t)object) + bodySize; + auto dstbase = ((uintptr_t)this) - headerSize; + + std::copy((uintptr_t*)srcbase, (uintptr_t*)srcend, (uintptr_t*)dstbase); +} diff --git a/runtime/cpp/HeapObject.h b/runtime/cpp/HeapObject.h index 60a404f7..e5d2a877 100644 --- a/runtime/cpp/HeapObject.h +++ b/runtime/cpp/HeapObject.h @@ -231,6 +231,9 @@ struct HeapObject uint64_t& uint64offset(const uint32_t subscript); /// Return a 64-bit uint of this object at 0-based `subscript` uint64_t& unsignedLargeAt_(uint32_t index) { return uint64offset((index - 1) * 8); }; // 1-based index for compatibility reasons + /// ~ copying ~ + void copyFrom_headerSize_bodySize_(HeapObject *object, uintptr_t headerSize, uintptr_t bodySize); + /// ~ object bytes ~ void replaceBytesFrom_to_with_startingAt_( const uintptr_t from, From 9820ccf9704cedc0877ffa51df72622dbcec14a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:20:57 -0300 Subject: [PATCH 12/26] [cppvm:gc] adapt Runtime class to the existance of a moving GC --- runtime/cpp/Evaluator/Runtime.cpp | 137 ++++++++++++++++++++++++++---- runtime/cpp/Evaluator/Runtime.h | 106 +++++++++++++++++------ 2 files changed, 201 insertions(+), 42 deletions(-) diff --git a/runtime/cpp/Evaluator/Runtime.cpp b/runtime/cpp/Evaluator/Runtime.cpp index 947ba3b2..2601b899 100644 --- a/runtime/cpp/Evaluator/Runtime.cpp +++ b/runtime/cpp/Evaluator/Runtime.cpp @@ -2,18 +2,33 @@ #include "Runtime.h" #include +#include +#include #include "Evaluator.h" #include "Allocator/GCHeap.h" #include "SAbstractMessage.h" #include "KnownConstants.h" +#include "GCedRef.h" +#include "StackGCedRef.h" using namespace Egg; Runtime *Egg::debugRuntime = nullptr; + +Runtime::Runtime(Bootstrapper* bootstrapper, ImageSegment* kernel): + _bootstrapper(bootstrapper), + _kernel(kernel), + _lastHash(0) +{ + this->initializeKernelObjects(); + KnownObjects::initializeFrom(this); + _heap = new GCHeap(this); + +} + void Runtime::initializeEvaluator() { - _heap = new GCHeap; _evaluator = new Evaluator(this, _falseObj, _trueObj, _nilObj); } @@ -30,9 +45,10 @@ uintptr_t Runtime::arrayedSizeOf_(Object *anObject) { HeapObject* Runtime::newBytes_size_(HeapObject *species, uint32_t size) { auto behavior = this->speciesInstanceBehavior_(species); + StackGCedRef gcedBehavior(_evaluator->context(), (Object*)behavior); auto result = _heap->allocateBytes_(size); - result->behavior(behavior); + result->behavior(gcedBehavior.asHeapObject()); return result; } @@ -40,8 +56,9 @@ HeapObject *Runtime::newSlots_size_(HeapObject *species, uint32_t size) { auto ivars = this->speciesInstanceSize_(species); HeapObject *behavior = this->speciesInstanceBehavior_(species); auto slotSize = ivars + size; + StackGCedRef gcedBehavior(_evaluator->context(), (Object*)behavior); HeapObject *result = _heap->allocateSlots_(slotSize); - result->behavior(behavior); + result->behavior(gcedBehavior.asHeapObject()); if (size > 0) result->beArrayed(); if (ivars > 0) @@ -100,13 +117,13 @@ HeapObject *Runtime::newClosureFor_(HeapObject *block) HeapObject *Runtime::newEnvironmentSized_(uint32_t size) { return this->newArraySized_(size); - } +} HeapObject *Runtime::newExecutableCodeFor_with_(HeapObject *compiledCode, - HeapObject *platformCode) { + PlatformCode *platformCode) { auto result = this->newSlots_size_(_arrayClass, 2); // fixme: use a proper kind of object for this - this->executableCodePlatformCode_put_(result, (Object *)platformCode); - this->executableCodeCompiledCode_put_(result, (Object *)compiledCode); + this->executableCodePlatformCode_put_(result, platformCode); + this->executableCodeCompiledCode_put_(result, compiledCode); return result; } @@ -125,6 +142,13 @@ HeapObject *Runtime::loadModule_(HeapObject *name) { return _bootstrapper->loadModule_(name->asLocalString()); } +void Runtime::addSegmentSpace_(ImageSegment* segment) +{ + GCSpace *space = GCSpace::allocatedAt_limit_(segment->spaceStart(), segment->spaceEnd(), false); + space->_name = this->moduleName_(segment->header.module)->asLocalString(); + this->_heap->addSpace_(space); +} + uintptr_t Runtime::hashFor_(Object *anObject) { if (anObject->isSmallInteger()) @@ -161,14 +185,53 @@ Object* Runtime::sendLocal_to_with_with_(const std::string &selector, Object *re return this->_evaluator->send_to_with_(symbol, receiver, args); } + +std::string Runtime::printGlobalCache() { + std::ostringstream stream; + for (const auto& entry : _globalCache) { + auto key_first = entry.first.first; + auto key_second = entry.first.second; + auto value = entry.second; + + stream << "Key: <" << key_first->get()->printString(); + stream << ", " << key_second->get()->printString(); + stream << "> -> Value: " << value->get()->printString(); + stream << std::endl; + } + + return stream.str(); +} + +void Runtime::checkCache() { + for (const auto& entry : _globalCache) { + auto symbol = entry.first.first->get(); + auto methodSelector = debugRuntime->methodSelector_(entry.second->get()); + ASSERT( symbol == methodSelector); + if (symbol != methodSelector) { + int a = 0; + } + } +} + HeapObject* Runtime::lookup_startingAt_(HeapObject *symbol, HeapObject *behavior) { + checkCache(); + + if (symbol->printString() == "#sizeInBytes") { + int a = 0; + } auto iter = _globalCache.find(global_cache_key(symbol,behavior)); - if (iter != _globalCache.end()) - return iter->second; + if (iter != _globalCache.end()) { + if (iter->second->get()->slotAt_(5)->printString() != symbol->printString()) + int b = 1; + return iter->second->get(); + } auto method = this->doLookup_startingAt_(symbol, behavior); - _globalCache[global_cache_key(symbol,behavior)] = method; + auto key = gced_global_cache_key(new GCedRef(symbol),new GCedRef(behavior)); + auto value = new GCedRef(method); + _globalCache.insert({key, value}); + checkCache(); return method; } @@ -243,15 +306,15 @@ void Runtime::flushDispatchCache_(HeapObject *aSymbol) { auto iter = _inlineCaches.find(aSymbol); if (iter != _inlineCaches.end()) { - auto messages = _inlineCaches[aSymbol]; + auto messages = iter->second; for (auto& m : *messages) { m->flushCache(); } } - std::vector cached; + std::vector cached; for (const auto& entry : _globalCache) { - if (entry.first.first == aSymbol) { + if (entry.first.first->get() == aSymbol) { cached.push_back(entry.first); } } @@ -268,13 +331,57 @@ void Runtime::flushDispatchCache_in_(HeapObject *aSymbol, HeapObject *klass) { auto iter = _inlineCaches.find(aSymbol); if (iter != _inlineCaches.end()) { - auto messages = _inlineCaches[aSymbol]; + auto messages = iter->second; for (auto& m : *messages) { m->flushCache(); } } - _globalCache.erase(std::make_pair(aSymbol, behavior)); + global_cache_key pair = std::make_pair(aSymbol, behavior); + auto globalIter = _globalCache.find(pair); + if (globalIter != _globalCache.end()) + _globalCache.erase(globalIter); +} + +uintptr_t Runtime::assignGCedRefIndex() { + if (_freeGCedRefs.empty()) + { + auto index = _gcedRefs.size(); + _gcedRefs.push_back(nullptr); + return index; + } + else + { + auto index = _freeGCedRefs.back(); + _freeGCedRefs.pop_back(); + return index; + } +} + +void Runtime::registerGCedRef_(GCedRef *gcedRef) { + _gcedRefs[gcedRef->index()] = gcedRef; +} + +GCedRef * Runtime::createGCedRef_(HeapObject *object) { + + auto index = this->assignGCedRefIndex(); + GCedRef *result = new GCedRef(object, index); + + return result; +} + +void Runtime::releaseGCedRef_(uintptr_t index) { + _freeGCedRefs.push_back(index); + _gcedRefs[index] = nullptr; +} + +void Runtime::gcedRefsDo_(const std::function &aBlock) +{ + for (auto ref : _gcedRefs) + { + if (ref != nullptr) + aBlock(ref); + } } std::string Egg::Runtime::print_(HeapObject *obj) { diff --git a/runtime/cpp/Evaluator/Runtime.h b/runtime/cpp/Evaluator/Runtime.h index 2586a843..87e75a26 100644 --- a/runtime/cpp/Evaluator/Runtime.h +++ b/runtime/cpp/Evaluator/Runtime.h @@ -1,6 +1,7 @@ -#ifndef _POWERTALKRUNTIME_H_ -#define _POWERTALKRUNTIME_H_ +#ifndef _RUNTIME_H_ +#define _RUNTIME_H_ +#include #include #include #include @@ -8,6 +9,8 @@ #include "../HeapObject.h" #include "../ImageSegment.h" #include "../KnownConstants.h" +#include "PlatformCode.h" +#include "GCedRef.h" namespace Egg { @@ -30,21 +33,16 @@ class Runtime { std::map _knownSymbols; //typedef std::vector inline_cache; - std::map * > _inlineCaches; + std::map *, GCedRef::Comparator > _inlineCaches; - typedef std::pair global_cache_key; - std::map _globalCache; + typedef std::pair gced_global_cache_key; + typedef std::pair global_cache_key; + + std::map _globalCache; uint16_t _lastHash; public: - Runtime(Bootstrapper *bootstrapper, ImageSegment *kernel) : - _bootstrapper(bootstrapper), - _kernel(kernel), - _lastHash(0) - { - this->initializeKernelObjects(); - KnownObjects::initializeFrom(this); - } + Runtime(Bootstrapper *bootstrapper, ImageSegment *kernel); std::string print_(HeapObject* obj); @@ -83,6 +81,7 @@ class Runtime { uintptr_t arrayedSizeOf_(Object *anObject); + HeapObject* newBytes_size_(HeapObject* species, uint32_t size); HeapObject* newSlots_size_(HeapObject *species, uint32_t size); HeapObject* newSlotsOf_(HeapObject* species); @@ -94,19 +93,25 @@ class Runtime { HeapObject* newClosureFor_(HeapObject *block); HeapObject* newCompiledMethod(); HeapObject* newEnvironmentSized_(uint32_t); - HeapObject* newExecutableCodeFor_with_(HeapObject *compiledCode, HeapObject *platformCode); + + HeapObject* newExecutableCodeFor_with_(HeapObject *compiledCode, PlatformCode *platformCode); HeapObject* newString_(const std::string &str); HeapObject* addSymbol_(const std::string &str); void addKnownSymbol_(const std::string &str, HeapObject *symbol) { _knownSymbols[str] = symbol; } HeapObject* loadModule_(HeapObject *name); + void addSegmentSpace_(ImageSegment *segment); uintptr_t hashFor_(Object *anObject); int16_t nextHash() { + auto prev = this->_lastHash; auto shifted = this->_lastHash >> 1; this->_lastHash = (this->_lastHash & 1) == 0 ? shifted : shifted ^ 0xB9C8; + if (this->_lastHash == 0) { + return prev; + } return this->_lastHash; } @@ -116,7 +121,7 @@ class Runtime { if (it == _inlineCaches.end()) { messages = new std::vector(); - _inlineCaches[symbol] = messages; + _inlineCaches.insert({new GCedRef(symbol), messages}); } else { messages = it->second; @@ -204,7 +209,7 @@ class Runtime { return methodExecutableCode_(block); } - void blockExecutableCode_put_(HeapObject* block, Object *anObject) { + void blockExecutableCode_put_(HeapObject* block, HeapObject *anObject) { return methodExecutableCode_put_(block, anObject); } @@ -242,23 +247,25 @@ class Runtime { } Object* executableCodePlatformCode_(HeapObject *code) { - return code->slot(Offsets::ExecutableCodePlatformCode); + return code->slot(Offsets::ExecutableCodePlatformCode); } - void executableCodePlatformCode_put_(HeapObject *code, Object *platformCode) { - code->slot(Offsets::ExecutableCodePlatformCode) = platformCode; + void executableCodePlatformCode_put_(HeapObject *code, PlatformCode *platformCode) { + auto smi = SmallInteger::smallpointerFrom((void*)platformCode); + code->slot(Offsets::ExecutableCodePlatformCode) = (Object*)smi; } HeapObject* executableCodeCompiledCode_(HeapObject *code) { return code->slot(Offsets::ExecutableCodeCompiledCode)->asHeapObject(); } - std::vector* executableCodeWork_(HeapObject *code) { - return (std::vector *)(code->slot(Offsets::ExecutableCodePlatformCode)); + PlatformCode* executableCodeWork_(HeapObject *code) { + auto slot = this->executableCodePlatformCode_(code); + return (PlatformCode*)slot->asSmallInteger()->asObject(); } - void executableCodeCompiledCode_put_(HeapObject *code, Object *compiledCode) { - code->slot(Offsets::ExecutableCodeCompiledCode) = compiledCode; + void executableCodeCompiledCode_put_(HeapObject *code, HeapObject *compiledCode) { + code->slot(Offsets::ExecutableCodeCompiledCode) = (Object*)compiledCode; } HeapObject* blockMethod_(HeapObject* block) { @@ -315,12 +322,17 @@ class Runtime { return ((this->methodFlags(method) >> 25) & 0x3F); } + int methodNeedsEnviornment_(HeapObject *method) { + return this->methodFlags(method) & MethodFlags::MethodNeedsEnvironment; + } + HeapObject* methodExecutableCode_(HeapObject *method) { + return method->slot(Offsets::CompiledCodeExecutableCode)->asHeapObject(); } - void methodExecutableCode_put_(HeapObject *method, Object *anObject) { - method->slot(Offsets::CompiledCodeExecutableCode) = anObject; + void methodExecutableCode_put_(HeapObject *method, HeapObject *executableCode) { + method->slot(Offsets::CompiledCodeExecutableCode) = (Object*)executableCode; } HeapObject* methodExtensionModule_(HeapObject *method) { @@ -365,6 +377,10 @@ class Runtime { return module->slot(Offsets::ModuleNamespace)->asHeapObject(); } + HeapObject* moduleName_(HeapObject *module) { + return module->slot(Offsets::ModuleName)->asHeapObject(); + } + HeapObject* classModule_(HeapObject *class_) { return class_->slot(Offsets::ClassModule)->asHeapObject(); } @@ -439,6 +455,26 @@ class Runtime { return this->speciesInstanceBehavior_(superclass); } + HeapObject* processStack_(HeapObject *process) { + return process->slot(Offsets::ProcessNativeStack)->asHeapObject(); + } + + bool processStackIsValid_(HeapObject *process) { + return process->slot(Offsets::ProcessTopContext) != (Object*)KnownObjects::nil; + } + + HeapObject* processVMStackProcess_(HeapObject * processVMStack) { + return processVMStack->slot(Offsets::ProcessVMStackProcess)->asHeapObject(); + } + + uintptr_t processVMStackSP_(HeapObject * processVMStack) { + return processVMStack->slot(Offsets::ProcessVMStackSP)->asSmallInteger()->asNative(); + } + + uintptr_t processVMStackBP_(HeapObject * processVMStack) { + return processVMStack->slot(Offsets::ProcessVMStackBP)->asSmallInteger()->asNative(); + } + void initializeKernelObjects() { this->_falseObj = _kernel->_exports["false"]; @@ -458,12 +494,13 @@ class Runtime { this->_closureClass = _kernel->_exports["Closure"]; this->_closureInstSize = this->speciesInstanceSize_(this->_closureClass); this->_behaviorClass = _kernel->_exports["Behavior"]; + this->_ephemeronClass = _kernel->_exports["Ephemeron"]; + this->_processStackClass = _kernel->_exports["ProcessVMStack"]; this->_symbolTable = _kernel->_exports["SymbolTable"]; this->_smallIntegerBehavior = this->speciesInstanceBehavior_(_smallIntegerClass); } - HeapObject *_falseObj; HeapObject *_trueObj; HeapObject *_nilObj; @@ -481,11 +518,26 @@ class Runtime { HeapObject *_closureClass; int _closureInstSize; HeapObject *_behaviorClass; + HeapObject *_ephemeronClass; + HeapObject *_processStackClass; HeapObject *_symbolTable; HeapObject *_smallIntegerBehavior; + + // API and state needed to alloc and free GCedRefs + + uintptr_t assignGCedRefIndex(); + void registerGCedRef_(GCedRef *gcedRef); + GCedRef* createGCedRef_(HeapObject * object); + void releaseGCedRef_(uintptr_t index); + void gcedRefsDo_(const std::function &aBlock); + + + std::vector _gcedRefs; + std::vector _freeGCedRefs; + }; } // namespace Egg -#endif // ~ _POWERTALKRUNTIME_H_ ~ +#endif // ~ _RUNTIME_H_ ~ From 59f114c781b936b3b6be3ee0e72a1ea62fdbdc01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:23:28 -0300 Subject: [PATCH 13/26] [cppvm:gc] Make evaluator aware of moving GC --- runtime/cpp/Evaluator/Evaluator.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/runtime/cpp/Evaluator/Evaluator.cpp b/runtime/cpp/Evaluator/Evaluator.cpp index 1f1377e4..2b3f9e80 100644 --- a/runtime/cpp/Evaluator/Evaluator.cpp +++ b/runtime/cpp/Evaluator/Evaluator.cpp @@ -1,6 +1,7 @@ #include "Evaluator.h" #include "Runtime.h" +#include "Allocator/GCSafepoint.h" #include "SExpressionLinearizer.h" #include "SOpAssign.h" #include "SOpDispatchMessage.h" @@ -203,7 +204,7 @@ Object *Evaluator::invoke_with_(HeapObject *method, Object *receiver) { int size = _runtime->methodEnvironmentSize_(method); HeapObject *environment = _runtime->newEnvironmentSized_(size); HeapObject *executable = this->prepareForExecution_(method); - _work = reinterpret_cast*>(_runtime->executableCodePlatformCode_(executable)); + _work = _runtime->executableCodeWork_(executable); this->_context->buildMethodFrameFor_code_environment_(receiver, method, environment); @@ -224,8 +225,8 @@ HeapObject* Evaluator::prepareForExecution_(HeapObject *method) { auto sexpressions = decoder.decodeMethod(); this->_linearizer->visitMethod(sexpressions, method); - executableCode = this->_runtime->newExecutableCodeFor_with_(method, reinterpret_cast(this->_linearizer->operations())); - this->_runtime->methodExecutableCode_put_(method, (Object*)executableCode); + executableCode = this->_runtime->newExecutableCodeFor_with_(method, this->_linearizer->operations()); + this->_runtime->methodExecutableCode_put_(method, executableCode); return executableCode; } @@ -262,7 +263,7 @@ Object* Evaluator::send_to_with_(HeapObject *symbol, Object *receiver, std::vect this->evaluate(); this->_context->popLaunchFrame(prevRegE); auto executableCode = this->_runtime->methodExecutableCode_(this->_context->method()); - this->_work = reinterpret_cast* >(_runtime->executableCodePlatformCode_(executableCode));; + this->_work = _runtime->executableCodeWork_(executableCode); return this->_regR; } @@ -419,6 +420,8 @@ void Evaluator::popFrameAndPrepare() void Evaluator::visitOpReturn(SOpReturn *anSOpReturn) { this->popFrameAndPrepare(); + + _runtime->_heap->collectIfTime(); } void Evaluator::visitOpNonLocalReturn(SOpNonLocalReturn *anSOpNonLocalReturn) @@ -685,15 +688,18 @@ Object* Evaluator::primitiveHostLoadModule() { } Object* Evaluator::primitiveNew() { + GCSafepoint safepoint(this->_runtime->_heap); return (Object*)this->_runtime->newSlotsOf_(this->_context->self()->asHeapObject()); } Object* Evaluator::primitiveNewBytes() { + GCSafepoint safepoint(this->_runtime->_heap); auto size = this->_context->firstArgument()->asSmallInteger()->asNative(); return (Object*)this->_runtime->newBytes_size_(this->_context->self()->asHeapObject(), size); } Object* Evaluator::primitiveNewSized() { + GCSafepoint safepoint(this->_runtime->_heap); auto size = this->_context->firstArgument()->asSmallInteger()->asNative(); return (Object*)this->_runtime->newOf_sized_(this->_context->self()->asHeapObject(), size); } From 59c19714073c509e7263809cbe8d888be9add79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:25:49 -0300 Subject: [PATCH 14/26] [cppvm:gc] Make evaluation context aware of moving GC --- runtime/cpp/Evaluator/EvaluationContext.cpp | 10 ++++------ runtime/cpp/Evaluator/EvaluationContext.h | 6 +++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/runtime/cpp/Evaluator/EvaluationContext.cpp b/runtime/cpp/Evaluator/EvaluationContext.cpp index 622a3dc7..6a577f71 100644 --- a/runtime/cpp/Evaluator/EvaluationContext.cpp +++ b/runtime/cpp/Evaluator/EvaluationContext.cpp @@ -16,8 +16,6 @@ using namespace Egg; -class Runtime; - HeapObject* EvaluationContext::classBinding() { return _runtime->methodClassBinding_(this->method()); @@ -68,12 +66,12 @@ std::vector EvaluationContext::methodArguments() { return arguments; } -std::vector* EvaluationContext::buildLaunchFrame(HeapObject *symbol, int argCount) +PlatformCode* EvaluationContext::buildLaunchFrame(HeapObject *symbol, int argCount) { auto launcher = _runtime->newCompiledMethod(); - auto bytecodes = new std::vector(); - auto executable = _runtime->newExecutableCodeFor_with_(launcher, reinterpret_cast(bytecodes)); - _runtime->methodExecutableCode_put_(launcher, (Object*)executable); + auto bytecodes = newPlatformCode(); + auto executable = _runtime->newExecutableCodeFor_with_(launcher, bytecodes); + _runtime->methodExecutableCode_put_(launcher, executable); this->buildMethodFrameFor_code_environment_((Object*)_runtime->_nilObj, launcher, _runtime->_nilObj); auto literal = new SLiteral(0, (Object*)_runtime->_nilObj); diff --git a/runtime/cpp/Evaluator/EvaluationContext.h b/runtime/cpp/Evaluator/EvaluationContext.h index b00e10d4..22adbe9c 100644 --- a/runtime/cpp/Evaluator/EvaluationContext.h +++ b/runtime/cpp/Evaluator/EvaluationContext.h @@ -52,6 +52,10 @@ class EvaluationContext { HeapObject* environment() { return _regE; } HeapObject* compiledCode() { return _regM; } + uintptr_t stackPointer() { return _regSP; } + uintptr_t framePointer() { return _regBP; } + Object** stack() { return _stack; } + HeapObject* classBinding(); int tempOffset() { return 4; } @@ -91,7 +95,7 @@ class EvaluationContext { std::vector methodArguments(); void buildFrameFor_code_environment_temps_(Object *receiver, HeapObject *compiledCode, HeapObject *environment, uint32_t temps); - std::vector* buildLaunchFrame(HeapObject *symbol, int argCount); + PlatformCode* buildLaunchFrame(HeapObject *symbol, int argCount); void buildClosureFrameFor_code_environment_(Object *receiver, HeapObject *compiledCode, HeapObject *environment); void buildMethodFrameFor_code_environment_(Object *receiver, HeapObject *compiledCode, HeapObject *environment); void popLaunchFrame(HeapObject *prevRegE); From 11219e354b980a5eea1eebbf8e9ac7f8bc2bb0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:26:46 -0300 Subject: [PATCH 15/26] [cppvm] add a few constants for accessing Process inst vars and friends --- runtime/cpp/KnownConstants.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/runtime/cpp/KnownConstants.h b/runtime/cpp/KnownConstants.h index d56275a5..e785134a 100644 --- a/runtime/cpp/KnownConstants.h +++ b/runtime/cpp/KnownConstants.h @@ -86,6 +86,15 @@ enum Offsets { ExecutableCodePlatformCode = 0, ExecutableCodeCompiledCode = 1, + // inst vars of Process + ProcessNativeStack = 1, + ProcessTopContext = 2, + + // inst vars of ProcessVMStack (the VM's internal stack object for processes) + ProcessVMStackProcess = 0, + ProcessVMStackSP = 1, + ProcessVMStackBP = 2, + FFI_uint8 = 0, FFI_sint8 = 1, FFI_uint16 = 2, From 56dbb674c3b3c2bdb1b1c217c0cc7c97807cf582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:28:10 -0300 Subject: [PATCH 16/26] [cppvm:gc] add a method to decommit memory (in posix) --- runtime/cpp/Posix/Memory.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/runtime/cpp/Posix/Memory.cpp b/runtime/cpp/Posix/Memory.cpp index 57b4152d..81f8dbdd 100644 --- a/runtime/cpp/Posix/Memory.cpp +++ b/runtime/cpp/Posix/Memory.cpp @@ -30,6 +30,7 @@ namespace Egg { } uintptr_t Egg::ReserveAligned4GB() { + uintptr_t size = 4L * 1024 * 1024 * 1024; // 4GB uintptr_t alignment = 4L * 1024 * 1024 * 1024; // 4GB alignment uintptr_t total_size = size + alignment; @@ -118,6 +119,13 @@ void Egg::CommitMemory(uintptr_t base, uintptr_t size) std::memset((char*)base, 0, size); } +void Egg::DecommitMemory(uintptr_t base, uintptr_t size) +{ + if (madvise((void*)base, size, MADV_DONTNEED) != 0) { + error("Failed to decommit memory."); + } +} + void Egg::FreeMemory(uintptr_t base, uintptr_t size) { munmap((void*)base, size); From f4218dd8360e13ff9f2e51d480b412cb24672895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:29:47 -0300 Subject: [PATCH 17/26] [cppvm:gc] a few last bits in SmallInteger for GC integration --- runtime/cpp/SmallInteger.h | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/runtime/cpp/SmallInteger.h b/runtime/cpp/SmallInteger.h index 5b249257..1864c22f 100644 --- a/runtime/cpp/SmallInteger.h +++ b/runtime/cpp/SmallInteger.h @@ -35,14 +35,33 @@ struct SmallInteger { return (SmallInteger *)(((uintptr_t)intVal << 1) | 1); } + /** + * Returns a `small pointer` from an aligned address: it is a way to encode + * a pointer as a small integer (marking int bit), so that GC doesn't mess + * with it when tracing. It only requires the pointer is aligned to pointer + * size (rightmost bits are 0). Used for example to encode native code addresses. + */ + static SmallInteger* + smallpointerFrom(void* alignedBuffer) + { + ASSERT( ((uintptr_t)alignedBuffer & 1) == 0 ); + return (SmallInteger *)(((uintptr_t)alignedBuffer) | 1); + } + bool isSmallInteger(); - intptr_t asNative(); /// Assuming `this` encodes a SmallInteger, decode its - /// (signed) integer value + /** + * Assuming `this` encodes a SmallInteger, decode its + * (signed) integer value + **/ + intptr_t asNative(); + /** + * Assuming `this` encodes a pointer stored as a SmallInteger, + * return the address of the original pointer (by clearing last bit) + **/ template - T asObject() /// Assuming `this` encodes a pointer stored as a SmallInteger, - /// return the address of the original pointer + T asObject() { // ASSERT(this->object()->isSmallInteger()); From 6f887411de0bffaf93bb597cd813d258ed2f3769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:31:38 -0300 Subject: [PATCH 18/26] [kernel] process stacks know their bp, which is used to traverse stack frames --- modules/Kernel/ProcessStack.st | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/Kernel/ProcessStack.st b/modules/Kernel/ProcessStack.st index bae0637e..9f64dca4 100644 --- a/modules/Kernel/ProcessStack.st +++ b/modules/Kernel/ProcessStack.st @@ -8,7 +8,8 @@ Class { #superclass : #Object, #instVars : [ 'process', - 'sp' + 'sp', + 'bp' ], #category : #Kernel } From d280157090910f799581cdc0e14fc83be3c04a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:32:36 -0300 Subject: [PATCH 19/26] [cppvm:gc] make bootstrapper register the kernel space to GC --- runtime/cpp/Bootstrapper.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runtime/cpp/Bootstrapper.h b/runtime/cpp/Bootstrapper.h index 252bc2b4..0eebb6c7 100644 --- a/runtime/cpp/Bootstrapper.h +++ b/runtime/cpp/Bootstrapper.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -22,6 +24,8 @@ class Bootstrapper { Bootstrapper(ImageSegment *kernel) { this->_kernel = kernel; this->_runtime = new Runtime(this, this->_kernel); + + this->_runtime->addSegmentSpace_(kernel); // this->_runtime->bootstrapper_(this); this->_runtime->initializeEvaluator(); } @@ -91,6 +95,7 @@ class Bootstrapper { std::vector imports; this->bindModuleImports(imageSegment, imports); imageSegment->fixPointerSlots(imports); + this->_runtime->addSegmentSpace_(imageSegment); return imageSegment; } From ebfe7a030be32f4989405b04314cbd6dffdc10c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:33:45 -0300 Subject: [PATCH 20/26] [cppvm:gc] add a couple missing declarations --- runtime/cpp/Evaluator/Runtime.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runtime/cpp/Evaluator/Runtime.h b/runtime/cpp/Evaluator/Runtime.h index 87e75a26..5fa0d969 100644 --- a/runtime/cpp/Evaluator/Runtime.h +++ b/runtime/cpp/Evaluator/Runtime.h @@ -24,7 +24,9 @@ class Bootstrapper; extern Runtime *debugRuntime; class Runtime { + std::string printGlobalCache(); public: + void checkCache(); Bootstrapper *_bootstrapper; ImageSegment *_kernel; Evaluator *_evaluator; From d5683887cce9cb1fb5d6eb31f4db05ca06fe2780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:40:53 -0300 Subject: [PATCH 21/26] [cppvm] add primitive for measuring time --- runtime/cpp/Evaluator/Evaluator.cpp | 9 ++++++++- runtime/cpp/Evaluator/Evaluator.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/runtime/cpp/Evaluator/Evaluator.cpp b/runtime/cpp/Evaluator/Evaluator.cpp index 2b3f9e80..69f63d5b 100644 --- a/runtime/cpp/Evaluator/Evaluator.cpp +++ b/runtime/cpp/Evaluator/Evaluator.cpp @@ -23,6 +23,7 @@ #include "FFIGlue.h" +#include #include #include #include @@ -159,7 +160,7 @@ void Evaluator::initializePrimitives() this->addPrimitive("FFICall", &Evaluator::primitiveFFICall); this->addPrimitive("HostInitializeFFI", &Evaluator::primitiveHostInitializeFFI); this->addPrimitive("HostPlatformName", &Evaluator::primitiveHostPlatformName); - + this->addPrimitive("HostCurrentMilliseconds", &Evaluator::primitiveHostCurrentMilliseconds); /*this->addPrimitive("PrepareForExecution", &Evaluator::primitivePrepareForExecution); this->addPrimitive("ProcessVMStackInitialize", &Evaluator::primitiveProcessVMStackInitialize); this->addPrimitive("ProcessVMStackAt", &Evaluator::primitiveProcessVMStackAt); @@ -663,6 +664,12 @@ Object* Evaluator::primitiveHash() { return newIntObject(this->_runtime->hashFor_(this->_context->self())); } +Object * Evaluator::primitiveHostCurrentMilliseconds() { + intptr_t now = std::chrono::duration_cast< std::chrono::milliseconds >( + std::chrono::system_clock::now().time_since_epoch()).count(); + return newIntObject((intptr_t)now); +} + Object* Evaluator::primitiveHostPlatformName() { return (Object*)this->_runtime->newString_(PlatformName()); } diff --git a/runtime/cpp/Evaluator/Evaluator.h b/runtime/cpp/Evaluator/Evaluator.h index 5145d0d5..ba52bb03 100644 --- a/runtime/cpp/Evaluator/Evaluator.h +++ b/runtime/cpp/Evaluator/Evaluator.h @@ -200,6 +200,7 @@ class Evaluator : public SExpressionVisitor { Object* primitiveHash(); + Object* primitiveHostCurrentMilliseconds(); Object* primitiveHostFixOverrides(); Object* primitiveHostInitializeFFI(); Object* primitiveHostLoadModule(); From 68bdc4179e260810910fe6b85f0becc17605464c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:50:21 -0300 Subject: [PATCH 22/26] [cppvm] make creation of enviornments optional methods that don't need an env will get need instead --- runtime/cpp/Evaluator/Evaluator.cpp | 3 ++- runtime/cpp/KnownConstants.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/cpp/Evaluator/Evaluator.cpp b/runtime/cpp/Evaluator/Evaluator.cpp index 69f63d5b..b8e7d47f 100644 --- a/runtime/cpp/Evaluator/Evaluator.cpp +++ b/runtime/cpp/Evaluator/Evaluator.cpp @@ -203,7 +203,8 @@ Evaluator::lookup_startingAt_sendSite_(HeapObject *symbol, HeapObject *behavior, Object *Evaluator::invoke_with_(HeapObject *method, Object *receiver) { int size = _runtime->methodEnvironmentSize_(method); - HeapObject *environment = _runtime->newEnvironmentSized_(size); + + HeapObject *environment = _runtime->methodNeedsEnviornment_(method) ? _runtime->newEnvironmentSized_(size) : _runtime-> _nilObj; HeapObject *executable = this->prepareForExecution_(method); _work = _runtime->executableCodeWork_(executable); diff --git a/runtime/cpp/KnownConstants.h b/runtime/cpp/KnownConstants.h index e785134a..7cb889a8 100644 --- a/runtime/cpp/KnownConstants.h +++ b/runtime/cpp/KnownConstants.h @@ -16,6 +16,7 @@ extern uintptr_t BEHAVIOR_ADDRESS_SPACE; namespace Egg { + enum BlockFlags { CapturesHome = 0x800000, CapturesSelf = 0x400000 @@ -33,6 +34,7 @@ enum MethodFlags { MethodArgCount = 0x3F, MethodTempCount = 0x1FE000, MethodTempCountShift = 13, + MethodNeedsEnvironment = 0x400000, MethodIsExtension = 0x80000000 }; From e1f6c396aee311a8a69e65b820bdf10424d68fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:51:33 -0300 Subject: [PATCH 23/26] [cppvm] enhanced debug pretty-printing of stack objects --- runtime/cpp/Evaluator/EvaluationContext.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/runtime/cpp/Evaluator/EvaluationContext.cpp b/runtime/cpp/Evaluator/EvaluationContext.cpp index 6a577f71..ec7cd53c 100644 --- a/runtime/cpp/Evaluator/EvaluationContext.cpp +++ b/runtime/cpp/Evaluator/EvaluationContext.cpp @@ -413,9 +413,9 @@ std::string EvaluationContext::backtrace() { return s.str(); } -void printStackObject_into_(Object *o, std::ostringstream &s) +void printStackObject_into_(uintptr_t index, Object *o, std::ostringstream &s) { - s << "| " << std::setw(sizeof(void*)*2) << std::setfill(' ') << std::hex << o << " | "; + s << std::setw(sizeof(void*)*2) << std::setfill(' ') << std::hex << index - 1 << " | " << std::setw(sizeof(void*)*2) << std::setfill(' ') << std::hex << o << " | "; if (o == nullptr) s << "bad obj"; else @@ -426,15 +426,15 @@ std::string EvaluationContext::printStackContents() { std::vector frames; uintptr_t current = _regSP; uintptr_t next = _regBP; - s << "|------------------|" << std::endl; + s << " |------------------|" << std::endl; do { for (uintptr_t index = current; index < next; index++) { - printStackObject_into_(_stack[index - 1], s); + printStackObject_into_(index, _stack[index - 1], s); s << std::endl; } - s << "| " << std::setw(sizeof(void*)*2) << std::setfill(' ') << std::hex << _stack[next - 1] << " | fp" << std::endl; - s << "| " << std::setw(sizeof(void*)*2) << std::setfill(' ') << std::hex << _stack[next + 1 - 1] << " | retaddr" << std::endl; + s << std::setw(sizeof(void*)*2) << std::setfill(' ') << std::hex << next - 1 << " | " << std::setw(sizeof(void*)*2) << std::setfill(' ') << std::hex << _stack[next - 1] << " | fp" << std::endl; + s << std::setw(sizeof(void*)*2) << std::setfill(' ') << std::hex << next + 1 - 1 << " | " << std::setw(sizeof(void*)*2) << std::setfill(' ') << std::hex << _stack[next + 1 - 1] << " | retaddr" << std::endl; current = next + 2; next = (uintptr_t)_stack[next - 1]; From cd405f7104d15f94f99a735a715f6592992c483c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 20:55:26 -0300 Subject: [PATCH 24/26] [cppvm] fix includes in Runtime.h --- runtime/cpp/Evaluator/Runtime.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/cpp/Evaluator/Runtime.h b/runtime/cpp/Evaluator/Runtime.h index 5fa0d969..5a6211e4 100644 --- a/runtime/cpp/Evaluator/Runtime.h +++ b/runtime/cpp/Evaluator/Runtime.h @@ -1,14 +1,14 @@ #ifndef _RUNTIME_H_ #define _RUNTIME_H_ -#include #include #include #include +#include -#include "../HeapObject.h" -#include "../ImageSegment.h" -#include "../KnownConstants.h" +#include "HeapObject.h" +#include "ImageSegment.h" +#include "KnownConstants.h" #include "PlatformCode.h" #include "GCedRef.h" From 3433bd581118ad0ec31a8f8b154af7ebc0794991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 21:01:21 -0300 Subject: [PATCH 25/26] [cppvm:gc] add DecommitMemory version for Windows --- runtime/cpp/Windows/Memory.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/runtime/cpp/Windows/Memory.cpp b/runtime/cpp/Windows/Memory.cpp index ae54361e..05cd856b 100644 --- a/runtime/cpp/Windows/Memory.cpp +++ b/runtime/cpp/Windows/Memory.cpp @@ -85,6 +85,13 @@ void Egg::CommitMemory(uintptr_t base, uintptr_t size) std::memset((char*)base, 0, size); } +void Egg::DecommitMemory(uintptr_t base, uintptr_t size) +{ + if (!VirtualFree((void*)base, size, MEM_DECOMMIT)) { + error("Failed to decommit memory."); + } +} + void Egg::FreeMemory(uintptr_t base, uintptr_t size) { VirtualFree((void*)base, 0, MEM_RELEASE); From 3fea986ec37ca4222f6229d46839b7bd47fbfade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Pim=C3=A1s?= Date: Sun, 23 Mar 2025 21:19:28 -0300 Subject: [PATCH 26/26] [cppvm] windows requires differentiated version for aligned allocation --- runtime/cpp/Allocator/Memory.h | 13 +++++++++++++ runtime/cpp/Evaluator/PlatformCode.cpp | 6 ++++-- runtime/cpp/Posix/Memory.cpp | 5 +++++ runtime/cpp/Windows/Memory.cpp | 5 +++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/runtime/cpp/Allocator/Memory.h b/runtime/cpp/Allocator/Memory.h index 4f083aa1..00bb8cd7 100644 --- a/runtime/cpp/Allocator/Memory.h +++ b/runtime/cpp/Allocator/Memory.h @@ -23,6 +23,19 @@ void FreeMemory(uintptr_t base, uintptr_t size); uintptr_t ReserveAligned4GB(); uintptr_t pagealign(uintptr_t addr); +template +T* aligned_alloc() +{ +#ifdef _WIN32 + return static_cast(_aligned_malloc(sizeof(T), sizeof(void*))); +#else + return static_cast(std::aligned_alloc(sizeof(void*), sizeof(T))); +#endif +} + +void aligned_free(void* mem); + + class HeapObject; } // namespace Egg diff --git a/runtime/cpp/Evaluator/PlatformCode.cpp b/runtime/cpp/Evaluator/PlatformCode.cpp index 6f3acb89..1bb4be31 100644 --- a/runtime/cpp/Evaluator/PlatformCode.cpp +++ b/runtime/cpp/Evaluator/PlatformCode.cpp @@ -1,9 +1,11 @@ #include "PlatformCode.h" +#include + Egg::PlatformCode* Egg::newPlatformCode() { - void* rawmem = static_cast(std::aligned_alloc(alignof(void*), sizeof(PlatformCode))); + void* rawmem = Egg::aligned_alloc(); if (!rawmem) { throw std::bad_alloc(); } @@ -17,5 +19,5 @@ void Egg::deletePlatformCode(Egg::PlatformCode* platformCode) platformCode->~PlatformCode(); // Free the raw memory - std::free(platformCode); + aligned_free((void*)platformCode); } \ No newline at end of file diff --git a/runtime/cpp/Posix/Memory.cpp b/runtime/cpp/Posix/Memory.cpp index 81f8dbdd..00191d1d 100644 --- a/runtime/cpp/Posix/Memory.cpp +++ b/runtime/cpp/Posix/Memory.cpp @@ -71,6 +71,11 @@ void Egg::InitializeMemory() } +void Egg::aligned_free(void* mem) +{ + std::free(mem); +} + uintptr_t Egg::pagealign(uintptr_t addr) { static int pagesize = -1; diff --git a/runtime/cpp/Windows/Memory.cpp b/runtime/cpp/Windows/Memory.cpp index 05cd856b..3215b94a 100644 --- a/runtime/cpp/Windows/Memory.cpp +++ b/runtime/cpp/Windows/Memory.cpp @@ -41,6 +41,11 @@ void Egg::InitializeMemory() } +void Egg::aligned_free(void* mem) +{ + _aligned_free(mem); +} + uintptr_t Egg::pagealign(uintptr_t addr) { return align(addr, Egg::page_alignment);