Compare commits

...

12 commits

Author SHA1 Message Date
HyukWoo Park
205b5f35bd Print compress/decompress info
* show source code size and tick (time) of each compression/decompression

Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2022-09-28 15:29:15 +09:00
HyukWoo Park
d1535b2088 Update measurement of source code size for the code compression
Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2022-01-27 21:18:18 +09:00
HyukWoo Park
a771419d0c Update measurement of source code size in the time line for origin mode
Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2022-01-18 19:44:24 +09:00
HyukWoo Park
dfba6d6bd5 Fix compression measurement
* fix conflict in compression which is GC_free should be invoked during GC is enabled
* add GC overhead profile
* add memory dump interval option

Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2022-01-03 13:25:56 +09:00
Boram Bae
e80a414017 Add an option to dump memory usage
Signed-off-by: Boram Bae <boram21.bae@samsung.com>
2021-12-16 13:40:50 +09:00
HyukWoo Park
af79291159 Remove ReloadableString in default configuration
Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2021-12-15 18:42:54 +09:00
HyukWoo Park
0eaf50714b Fix nested compression case
* code compression could trigger another GC when it allocates compressed string on the heap
* disable GC while code compression working

Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2021-12-15 18:34:42 +09:00
HyukWoo Park
b51839b7b9 Add reference counter in CompressibleString
* remove stack searching to check if CompressibleString is in use
* add ref counter instead

Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2021-12-10 18:48:35 +09:00
HyukWoo Park
73e5e66b4a Update HeapProfile to collect heap information during the runtime
Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2021-11-26 16:06:20 +09:00
HyukWoo Park
bcdbaa4bac Allocate heap space for temporal buffer used while compresison/decompression
Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2021-11-26 15:30:08 +09:00
HyukWoo Park
31cf4200f9 Add build option for compression
Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2021-11-25 14:22:08 +09:00
HyukWoo Park
e3fe51cf14 Allocate string buffer of CompressibleString in the heap
* to test CompressibleString's effect on GC heap

Signed-off-by: HyukWoo Park <hyukwoo.park@samsung.com>
2021-11-24 17:55:59 +09:00
13 changed files with 369 additions and 81 deletions

View file

@ -144,13 +144,14 @@ ELSEIF (${ESCARGOT_HOST} STREQUAL "tizen_obs")
SET (ESCARGOT_CXXFLAGS ${ESCARGOT_CXXFLAGS} ${DLOG_CFLAGS_OTHER}) SET (ESCARGOT_CXXFLAGS ${ESCARGOT_CXXFLAGS} ${DLOG_CFLAGS_OTHER})
ENDIF() ENDIF()
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DENABLE_COMPRESSIBLE_STRING) IF (ESCARGOT_SOURCE_COMPRESSION)
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DENABLE_COMPRESSIBLE_STRING)
ENDIF()
IF (ESCARGOT_SMALL_CONFIG) IF (ESCARGOT_SMALL_CONFIG)
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DLZ4_MEMORY_USAGE=16 -DLZ4_HEAPMODE=1) SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DLZ4_MEMORY_USAGE=16 -DLZ4_HEAPMODE=1)
ENDIF() ENDIF()
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DENABLE_RELOADABLE_STRING)
IF (ESCARGOT_CODE_CACHE) IF (ESCARGOT_CODE_CACHE)
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DENABLE_CODE_CACHE) SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DENABLE_CODE_CACHE)
ENDIF() ENDIF()

View file

@ -511,6 +511,12 @@ StringRef* StringRef::createFromUTF8(const char* s, size_t len, bool maybeASCII)
return toRef(String::fromUTF8(s, len, maybeASCII)); return toRef(String::fromUTF8(s, len, maybeASCII));
} }
StringRef* StringRef::createFromUTF8(ContextRef* context, const char* src, size_t len, bool maybeASCII)
{
auto s = utf8StringToUTF16String(src, len);
return toRef(new UTF16StringFinalizer(toImpl(context), std::move(s)));
}
StringRef* StringRef::createFromUTF16(const char16_t* s, size_t len) StringRef* StringRef::createFromUTF16(const char16_t* s, size_t len)
{ {
return toRef(new UTF16String(s, len)); return toRef(new UTF16String(s, len));
@ -521,6 +527,11 @@ StringRef* StringRef::createFromLatin1(const unsigned char* s, size_t len)
return toRef(new Latin1String(s, len)); return toRef(new Latin1String(s, len));
} }
StringRef* StringRef::createFromLatin1(ContextRef* context, const unsigned char* s, size_t len)
{
return toRef(new Latin1StringFinalizer(toImpl(context), s, len));
}
StringRef* StringRef::createExternalFromASCII(const char* s, size_t len) StringRef* StringRef::createExternalFromASCII(const char* s, size_t len)
{ {
return toRef(new ASCIIStringFromExternalMemory(s, len)); return toRef(new ASCIIStringFromExternalMemory(s, len));
@ -1101,6 +1112,11 @@ void VMInstanceRef::unregisterErrorCreationCallback()
toImpl(this)->unregisterErrorCreationCallback(); toImpl(this)->unregisterErrorCreationCallback();
} }
size_t VMInstanceRef::validSourceSize()
{
return toImpl(this)->validSourceSize();
}
void VMInstanceRef::registerPromiseHook(PromiseHook promiseHook) void VMInstanceRef::registerPromiseHook(PromiseHook promiseHook)
{ {
toImpl(this)->registerPromiseHook([](ExecutionState& state, VMInstance::PromiseHookType type, PromiseObject* promise, const Value& parent, void* hook) -> void { toImpl(this)->registerPromiseHook([](ExecutionState& state, VMInstance::PromiseHookType type, PromiseObject* promise, const Value& parent, void* hook) -> void {

View file

@ -260,6 +260,11 @@ public:
return *m_holder; return *m_holder;
} }
bool isInitialized() const
{
return m_holder != nullptr;
}
private: private:
void initHolderSpace(T* initialValue) void initHolderSpace(T* initialValue)
{ {
@ -639,6 +644,8 @@ public:
void registerErrorCreationCallback(ErrorCreationCallback cb); void registerErrorCreationCallback(ErrorCreationCallback cb);
void unregisterErrorCreationCallback(); void unregisterErrorCreationCallback();
size_t validSourceSize();
enum PromiseHookType { enum PromiseHookType {
Init, Init,
Resolve, Resolve,
@ -859,8 +866,11 @@ public:
return createFromUTF8(str, N - 1); return createFromUTF8(str, N - 1);
} }
static StringRef* createFromUTF8(const char* s, size_t byteLength, bool maybeASCII = true); static StringRef* createFromUTF8(const char* s, size_t byteLength, bool maybeASCII = true);
static StringRef* createFromUTF8(ContextRef* context, const char* s, size_t len, bool maybeASCII);
static StringRef* createFromUTF16(const char16_t* s, size_t stringLength); static StringRef* createFromUTF16(const char16_t* s, size_t stringLength);
static StringRef* createFromLatin1(const unsigned char* s, size_t stringLength); static StringRef* createFromLatin1(const unsigned char* s, size_t stringLength);
static StringRef* createFromLatin1(ContextRef* context, const unsigned char* s, size_t len);
static StringRef* createExternalFromASCII(const char* s, size_t stringLength); static StringRef* createExternalFromASCII(const char* s, size_t stringLength);
static StringRef* createExternalFromLatin1(const unsigned char* s, size_t stringLength); static StringRef* createExternalFromLatin1(const unsigned char* s, size_t stringLength);

View file

@ -23,6 +23,7 @@
#include "CompressibleString.h" #include "CompressibleString.h"
#include "runtime/Context.h" #include "runtime/Context.h"
#include "runtime/VMInstance.h" #include "runtime/VMInstance.h"
#include "runtime/Global.h"
#include "lz4.h" #include "lz4.h"
namespace Escargot { namespace Escargot {
@ -33,7 +34,9 @@ void* CompressibleString::operator new(size_t size)
static MAY_THREAD_LOCAL GC_descr descr; static MAY_THREAD_LOCAL GC_descr descr;
if (!typeInited) { if (!typeInited) {
GC_word obj_bitmap[GC_BITMAP_SIZE(CompressibleString)] = { 0 }; GC_word obj_bitmap[GC_BITMAP_SIZE(CompressibleString)] = { 0 };
GC_set_bit(obj_bitmap, GC_WORD_OFFSET(CompressibleString, m_bufferData.buffer));
GC_set_bit(obj_bitmap, GC_WORD_OFFSET(CompressibleString, m_vmInstance)); GC_set_bit(obj_bitmap, GC_WORD_OFFSET(CompressibleString, m_vmInstance));
GC_set_bit(obj_bitmap, GC_WORD_OFFSET(CompressibleString, m_compressedData));
descr = GC_make_descriptor(obj_bitmap, GC_WORD_LEN(CompressibleString)); descr = GC_make_descriptor(obj_bitmap, GC_WORD_LEN(CompressibleString));
typeInited = true; typeInited = true;
} }
@ -44,6 +47,7 @@ CompressibleString::CompressibleString(VMInstance* instance)
: String() : String()
, m_isOwnerMayFreed(false) , m_isOwnerMayFreed(false)
, m_isCompressed(false) , m_isCompressed(false)
, m_refCount(0)
, m_vmInstance(instance) , m_vmInstance(instance)
, m_lastUsedTickcount(fastTickCount()) , m_lastUsedTickcount(fastTickCount())
{ {
@ -53,14 +57,18 @@ CompressibleString::CompressibleString(VMInstance* instance)
v.push_back(this); v.push_back(this);
GC_REGISTER_FINALIZER_NO_ORDER(this, [](void* obj, void*) { GC_REGISTER_FINALIZER_NO_ORDER(this, [](void* obj, void*) {
CompressibleString* self = (CompressibleString*)obj; CompressibleString* self = (CompressibleString*)obj;
ASSERT(self->refCount() == 0);
size_t strLength = self->m_bufferData.length * (self->m_bufferData.has8BitContent ? 1 : 2);
if (self->isCompressed()) { if (self->isCompressed()) {
self->m_compressedData.~CompressedDataVector(); self->clearCompressedData();
} else { } else {
deallocateStringDataBuffer(const_cast<void*>(self->m_bufferData.buffer), self->m_bufferData.length * (self->m_bufferData.has8BitContent ? 1 : 2)); deallocateStringDataBuffer(const_cast<void*>(self->m_bufferData.buffer), strLength);
} }
if (!self->m_isOwnerMayFreed) { if (!self->m_isOwnerMayFreed) {
self->m_vmInstance->compressibleStringsUncomressedBufferSize() -= self->decomressedBufferSize(); self->m_vmInstance->compressibleStringsUncomressedBufferSize() -= self->decomressedBufferSize();
self->m_vmInstance->decreaseSourceSize(strLength);
auto& v = self->m_vmInstance->compressibleStrings(); auto& v = self->m_vmInstance->compressibleStrings();
v.erase(std::find(v.begin(), v.end(), self)); v.erase(std::find(v.begin(), v.end(), self));
@ -75,6 +83,7 @@ CompressibleString::CompressibleString(VMInstance* instance, const char* str, si
char* buf = (char*)allocateStringDataBuffer(sizeof(char) * len); char* buf = (char*)allocateStringDataBuffer(sizeof(char) * len);
memcpy(buf, str, len); memcpy(buf, str, len);
initBufferAccessData(buf, len, true); initBufferAccessData(buf, len, true);
instance->increaseSourceSize(len);
} }
CompressibleString::CompressibleString(VMInstance* instance, const LChar* str, size_t len) CompressibleString::CompressibleString(VMInstance* instance, const LChar* str, size_t len)
@ -83,6 +92,7 @@ CompressibleString::CompressibleString(VMInstance* instance, const LChar* str, s
char* buf = (char*)allocateStringDataBuffer(sizeof(char) * len); char* buf = (char*)allocateStringDataBuffer(sizeof(char) * len);
memcpy(buf, str, len); memcpy(buf, str, len);
initBufferAccessData(buf, len, true); initBufferAccessData(buf, len, true);
instance->increaseSourceSize(len);
} }
CompressibleString::CompressibleString(VMInstance* instance, const char16_t* str, size_t len) CompressibleString::CompressibleString(VMInstance* instance, const char16_t* str, size_t len)
@ -91,11 +101,13 @@ CompressibleString::CompressibleString(VMInstance* instance, const char16_t* str
char* buf = (char*)allocateStringDataBuffer(sizeof(char) * len * 2); char* buf = (char*)allocateStringDataBuffer(sizeof(char) * len * 2);
memcpy(buf, str, len * 2); memcpy(buf, str, len * 2);
initBufferAccessData(buf, len, false); initBufferAccessData(buf, len, false);
instance->increaseSourceSize(len * 2);
} }
CompressibleString::CompressibleString(VMInstance* instance, void* buffer, size_t stringLength, bool is8bit) CompressibleString::CompressibleString(VMInstance* instance, void* buffer, size_t stringLength, bool is8bit)
: CompressibleString(instance) : CompressibleString(instance)
{ {
RELEASE_ASSERT_NOT_REACHED();
initBufferAccessData(buffer, stringLength, is8bit); initBufferAccessData(buffer, stringLength, is8bit);
} }
@ -135,26 +147,36 @@ UTF16StringData CompressibleString::toUTF16StringData() const
void* CompressibleString::allocateStringDataBuffer(size_t byteLength) void* CompressibleString::allocateStringDataBuffer(size_t byteLength)
{ {
return malloc(byteLength); return GC_MALLOC_ATOMIC(byteLength);
} }
void CompressibleString::deallocateStringDataBuffer(void* ptr, size_t byteLength) void CompressibleString::deallocateStringDataBuffer(void* ptr, size_t byteLength)
{ {
free(ptr); GC_FREE(ptr);
}
void CompressibleString::clearCompressedData()
{
for (size_t i = 0; i < m_compressedData.size(); i++) {
GC_FREE(m_compressedData[i].compressedBuffer);
}
m_compressedData.clear();
} }
bool CompressibleString::compress() bool CompressibleString::compress()
{ {
ASSERT(!m_isCompressed); ASSERT(!m_isCompressed);
if (UNLIKELY(!m_bufferData.length)) { if (UNLIKELY(!m_bufferData.length || !!m_refCount)) {
ESCARGOT_LOG_INFO("Compression Failed due to string usage in stack\n");
return false; return false;
} }
bool has8Bit = m_bufferData.has8BitContent; bool has8Bit = m_bufferData.has8BitContent;
if (has8Bit) { if (has8Bit) {
return compressWorker<LChar>(currentStackPointer()); return compressWorker<LChar>();
} else { } else {
return compressWorker<char16_t>(currentStackPointer()); return compressWorker<char16_t>();
} }
} }
@ -163,75 +185,79 @@ void CompressibleString::decompress()
ASSERT(m_isCompressed); ASSERT(m_isCompressed);
ASSERT(m_bufferData.length); ASSERT(m_bufferData.length);
uint64_t currentTick = fastTickCount();
bool has8Bit = m_bufferData.has8BitContent; bool has8Bit = m_bufferData.has8BitContent;
if (has8Bit) { if (has8Bit) {
decompressWorker<LChar>(); decompressWorker<LChar>();
} else { } else {
decompressWorker<char16_t>(); decompressWorker<char16_t>();
} }
Global::addHeapProfileDecompTime(fastTickCount() - currentTick);
Global::increaseHeapProfileDecompCount();
} }
constexpr static const size_t g_compressChunkSize = 1044465; constexpr static const size_t g_compressChunkSize = 1044465;
static_assert(LZ4_COMPRESSBOUND(g_compressChunkSize) == 1024 * 1024, ""); static_assert(LZ4_COMPRESSBOUND(g_compressChunkSize) == 1024 * 1024, "");
static ATTRIBUTE_NO_SANITIZE_ADDRESS bool testPointerExistsOnStack(size_t* start, size_t* end, const void* ptr)
{
while (start != end) {
if (UNLIKELY(*start == (size_t)ptr)) {
// if there is reference on stack, we cannot compress string.
return true;
}
start++;
}
return false;
}
template <typename StringType> template <typename StringType>
bool CompressibleString::compressWorker(void* callerSP) bool CompressibleString::compressWorker()
{ {
ASSERT(!m_isCompressed); ASSERT(!m_isCompressed && !m_refCount);
ASSERT(m_bufferData.length > 0); ASSERT(m_bufferData.length > 0);
ASSERT(!GC_is_disabled());
#if defined(STACK_GROWS_DOWN) uint64_t currentTick = fastTickCount();
size_t* start = (size_t*)((size_t)callerSP & ~(sizeof(size_t) - 1));
size_t* end = (size_t*)m_vmInstance->stackStartAddress();
#else
size_t* start = (size_t*)m_vmInstance->stackStartAddress();
size_t* end = (size_t*)((size_t)callerSP & ~(sizeof(size_t) - 1));
#endif
if (testPointerExistsOnStack(start, end, m_bufferData.buffer)) { GC_disable();
return false;
}
size_t originByteLength = m_bufferData.length * sizeof(StringType); size_t originByteLength = m_bufferData.length * sizeof(StringType);
size_t totalDecompLength = 0;
int lastBoundLength = 0; int lastBoundLength = 0;
std::unique_ptr<char[]> compBuffer; char* tempBuffer = nullptr;
ESCARGOT_LOG_INFO("COMPRESS TICK: %" PRIu64 " Size: %zu\n", currentTick, originByteLength);
for (size_t srcIndex = 0; srcIndex < originByteLength; srcIndex += g_compressChunkSize) { for (size_t srcIndex = 0; srcIndex < originByteLength; srcIndex += g_compressChunkSize) {
int srcSize = (int)std::min(g_compressChunkSize, originByteLength - srcIndex); int srcSize = (int)std::min(g_compressChunkSize, originByteLength - srcIndex);
int boundLength = LZ4::LZ4_compressBound(srcSize); int boundLength = LZ4::LZ4_compressBound(srcSize);
if (boundLength > lastBoundLength) { if (boundLength > lastBoundLength) {
compBuffer.reset(new char[boundLength]); delete[] tempBuffer;
tempBuffer = new char[boundLength];
lastBoundLength = boundLength; lastBoundLength = boundLength;
} }
int compressedLength = LZ4::LZ4_compress_default(m_bufferData.bufferAs8Bit + srcIndex, (char*)compBuffer.get(), srcSize, boundLength); int compressedLength = LZ4::LZ4_compress_default(m_bufferData.bufferAs8Bit + srcIndex, (char*)tempBuffer, srcSize, boundLength);
if (!compressedLength) { if (!compressedLength) {
// compression fail // compression fail
ESCARGOT_LOG_ERROR("Compression Failed\n");
delete[] tempBuffer;
GC_enable();
return false; return false;
} }
ASSERT(compressedLength > 0); ASSERT(compressedLength > 0);
m_compressedData.push_back(std::vector<char>(compBuffer.get(), compBuffer.get() + compressedLength)); totalDecompLength += compressedLength;
char* compBuffer = reinterpret_cast<char*>(GC_MALLOC_ATOMIC(compressedLength));
memcpy(compBuffer, tempBuffer, compressedLength);
m_compressedData.push_back(CompressedElement(compBuffer, compressedLength));
} }
delete[] tempBuffer;
m_vmInstance->compressibleStringsUncomressedBufferSize() -= decomressedBufferSize(); m_vmInstance->compressibleStringsUncomressedBufferSize() -= decomressedBufferSize();
ASSERT(originByteLength > totalDecompLength);
m_vmInstance->decreaseSourceSize(originByteLength - totalDecompLength);
GC_enable();
// immediately free the original string after compression when there is no reference on stack // immediately free the original string after compression when there is no reference on stack
deallocateStringDataBuffer(const_cast<void*>(m_bufferData.buffer), m_bufferData.length * (m_bufferData.has8BitContent ? 1 : 2)); deallocateStringDataBuffer(const_cast<void*>(m_bufferData.buffer), m_bufferData.length * (m_bufferData.has8BitContent ? 1 : 2));
m_bufferData.bufferAs8Bit = nullptr; m_bufferData.buffer = nullptr;
m_isCompressed = true; m_isCompressed = true;
/* /*
@ -242,6 +268,8 @@ bool CompressibleString::compressWorker(void* callerSP)
ESCARGOT_LOG_INFO("CompressibleString::compressWorker %fKB -> %fKB\n", originByteLength / 1024.f, compressedSize / 1024.f); ESCARGOT_LOG_INFO("CompressibleString::compressWorker %fKB -> %fKB\n", originByteLength / 1024.f, compressedSize / 1024.f);
*/ */
Global::addHeapProfileCompTime(fastTickCount() - currentTick);
Global::increaseHeapProfileCompCount();
return true; return true;
} }
@ -252,6 +280,7 @@ void CompressibleString::decompressWorker()
ASSERT(m_isCompressed); ASSERT(m_isCompressed);
size_t originByteLength = m_bufferData.length * sizeof(StringType); size_t originByteLength = m_bufferData.length * sizeof(StringType);
size_t totalDecompLength = 0;
char* dstBuffer = (char*)allocateStringDataBuffer(originByteLength); char* dstBuffer = (char*)allocateStringDataBuffer(originByteLength);
int dstIndex = 0; int dstIndex = 0;
@ -259,7 +288,9 @@ void CompressibleString::decompressWorker()
for (size_t srcIndex = 0, bufIndex = 0; srcIndex < originByteLength; srcIndex += g_compressChunkSize, bufIndex++) { for (size_t srcIndex = 0, bufIndex = 0; srcIndex < originByteLength; srcIndex += g_compressChunkSize, bufIndex++) {
int srcSize = (int)std::min(g_compressChunkSize, originByteLength - srcIndex); int srcSize = (int)std::min(g_compressChunkSize, originByteLength - srcIndex);
int decompressedLength = LZ4::LZ4_decompress_safe(m_compressedData[bufIndex].data(), dstBuffer + dstIndex, m_compressedData[bufIndex].size(), srcSize); totalDecompLength += m_compressedData[bufIndex].compressedLength;
int decompressedLength = LZ4::LZ4_decompress_safe(m_compressedData[bufIndex].compressedBuffer, dstBuffer + dstIndex, m_compressedData[bufIndex].compressedLength, srcSize);
if (!decompressedLength) { if (!decompressedLength) {
// decompress fail // decompress fail
RELEASE_ASSERT_NOT_REACHED(); RELEASE_ASSERT_NOT_REACHED();
@ -268,12 +299,16 @@ void CompressibleString::decompressWorker()
dstIndex += srcSize; dstIndex += srcSize;
} }
CompressedDataVector().swap(m_compressedData); clearCompressedData();
m_bufferData.bufferAs8Bit = const_cast<const char*>(dstBuffer); m_bufferData.bufferAs8Bit = const_cast<const char*>(dstBuffer);
m_isCompressed = false; m_isCompressed = false;
m_vmInstance->compressibleStringsUncomressedBufferSize() += decomressedBufferSize(); m_vmInstance->compressibleStringsUncomressedBufferSize() += decomressedBufferSize();
ASSERT(originByteLength > totalDecompLength);
m_vmInstance->increaseSourceSize(originByteLength - totalDecompLength);
ESCARGOT_LOG_INFO("DECOMPRESS TICK: %" PRIu64 " Size: %zu\n", fastTickCount(), originByteLength);
} }
} // namespace Escargot } // namespace Escargot

View file

@ -28,6 +28,17 @@ namespace Escargot {
class VMInstance; class VMInstance;
struct CompressedElement {
CompressedElement(char* buffer, size_t length)
: compressedBuffer(buffer)
, compressedLength(length)
{
}
char* compressedBuffer;
size_t compressedLength;
};
class CompressibleString : public String { class CompressibleString : public String {
friend class VMInstance; friend class VMInstance;
@ -67,7 +78,8 @@ public:
if (isCompressed()) { if (isCompressed()) {
decompress(); decompress();
} }
return StringBufferAccessData(m_bufferData.has8BitContent, m_bufferData.length, const_cast<void*>(m_bufferData.buffer));
return StringBufferAccessData(m_bufferData.has8BitContent, m_bufferData.length, const_cast<void*>(m_bufferData.buffer), &m_refCount);
} }
bool isCompressed() bool isCompressed()
@ -75,12 +87,18 @@ public:
return m_isCompressed; return m_isCompressed;
} }
size_t refCount()
{
return m_refCount;
}
void* operator new(size_t); void* operator new(size_t);
void* operator new[](size_t) = delete; void* operator new[](size_t) = delete;
void operator delete[](void*) = delete; void operator delete[](void*) = delete;
static void* allocateStringDataBuffer(size_t byteLength); static void* allocateStringDataBuffer(size_t byteLength);
static void deallocateStringDataBuffer(void* ptr, size_t byteLength); static void deallocateStringDataBuffer(void* ptr, size_t byteLength);
void clearCompressedData();
bool compress(); bool compress();
void decompress(); void decompress();
@ -100,15 +118,16 @@ private:
} }
template <typename StringType> template <typename StringType>
NEVER_INLINE bool compressWorker(void* callerSP); NEVER_INLINE bool compressWorker();
template <typename StringType> template <typename StringType>
NEVER_INLINE void decompressWorker(); NEVER_INLINE void decompressWorker();
bool m_isOwnerMayFreed; bool m_isOwnerMayFreed;
bool m_isCompressed; bool m_isCompressed;
size_t m_refCount; // reference count representing the usage of this CompressibleString
VMInstance* m_vmInstance; VMInstance* m_vmInstance;
uint64_t m_lastUsedTickcount; uint64_t m_lastUsedTickcount;
typedef std::vector<std::vector<char>> CompressedDataVector; typedef Vector<CompressedElement, GCUtil::gc_malloc_allocator<CompressedElement>> CompressedDataVector;
CompressedDataVector m_compressedData; CompressedDataVector m_compressedData;
}; };
} // namespace Escargot } // namespace Escargot

View file

@ -22,6 +22,7 @@
#include "runtime/Platform.h" #include "runtime/Platform.h"
#include "runtime/PointerValue.h" #include "runtime/PointerValue.h"
#include "runtime/ArrayObject.h" #include "runtime/ArrayObject.h"
#include <inttypes.h>
namespace Escargot { namespace Escargot {
@ -34,6 +35,7 @@ SpinLock Global::g_atomicsLock;
std::mutex Global::g_waiterMutex; std::mutex Global::g_waiterMutex;
std::vector<Global::Waiter*> Global::g_waiter; std::vector<Global::Waiter*> Global::g_waiter;
#endif #endif
Global::HeapProfile* Global::g_heapProfile;
void Global::initialize(Platform* platform) void Global::initialize(Platform* platform)
{ {
@ -51,6 +53,8 @@ void Global::initialize(Platform* platform)
PointerValue::g_objectRareDataTag = ObjectRareData(nullptr).getTag(); PointerValue::g_objectRareDataTag = ObjectRareData(nullptr).getTag();
PointerValue::g_doubleInEncodedValueTag = DoubleInEncodedValue(0).getTag(); PointerValue::g_doubleInEncodedValueTag = DoubleInEncodedValue(0).getTag();
g_heapProfile = new HeapProfile();
called_once = false; called_once = false;
inited = true; inited = true;
} }
@ -72,6 +76,9 @@ void Global::finalize()
delete g_platform; delete g_platform;
g_platform = nullptr; g_platform = nullptr;
g_heapProfile->printResult();
delete g_heapProfile;
called_once = false; called_once = false;
inited = false; inited = false;
} }
@ -82,6 +89,19 @@ Platform* Global::platform()
return g_platform; return g_platform;
} }
void Global::HeapProfile::printResult()
{
ESCARGOT_LOG_INFO("=== HeapProfile Result ===\n");
ESCARGOT_LOG_INFO("GC_Count: %zu\nComp_Count: %zu\nDecomp_Count: %zu\n", gcCount, compCount, decompCount);
ESCARGOT_LOG_INFO("GC_Time: %" PRIu64 "\nComp_Time: %" PRIu64 "\nDecomp_Time:%" PRIu64 "\n", gcTime, compTime, decompTime);
}
Global::HeapProfile* Global::heapProfile()
{
ASSERT(inited && !!g_heapProfile);
return g_heapProfile;
}
#if defined(ENABLE_THREADING) #if defined(ENABLE_THREADING)
Global::Waiter* Global::waiter(void* blockAddress) Global::Waiter* Global::waiter(void* blockAddress)
{ {

View file

@ -68,6 +68,50 @@ public:
static std::vector<Waiter*> g_waiter; static std::vector<Waiter*> g_waiter;
static Waiter* waiter(void* blockAddress); static Waiter* waiter(void* blockAddress);
#endif #endif
struct HeapProfile {
HeapProfile()
: gcCount(0)
, compCount(0)
, decompCount(0)
, lastGCMarkTime(0)
, gcTime(0)
, compTime(0)
, decompTime(0)
{
}
size_t gcCount;
size_t compCount;
size_t decompCount;
uint64_t lastGCMarkTime;
uint64_t gcTime;
uint64_t compTime;
uint64_t decompTime;
void printResult();
};
static HeapProfile* g_heapProfile;
static HeapProfile* heapProfile();
// Heap Profile Functions
static void increaseHeapProfileGCCount() { g_heapProfile->gcCount++; }
static void increaseHeapProfileCompCount() { g_heapProfile->compCount++; }
static void increaseHeapProfileDecompCount() { g_heapProfile->decompCount++; }
static void markHeapProfileGCMarkStartTime(uint64_t time)
{
ASSERT(g_heapProfile->lastGCMarkTime == 0);
g_heapProfile->lastGCMarkTime = time;
}
static void markHeapProfileGCReclaimEndTime(uint64_t time)
{
ASSERT(time >= g_heapProfile->lastGCMarkTime);
g_heapProfile->gcTime += (time - g_heapProfile->lastGCMarkTime);
g_heapProfile->lastGCMarkTime = 0;
}
static void addHeapProfileCompTime(uint64_t time) { g_heapProfile->compTime += time; }
static void addHeapProfileDecompTime(uint64_t time) { g_heapProfile->decompTime += time; }
}; };
} // namespace Escargot } // namespace Escargot

View file

@ -52,6 +52,7 @@
#include "fast-dtoa.h" #include "fast-dtoa.h"
#include "bignum-dtoa.h" #include "bignum-dtoa.h"
#include "runtime/VMInstance.h"
namespace Escargot { namespace Escargot {
@ -623,6 +624,32 @@ String* String::fromDouble(double v)
return new ASCIIString(std::move(s)); return new ASCIIString(std::move(s));
} }
UTF16StringFinalizer::UTF16StringFinalizer(Context* context, UTF16StringData&& src)
: UTF16String(std::forward<UTF16StringData>(src))
, m_context(context)
{
context->vmInstance()->increaseSourceSize(length() * 2);
GC_REGISTER_FINALIZER_NO_ORDER(this, [](void* obj, void*) {
UTF16StringFinalizer* self = (UTF16StringFinalizer*)obj;
self->m_context->vmInstance()->decreaseSourceSize((self->length()) * 2);
},
nullptr, nullptr, nullptr);
}
void* UTF16StringFinalizer::operator new(size_t size)
{
static MAY_THREAD_LOCAL bool typeInited = false;
static MAY_THREAD_LOCAL GC_descr descr;
if (!typeInited) {
GC_word obj_bitmap[GC_BITMAP_SIZE(UTF16StringFinalizer)] = { 0 };
GC_set_bit(obj_bitmap, GC_WORD_OFFSET(UTF16StringFinalizer, m_bufferData.buffer));
GC_set_bit(obj_bitmap, GC_WORD_OFFSET(UTF16StringFinalizer, m_context));
descr = GC_make_descriptor(obj_bitmap, GC_WORD_LEN(UTF16StringFinalizer));
typeInited = true;
}
return GC_MALLOC_EXPLICITLY_TYPED(size, descr);
}
String* String::fromUTF8(const char* src, size_t len, bool maybeASCII) String* String::fromUTF8(const char* src, size_t len, bool maybeASCII)
{ {
if (maybeASCII && isAllASCII(src, len)) { if (maybeASCII && isAllASCII(src, len)) {
@ -637,6 +664,7 @@ String* String::fromUTF8(const char* src, size_t len, bool maybeASCII)
String* String::fromUTF8ToCompressibleString(VMInstance* instance, const char* src, size_t len, bool maybeASCII) String* String::fromUTF8ToCompressibleString(VMInstance* instance, const char* src, size_t len, bool maybeASCII)
{ {
if (maybeASCII && isAllASCII(src, len)) { if (maybeASCII && isAllASCII(src, len)) {
RELEASE_ASSERT_NOT_REACHED();
return new CompressibleString(instance, src, len); return new CompressibleString(instance, src, len);
} else { } else {
auto s = utf8StringToUTF16StringNonGC(src, len); auto s = utf8StringToUTF16StringNonGC(src, len);
@ -927,6 +955,32 @@ void* Latin1String::operator new(size_t size)
return GC_MALLOC_EXPLICITLY_TYPED(size, descr); return GC_MALLOC_EXPLICITLY_TYPED(size, descr);
} }
Latin1StringFinalizer::Latin1StringFinalizer(Context* context, const LChar* str, size_t len)
: Latin1String(str, len)
, m_context(context)
{
context->vmInstance()->increaseSourceSize(length());
GC_REGISTER_FINALIZER_NO_ORDER(this, [](void* obj, void*) {
Latin1StringFinalizer* self = (Latin1StringFinalizer*)obj;
self->m_context->vmInstance()->decreaseSourceSize(self->length());
},
nullptr, nullptr, nullptr);
}
void* Latin1StringFinalizer::operator new(size_t size)
{
static MAY_THREAD_LOCAL bool typeInited = false;
static MAY_THREAD_LOCAL GC_descr descr;
if (!typeInited) {
GC_word obj_bitmap[GC_BITMAP_SIZE(Latin1StringFinalizer)] = { 0 };
GC_set_bit(obj_bitmap, GC_WORD_OFFSET(Latin1StringFinalizer, m_bufferData.buffer));
GC_set_bit(obj_bitmap, GC_WORD_OFFSET(Latin1StringFinalizer, m_context));
descr = GC_make_descriptor(obj_bitmap, GC_WORD_LEN(Latin1StringFinalizer));
typeInited = true;
}
return GC_MALLOC_EXPLICITLY_TYPED(size, descr);
}
void* UTF16String::operator new(size_t size) void* UTF16String::operator new(size_t size)
{ {
static MAY_THREAD_LOCAL bool typeInited = false; static MAY_THREAD_LOCAL bool typeInited = false;

View file

@ -27,6 +27,8 @@
namespace Escargot { namespace Escargot {
class Context;
// A type to hold a single Latin-1 character. // A type to hold a single Latin-1 character.
typedef unsigned char LChar; typedef unsigned char LChar;
@ -100,12 +102,27 @@ struct StringBufferAccessData {
}; };
void* extraData; void* extraData;
StringBufferAccessData(bool has8Bit, size_t len, void* buffer, void* extraDataKeepInStack = nullptr) StringBufferAccessData(bool has8Bit, size_t len, void* buffer, void* extraDataToKeep = nullptr)
: has8BitContent(has8Bit) : has8BitContent(has8Bit)
, length(len) , length(len)
, buffer(buffer) , buffer(buffer)
, extraData(extraDataKeepInStack) , extraData(extraDataToKeep)
{ {
if (extraData) {
// increase refCount in CompressibleString
size_t& count = *reinterpret_cast<size_t*>(extraData);
count++;
}
}
~StringBufferAccessData()
{
if (extraData) {
// decrease refCount in CompressibleString
size_t& count = *reinterpret_cast<size_t*>(extraData);
ASSERT(count > 0);
count--;
}
} }
char16_t uncheckedCharAtFor8Bit(size_t idx) const char16_t uncheckedCharAtFor8Bit(size_t idx) const
@ -759,6 +776,16 @@ public:
void* operator new[](size_t size) = delete; void* operator new[](size_t size) = delete;
}; };
class Latin1StringFinalizer : public Latin1String {
public:
Latin1StringFinalizer(Context* context, const LChar* str, size_t len);
Context* m_context;
void* operator new(size_t size);
void* operator new[](size_t size) = delete;
};
class Latin1StringFromExternalMemory : public Latin1String { class Latin1StringFromExternalMemory : public Latin1String {
public: public:
Latin1StringFromExternalMemory(const unsigned char* str, size_t len) Latin1StringFromExternalMemory(const unsigned char* str, size_t len)
@ -825,6 +852,16 @@ public:
void* operator new[](size_t size) = delete; void* operator new[](size_t size) = delete;
}; };
class UTF16StringFinalizer : public UTF16String {
public:
explicit UTF16StringFinalizer(Context* context, UTF16StringData&& src);
Context* m_context;
void* operator new(size_t size);
void* operator new[](size_t size) = delete;
};
class UTF16StringFromExternalMemory : public UTF16String { class UTF16StringFromExternalMemory : public UTF16String {
public: public:
UTF16StringFromExternalMemory(const char16_t* str, size_t len) UTF16StringFromExternalMemory(const char16_t* str, size_t len)

View file

@ -139,9 +139,6 @@ protected:
ASSERT(m_bufferData.hasSpecialImpl); ASSERT(m_bufferData.hasSpecialImpl);
StringBufferAccessData r = m_bufferData.bufferAsString->bufferAccessData(); StringBufferAccessData r = m_bufferData.bufferAsString->bufferAccessData();
// keep original buffer pointer in stack
// without this, compressible string can free this pointer
r.extraData = const_cast<void*>(r.buffer);
r.length = m_bufferData.length; r.length = m_bufferData.length;
if (r.has8BitContent) { if (r.has8BitContent) {
r.bufferAs8Bit += m_start; r.bufferAs8Bit += m_start;

View file

@ -206,6 +206,8 @@ void* VMInstance::operator new(size_t size)
void vmMarkStartCallback(void* data) void vmMarkStartCallback(void* data)
{ {
VMInstance* self = (VMInstance*)data; VMInstance* self = (VMInstance*)data;
Global::increaseHeapProfileGCCount();
Global::markHeapProfileGCMarkStartTime(fastTickCount());
#ifdef ESCARGOT_DEBUGGER #ifdef ESCARGOT_DEBUGGER
if (!self->m_debuggerEnabled) { if (!self->m_debuggerEnabled) {
@ -266,6 +268,11 @@ void vmReclaimEndCallback(void* data)
} }
} }
Global::markHeapProfileGCReclaimEndTime(fastTickCount());
// disabled in default due to massive logs
//ESCARGOT_LOG_INFO("Heap After GC: %f MB\n", GC_get_heap_size() / 1024.f / 1024.f);
/* /*
if (t == GC_EventType::GC_EVENT_RECLAIM_END) { if (t == GC_EventType::GC_EVENT_RECLAIM_END) {
printf("Done GC: HeapSize: [%f MB , %f MB]\n", GC_get_memory_use() / 1024.f / 1024.f, GC_get_heap_size() / 1024.f / 1024.f); printf("Done GC: HeapSize: [%f MB , %f MB]\n", GC_get_memory_use() / 1024.f / 1024.f, GC_get_heap_size() / 1024.f / 1024.f);
@ -342,6 +349,7 @@ VMInstance::VMInstance(const char* locale, const char* timezone, const char* bas
, m_debuggerEnabled(false) , m_debuggerEnabled(false)
#endif /* ESCARGOT_DEBUGGER */ #endif /* ESCARGOT_DEBUGGER */
, m_compiledByteCodeSize(0) , m_compiledByteCodeSize(0)
, m_validSourceSize(0)
#if defined(ENABLE_COMPRESSIBLE_STRING) #if defined(ENABLE_COMPRESSIBLE_STRING)
, m_lastCompressibleStringsTestTime(0) , m_lastCompressibleStringsTestTime(0)
, m_compressibleStringsUncomressedBufferSize(0) , m_compressibleStringsUncomressedBufferSize(0)

View file

@ -185,6 +185,22 @@ public:
return m_compiledByteCodeSize; return m_compiledByteCodeSize;
} }
size_t validSourceSize()
{
return m_validSourceSize;
}
void increaseSourceSize(size_t size)
{
m_validSourceSize += size;
}
void decreaseSourceSize(size_t size)
{
ASSERT(m_validSourceSize >= size);
m_validSourceSize -= size;
}
#if defined(ENABLE_COMPRESSIBLE_STRING) #if defined(ENABLE_COMPRESSIBLE_STRING)
std::vector<CompressibleString*>& compressibleStrings() std::vector<CompressibleString*>& compressibleStrings()
{ {
@ -328,6 +344,7 @@ private:
std::vector<ByteCodeBlock*> m_compiledByteCodeBlocks; std::vector<ByteCodeBlock*> m_compiledByteCodeBlocks;
size_t m_compiledByteCodeSize; size_t m_compiledByteCodeSize;
size_t m_validSourceSize;
#if defined(ENABLE_COMPRESSIBLE_STRING) #if defined(ENABLE_COMPRESSIBLE_STRING)
uint64_t m_lastCompressibleStringsTestTime; uint64_t m_lastCompressibleStringsTestTime;

View file

@ -32,9 +32,13 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#include <time.h>
#include "api/EscargotPublic.h" #include "api/EscargotPublic.h"
#include "malloc.h" #include "malloc.h"
#include <inttypes.h>
#if defined(ESCARGOT_ENABLE_TEST) #if defined(ESCARGOT_ENABLE_TEST)
// these header & function below are used for Escargot internal development // these header & function below are used for Escargot internal development
// general client doesn't need this // general client doesn't need this
@ -188,24 +192,27 @@ static OptionalRef<StringRef> builtinHelperFileRead(OptionalRef<ExecutionStateRe
} }
fclose(fp); fclose(fp);
if (StringRef::isCompressibleStringEnabled()) { if (StringRef::isCompressibleStringEnabled()) {
if (state) { //if (state) {
if (hasNonLatin1Content) { if (hasNonLatin1Content) {
src = StringRef::createFromUTF8ToCompressibleString(state->context()->vmInstance(), utf8Str.data(), utf8Str.length(), false); src = StringRef::createFromUTF8ToCompressibleString(state->context()->vmInstance(), utf8Str.data(), utf8Str.length(), false);
} else {
src = StringRef::createFromLatin1ToCompressibleString(state->context()->vmInstance(), str.data(), str.length());
}
} else { } else {
src = StringRef::createFromLatin1ToCompressibleString(state->context()->vmInstance(), str.data(), str.length());
}
//}
/*
else {
if (hasNonLatin1Content) { if (hasNonLatin1Content) {
src = StringRef::createFromUTF8(utf8Str.data(), utf8Str.length(), false); src = StringRef::createFromUTF8(utf8Str.data(), utf8Str.length(), false);
} else { } else {
src = StringRef::createFromLatin1(str.data(), str.length()); src = StringRef::createFromLatin1(str.data(), str.length());
} }
} }
*/
} else { } else {
if (hasNonLatin1Content) { if (hasNonLatin1Content) {
src = StringRef::createFromUTF8(utf8Str.data(), utf8Str.length(), false); src = StringRef::createFromUTF8(state->context(), utf8Str.data(), utf8Str.length(), false);
} else { } else {
src = StringRef::createFromLatin1(str.data(), str.length()); src = StringRef::createFromLatin1(state->context(), str.data(), str.length());
} }
} }
return src; return src;
@ -921,6 +928,30 @@ PersistentRefHolder<ContextRef> createEscargotContext(VMInstanceRef* instance, b
return context; return context;
} }
static uint64_t g_lastTick = 0;
static uint64_t g_dumpInterval = 1000; // default interval is set to 1 second
static uint64_t fastTickCount()
{
#if defined(CLOCK_MONOTONIC_COARSE)
timespec ts;
clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
return (uint64_t)ts.tv_sec * 1000UL + ts.tv_nsec / 1000000UL;
#else
return tickCount();
#endif
}
static void printGCMemory(void* data)
{
uint64_t currentTick = fastTickCount();
if (currentTick - g_lastTick >= g_dumpInterval) {
g_lastTick = currentTick;
VMInstanceRef* vm = static_cast<VMInstanceRef*>(data);
printf("Tick %" PRIu64 ": %f MB %lu \n", currentTick, Memory::heapSize() / 1024.f / 1024.f, vm->validSourceSize());
}
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
#ifndef NDEBUG #ifndef NDEBUG
@ -941,7 +972,7 @@ int main(int argc, char* argv[])
Memory::setGCFrequency(24); Memory::setGCFrequency(24);
PersistentRefHolder<VMInstanceRef> instance = VMInstanceRef::create(); PersistentRefHolder<VMInstanceRef> instance = VMInstanceRef::create();
PersistentRefHolder<ContextRef> context = createEscargotContext(instance.get()); PersistentRefHolder<ContextRef> context;
if (getenv("GC_FREE_SPACE_DIVISOR") && strlen(getenv("GC_FREE_SPACE_DIVISOR"))) { if (getenv("GC_FREE_SPACE_DIVISOR") && strlen(getenv("GC_FREE_SPACE_DIVISOR"))) {
int d = atoi(getenv("GC_FREE_SPACE_DIVISOR")); int d = atoi(getenv("GC_FREE_SPACE_DIVISOR"));
@ -951,6 +982,7 @@ int main(int argc, char* argv[])
bool runShell = true; bool runShell = true;
bool seenModule = false; bool seenModule = false;
std::string fileName; std::string fileName;
bool memoryUsageLog = false;
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
if (strlen(argv[i]) >= 2 && argv[i][0] == '-') { // parse command line option if (strlen(argv[i]) >= 2 && argv[i][0] == '-') { // parse command line option
@ -971,32 +1003,20 @@ int main(int argc, char* argv[])
fileName = argv[i] + sizeof("--filename-as=") - 1; fileName = argv[i] + sizeof("--filename-as=") - 1;
continue; continue;
} }
if (strcmp(argv[i], "--start-debug-server") == 0) { if (strcmp(argv[i], "--dump-memory-usage") == 0) {
context->initDebugger(nullptr); memoryUsageLog = true;
g_lastTick = fastTickCount();
printf("Tick %" PRIu64 ": Start Measure\n", g_lastTick);
Memory::removeGCEventListener(Memory::RECLAIM_END, printGCMemory, instance.get());
Memory::addGCEventListener(Memory::RECLAIM_END, printGCMemory, instance.get());
continue; continue;
} }
if (strcmp(argv[i], "--debugger-wait-source") == 0) { if (strstr(argv[i], "--dump-memory-interval=") == argv[i]) {
StringRef* sourceName; std::string interval = argv[i] + sizeof("--dump-memory-interval=") - 1;
while (true) { g_dumpInterval = atoi(interval.c_str());
StringRef* clientSourceRef = context->getClientSource(&sourceName);
if (!clientSourceRef) {
break;
}
if (!evalScript(context, clientSourceRef, sourceName, false, false))
return 3;
runShell = false;
}
continue; continue;
} }
} else { // `-option` case } else { // `-option` case
if (strcmp(argv[i], "-e") == 0) {
runShell = false;
i++;
StringRef* src = StringRef::createFromUTF8(argv[i], strlen(argv[i]));
if (!evalScript(context, src, StringRef::createFromASCII("shell input"), false, false))
return 3;
continue;
}
if (strcmp(argv[i], "-f") == 0) { if (strcmp(argv[i], "-f") == 0) {
continue; continue;
} }
@ -1006,6 +1026,13 @@ int main(int argc, char* argv[])
continue; continue;
} }
if (!context.isInitialized()) {
// postpone allocation of Context to here
// initialization of Context triggers several GC
// so, register of gc event with `--dump-memory-usage` should be done before Context creation
context.reset(createEscargotContext(instance.get()));
}
FILE* fp = fopen(argv[i], "r"); FILE* fp = fopen(argv[i], "r");
if (fp) { if (fp) {
fclose(fp); fclose(fp);
@ -1024,6 +1051,7 @@ int main(int argc, char* argv[])
if (!evalScript(context, src, StringRef::createFromUTF8(fileName.data(), fileName.length()), false, seenModule)) { if (!evalScript(context, src, StringRef::createFromUTF8(fileName.data(), fileName.length()), false, seenModule)) {
return 3; return 3;
} }
seenModule = false; seenModule = false;
fileName.clear(); fileName.clear();
} else { } else {
@ -1076,6 +1104,8 @@ int main(int argc, char* argv[])
instance.release(); instance.release();
Globals::finalize(); Globals::finalize();
if (memoryUsageLog) {
printf("Tick %" PRIu64 ": End Measure\n", fastTickCount());
}
return 0; return 0;
} }