diff --git a/src/checker/MemoryPool.h b/src/checker/MemoryPool.h index ab1ed77..da52cdd 100644 --- a/src/checker/MemoryPool.h +++ b/src/checker/MemoryPool.h @@ -1,11 +1,12 @@ #pragma once +#include #include #include #include #include -template +template class MemoryPool { public: /* Member types */ @@ -15,237 +16,177 @@ class MemoryPool { typedef const T *const_pointer; typedef const T &const_reference; typedef size_t size_type; - typedef ptrdiff_t difference_type; - typedef std::false_type propagate_on_container_copy_assignment; - typedef std::true_type propagate_on_container_move_assignment; - typedef std::true_type propagate_on_container_swap; - - template - struct rebind { - typedef MemoryPool other; + + union Slot { + value_type element; + struct Pointer { + Slot *prev; + Slot *next; + } pointer; }; + typedef char *data_pointer; + typedef Slot slot_type; + typedef Slot *slot_pointer; + /* Member functions */ - MemoryPool() noexcept; - MemoryPool(const MemoryPool &memoryPool) noexcept; - MemoryPool(MemoryPool &&memoryPool) noexcept; - template - MemoryPool(const MemoryPool &memoryPool) noexcept; + MemoryPool() noexcept { + currentBlock = nullptr; + firstBlock = nullptr; + currentSlot = nullptr; + lastSlot = nullptr; + freeSlots = nullptr; + } - ~MemoryPool() noexcept; + MemoryPool(const MemoryPool &memoryPool) noexcept: MemoryPool() {} + MemoryPool(MemoryPool &&memoryPool) noexcept { + currentBlock = memoryPool.currentBlock; + memoryPool.currentBlock = nullptr; + currentSlot = memoryPool.currentSlot; + firstBlock = memoryPool.firstBlock; + lastSlot = memoryPool.lastSlot; + freeSlots = memoryPool.freeSlots; + } + + template + MemoryPool(const MemoryPool &memoryPool) noexcept: MemoryPool() {} + + ~MemoryPool() noexcept { + slot_pointer curr = currentBlock; + while (curr != nullptr) { + slot_pointer prev = curr->pointer.prev; + operator delete(reinterpret_cast(curr)); + curr = prev; + } + } unsigned int active = 0; + unsigned int blocks = 0; MemoryPool &operator=(const MemoryPool &memoryPool) = delete; - MemoryPool &operator=(MemoryPool &&memoryPool) noexcept; + MemoryPool &operator=(MemoryPool &&memoryPool) noexcept { + if (this != &memoryPool) { + std::swap(currentBlock, memoryPool.currentBlock); + currentSlot = memoryPool.currentSlot; + lastSlot = memoryPool.lastSlot; + firstBlock = memoryPool.firstBlock; + freeSlots = memoryPool.freeSlots; + } + return *this; + } - pointer address(reference x) const noexcept; - const_pointer address(const_reference x) const noexcept; + pointer address(reference x) const noexcept { + return &x; + } + + const_pointer address(const_reference x) const noexcept { + return &x; + } // Can only allocate one object at a time. n and hint are ignored - pointer allocate(size_type n = 1, const_pointer hint = 0); - void deallocate(pointer p, size_type n = 1); + pointer allocate() { + active++; + if (freeSlots != nullptr) { + pointer result = reinterpret_cast(freeSlots); + freeSlots = freeSlots->pointer.next; + return result; + } else { + if (currentSlot>=lastSlot) { + allocateBlock(); + } + return reinterpret_cast(currentSlot++); + } + } - // Schedules for garbage collection - void gc(pointer p); + void deallocate(pointer p) { + if (p != nullptr) { + active--; + reinterpret_cast(p)->pointer = {.prev = nullptr, .next = freeSlots}; + freeSlots = reinterpret_cast(p); + } + } - size_type max_size() const noexcept; + size_type max_size() const noexcept { + size_type maxBlocks = -1 / BlockSize; + return (BlockSize - sizeof(data_pointer)) / sizeof(slot_type) * maxBlocks; + } template - void construct(U *p, Args &&... args); + void construct(U *p, Args &&... args) { + new(p) U(std::forward(args)...); + } template - void destroy(U *p); + void destroy(U *p) { + p->~U(); + } template - pointer newElement(Args &&... args); - void deleteElement(pointer p); + pointer newElement(Args &&... args) { + pointer result = allocate(); + construct(result, std::forward(args)...); + return result; + } + + void deleteElement(pointer p) { + if (p != nullptr) { + p->~value_type(); + deallocate(p); + } + } + void clear() { + active = 0; + freeSlots = nullptr; + if (firstBlock) initializeBlock(firstBlock); + } private: - union Slot_ { - value_type element; - Slot_ *next; - }; + slot_pointer currentBlock; + slot_pointer firstBlock; - typedef char *data_pointer_; - typedef Slot_ slot_type_; - typedef Slot_ *slot_pointer_; - - slot_pointer_ currentBlock_; - slot_pointer_ currentSlot_; - slot_pointer_ lastSlot_; - slot_pointer_ freeSlots_; - - size_type padPointer(data_pointer_ p, size_type align) const noexcept; - void allocateBlock(); - - static_assert(BlockSize >= 2 * sizeof(slot_type_), "BlockSize too small."); -}; - -template -inline typename MemoryPool::size_type -MemoryPool::padPointer(data_pointer_ p, size_type align) -const noexcept { - uintptr_t result = reinterpret_cast(p); - return ((align - result) % align); -} - -template -MemoryPool::MemoryPool() -noexcept { - currentBlock_ = nullptr; - currentSlot_ = nullptr; - lastSlot_ = nullptr; - freeSlots_ = nullptr; -} - -template -MemoryPool::MemoryPool(const MemoryPool &memoryPool) -noexcept : - MemoryPool() {} - -template -MemoryPool::MemoryPool(MemoryPool &&memoryPool) -noexcept { - currentBlock_ = memoryPool.currentBlock_; - memoryPool.currentBlock_ = nullptr; - currentSlot_ = memoryPool.currentSlot_; - lastSlot_ = memoryPool.lastSlot_; - freeSlots_ = memoryPool.freeSlots; -} - -template -template -MemoryPool::MemoryPool(const MemoryPool &memoryPool) -noexcept : - MemoryPool() {} - -template -MemoryPool & -MemoryPool::operator=(MemoryPool &&memoryPool) -noexcept { - if (this != &memoryPool) { - std::swap(currentBlock_, memoryPool.currentBlock_); - currentSlot_ = memoryPool.currentSlot_; - lastSlot_ = memoryPool.lastSlot_; - freeSlots_ = memoryPool.freeSlots_; - } - return *this; -} - -template -MemoryPool::~MemoryPool() -noexcept { - slot_pointer_ curr = currentBlock_; - while (curr != nullptr) { - slot_pointer_ prev = curr->next; - operator delete(reinterpret_cast(curr)); - curr = prev; - } -} - -template -inline typename MemoryPool::pointer -MemoryPool::address(reference x) -const noexcept { - return &x; -} - -template -inline typename MemoryPool::const_pointer -MemoryPool::address(const_reference x) -const noexcept { - return &x; -} - -template -void -MemoryPool::allocateBlock() { - // Allocate space for the new block and store a pointer to the previous one - data_pointer_ newBlock = reinterpret_cast - (operator new(BlockSize)); - reinterpret_cast(newBlock)->next = currentBlock_; - currentBlock_ = reinterpret_cast(newBlock); - // Pad block body to staisfy the alignment requirements for elements - data_pointer_ body = newBlock + sizeof(slot_pointer_); - size_type bodyPadding = padPointer(body, alignof(slot_type_)); - currentSlot_ = reinterpret_cast(body + bodyPadding); - lastSlot_ = reinterpret_cast - (newBlock + BlockSize - sizeof(slot_type_) + 1); -} - -template -inline typename MemoryPool::pointer -MemoryPool::allocate(size_type n, const_pointer hint) { - active++; - if (freeSlots_ != nullptr) { - pointer result = reinterpret_cast(freeSlots_); - freeSlots_ = freeSlots_->next; - return result; - } else { - if (currentSlot_ >= lastSlot_) { - allocateBlock(); + slot_pointer currentSlot; + slot_pointer lastSlot; + slot_pointer freeSlots; + + size_type padPointer(data_pointer p, size_type align) const noexcept { + uintptr_t result = reinterpret_cast(p); + return ((align - result) % align); + } + + void allocateBlock() { + if (currentBlock && reinterpret_cast(currentBlock)->pointer.next) { + initializeBlock(reinterpret_cast(currentBlock)->pointer.next); + } else { + blocks++; + // Allocate space for the new block and store a pointer to the previous one + data_pointer newBlock = reinterpret_cast(operator new(BlockSize)); + reinterpret_cast(newBlock)->pointer = {.prev = currentBlock, .next = nullptr}; + setNextBlock(reinterpret_cast(newBlock)); } - return reinterpret_cast(currentSlot_++); - } -} - -template -inline void -MemoryPool::gc(pointer p) { - deallocate(p); -// if (p != nullptr) { -// reinterpret_cast(p)->next = freeSlots_; -// freeSlots_ = reinterpret_cast(p); -// } -} - -template -inline void -MemoryPool::deallocate(pointer p, size_type n) { - active--; - if (p != nullptr) { - reinterpret_cast(p)->next = freeSlots_; - freeSlots_ = reinterpret_cast(p); - } -} - -template -inline typename MemoryPool::size_type -MemoryPool::max_size() -const noexcept { - size_type maxBlocks = -1 / BlockSize; - return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks; -} - -template -template -inline void -MemoryPool::construct(U *p, Args &&... args) { - new(p) U(std::forward(args)...); -} - -template -template -inline void -MemoryPool::destroy(U *p) { - p->~U(); -} - -template -template -inline typename MemoryPool::pointer -MemoryPool::newElement(Args &&... args) { - pointer result = allocate(); - construct(result, std::forward(args)...); - return result; -} - -template -inline void -MemoryPool::deleteElement(pointer p) { - if (p != nullptr) { - p->~value_type(); - deallocate(p); - } -} \ No newline at end of file + } + + void setNextBlock(slot_pointer nextBlock) { + if (currentBlock) currentBlock->pointer.next = nextBlock; + if (!firstBlock) firstBlock = nextBlock; + initializeBlock(nextBlock); + } + + slot_pointer blockStartSlot(slot_pointer block) { + auto blockPoint = reinterpret_cast(block); + // Pad block body to satisfy the alignment requirements for elements + //data_pointer body = blockPoint + sizeof(slot_pointer); + //size_type bodyPadding = padPointer(body, alignof(slot_type)); + return reinterpret_cast(blockPoint + sizeof(slot_type)); + } + + void initializeBlock(slot_pointer nextBlock) { + currentBlock = nextBlock; + //// Pad block body to satisfy the alignment requirements for elements + //data_pointer_ body = newBlock + sizeof(slot_pointer_); + //size_type bodyPadding = padPointer(body, alignof(slot_type_)); + currentSlot = blockStartSlot(nextBlock); // reinterpret_cast(body + bodyPadding); + lastSlot = reinterpret_cast(reinterpret_cast(nextBlock) + BlockSize - sizeof(slot_type) + 1); + } + + static_assert(BlockSize>=2 * sizeof(slot_type), "BlockSize too small."); +}; \ No newline at end of file diff --git a/src/checker/debug.h b/src/checker/debug.h index 6a0e61b..b13c7d2 100644 --- a/src/checker/debug.h +++ b/src/checker/debug.h @@ -40,14 +40,14 @@ namespace ts::checker { unsigned int storageEnd = 0; bool newSubRoutine = false; DebugBinResult result; - if (print) fmt::print("Bin {} bytes: ", bin.size()); + if (print) std::cout << fmt::format("Bin {} bytes: ", bin.size()); for (unsigned int i = 0; i < end; i++) { if (storageEnd) { while (i < storageEnd) { auto size = vm::readUint16(bin, i + 8); auto data = bin.substr(i + 8 + 2, size); - if (print) fmt::print("(Storage ({})\"{}\") ", size, data); + if (print) std::cout << fmt::format("(Storage ({})\"{}\") ", size, data); result.storages.push_back(string(data)); i += 8 + 2 + size; } @@ -60,7 +60,7 @@ namespace ts::checker { unsigned int j = 0; for (auto &&r: result.subroutines) { if (r.address == i) { - if (print) fmt::print("\n&{} {}(): ", j, r.name); + if (print) std::cout << fmt::format("\n&{} {}(): ", j, r.name); result.activeSubroutine = &r; found = true; break; @@ -68,7 +68,7 @@ namespace ts::checker { j++; } if (!found) { - if (print) fmt::print("\nunknown!(): "); + if (print) std::cout << fmt::format("\nunknown!(): "); } newSubRoutine = false; } @@ -189,7 +189,7 @@ namespace ts::checker { std::cout << "[" << startI << "](" << text << ") "; } } - if (print) fmt::print("\n"); + if (print) std::cout << "\n"; return result; } diff --git a/src/checker/vm2.cpp b/src/checker/vm2.cpp index 7589a78..45723f1 100644 --- a/src/checker/vm2.cpp +++ b/src/checker/vm2.cpp @@ -528,7 +528,7 @@ namespace ts::vm2 { case OP::Assign: { auto rvalue = pop(); auto lvalue = pop(); -// debug("assign {} = {}", stringify(rvalue), stringify(lvalue)); + debug("assign {} = {}", stringify(rvalue), stringify(lvalue)); if (!extends(lvalue, rvalue)) { // auto error = stack.errorMessage(); // error.ip = ip; @@ -915,14 +915,14 @@ namespace ts::vm2 { } else if (T->kind == TypeKind::Tuple) { //type T = [y, z]; //type New = [...T, x]; => [y, z, x]; -// auto length = refLength((TypeRef *) T->type); -// debug("...T of size {} with {} users *{}", length, T->users, (void *) T); + auto length = refLength((TypeRef *) T->type); + debug("...T of size {} with refCount={} *{}", length, T->refCount, (void *) T); //if type has no owner, we can just use it as the new type //T.users is minimum 1, because the T is owned by Rest, and Rest owned by TupleMember, and TupleMember by nobody, //if T comes from a type argument, it is 2 since argument belongs to the caller. //thus an expression of [...T] yields always T.users >= 1. - if (T->refCount == 1 && firstType->flag & TypeFlag::RestReuse && !(firstType->flag & TypeFlag::Stored)) { + if (T->refCount == 2 && firstType->flag & TypeFlag::RestReuse && !(firstType->flag & TypeFlag::Stored)) { item = use(T); } else { item = allocate(TypeKind::Tuple); @@ -949,6 +949,7 @@ namespace ts::vm2 { } //drop Rest operator, since it was consumed now, so its resources are freed. //the tuple member has users=0 in a [...T] operation, so it also GCs its REST type. + //decreases T->refCount gc(firstTupleMember); } else { item = allocate(TypeKind::Tuple); diff --git a/src/checker/vm2.h b/src/checker/vm2.h index 530ac98..3adfbc8 100644 --- a/src/checker/vm2.h +++ b/src/checker/vm2.h @@ -31,7 +31,7 @@ namespace ts::vm2 { // MemoryPool propertySignature; // }; - constexpr auto poolSize = sizeof(Type) * 2048; + constexpr auto poolSize = 10000; inline MemoryPool pool; inline MemoryPool poolRef; void gcFlush(); @@ -167,8 +167,10 @@ namespace ts::vm2 { static void run(shared module) { // profiler.clear(); - pool = MemoryPool(); - poolRef = MemoryPool(); +// pool = MemoryPool(); +// poolRef = MemoryPool(); + pool.clear(); + poolRef.clear(); gcQueueIdx = 0; gcQueueRefIdx = 0; diff --git a/src/core.h b/src/core.h index c3d5f9c..7a352ca 100644 --- a/src/core.h +++ b/src/core.h @@ -251,7 +251,7 @@ namespace ts { inline void bench(string title, int iterations, const function &callback) { auto took = benchRun(iterations, callback); - fmt::print("{} {} iterations took {:.9f}ms, {:.9f}ms per iteration\n", title, iterations, took.count(), took.count()/iterations); + std::cout << fmt::format("{} {} iterations took {:.9f}ms, {:.9f}ms per iteration\n", title, iterations, took.count(), took.count()/iterations); } inline void bench(int iterations, const function &callback) { diff --git a/src/tests/CmakeLists.txt b/src/tests/CmakeLists.txt index 22a3249..088589c 100644 --- a/src/tests/CmakeLists.txt +++ b/src/tests/CmakeLists.txt @@ -2,19 +2,17 @@ cmake_minimum_required(VERSION 3.10) project(Tests) -Include(FetchContent) -FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.11.0 -) -# For Windows: Prevent overriding the parent project's compiler/linker settings -#set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -#include_directories(${}) - - +#Include(FetchContent) +#FetchContent_Declare( +# googletest +# GIT_REPOSITORY https://github.com/google/googletest.git +# GIT_TAG release-1.11.0 +#) +## For Windows: Prevent overriding the parent project's compiler/linker settings +##set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +#FetchContent_MakeAvailable(googletest) +# +##include_directories(${}) #project(Tests) @@ -50,20 +48,34 @@ FetchContent_MakeAvailable(googletest) ##link_libraries(PRIVATE Catch2::Catch2WithMain) ##link_libraries(typescript) + +Include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v2.13.9 # or a later release +) + +FetchContent_MakeAvailable(Catch2) + file(GLOB TESTS test*.cpp) #add_executable(Tests_run test_core.cpp) #target_link_libraries(Tests_run gtest gtest_main typescript) -foreach(file ${TESTS}) +foreach (file ${TESTS}) get_filename_component(name ${file} NAME_WE) MESSAGE("Test found typescript_${name}.") + MESSAGE("Catch2_SOURCE_DIR ${Catch2_SOURCE_DIR}") add_executable(typescript_${name} ${file}) -# target_link_libraries(typescript_${name} PUBLIC Tracy::TracyClient) + # target_link_libraries(typescript_${name} PUBLIC Tracy::TracyClient) # target_link_libraries(typescript_${name} gtest_main) - target_link_libraries(typescript_${name} gtest gtest_main typescript) -# target_link_libraries(typescript_${name} PRIVATE Catch2::Catch2WithMain) -# target_link_libraries(typescript_${name} PRIVATE typescript) -endforeach() + target_include_directories(typescript_${name} PUBLIC ${Catch2_SOURCE_DIR}/single_include) + target_link_libraries(typescript_${name} PRIVATE Catch2::Catch2 typescript ) +# target_link_libraries(typescript_${name} typescript) + # target_link_libraries(typescript_${name} PRIVATE Catch2::Catch2WithMain) + # target_link_libraries(typescript_${name} PRIVATE typescript) +endforeach () diff --git a/src/tests/test_allocator.cpp b/src/tests/test_allocator.cpp new file mode 100644 index 0000000..5f0a874 --- /dev/null +++ b/src/tests/test_allocator.cpp @@ -0,0 +1,160 @@ +#define CATCH_CONFIG_MAIN + +#include +#include +#include "../checker/MemoryPool.h" +#include + +struct Item { + std::string_view title; + unsigned int i; + std::vector jopp; +}; + +TEST_CASE("allocator1") { + MemoryPool pool; + REQUIRE(pool.blocks == 0); + REQUIRE(pool.active == 0); + + auto p1 = pool.allocate(); + REQUIRE(pool.active == 1); + REQUIRE(pool.blocks == 1); + + auto p2 = pool.allocate(); + REQUIRE(pool.blocks == 1); + REQUIRE(pool.active == 2); + + pool.deallocate(p1); + REQUIRE(pool.blocks == 1); + REQUIRE(pool.active == 1); + + pool.deallocate(p2); + REQUIRE(pool.blocks == 1); + REQUIRE(pool.active == 0); + + auto p3 = pool.allocate(); + REQUIRE(pool.active == 1); + REQUIRE(pool.blocks == 1); + + auto p4 = pool.allocate(); + REQUIRE(pool.blocks == 1); + REQUIRE(pool.active == 2); +} + +TEST_CASE("allocator2") { + MemoryPool pool; + auto p1 = pool.allocate(); + auto p2 = pool.allocate(); + REQUIRE(pool.active == 2); + REQUIRE(pool.blocks == 1); + + auto p3 = pool.allocate(); + REQUIRE(pool.active == 3); + REQUIRE(pool.blocks == 2); + + auto p4 = pool.allocate(); + REQUIRE(pool.active == 4); + REQUIRE(pool.blocks == 2); + + auto p5 = pool.allocate(); + REQUIRE(pool.active == 5); + REQUIRE(pool.blocks == 3); +} + +TEST_CASE("allocator3") { + MemoryPool pool; + { + auto p1 = pool.allocate(); + auto p2 = pool.allocate(); + REQUIRE(pool.active == 2); + REQUIRE(pool.blocks == 1); + + auto p3 = pool.allocate(); + REQUIRE(pool.active == 3); + REQUIRE(pool.blocks == 2); + + auto p4 = pool.allocate(); + REQUIRE(pool.active == 4); + REQUIRE(pool.blocks == 2); + } + + { + pool.clear(); + auto p1 = pool.allocate(); + REQUIRE(pool.active == 1); + REQUIRE(pool.blocks == 2); + + auto p2 = pool.allocate(); + REQUIRE(pool.active == 2); + REQUIRE(pool.blocks == 2); + + //now block 2 should be reused + auto p3 = pool.allocate(); + REQUIRE(pool.active == 3); + REQUIRE(pool.blocks == 2); + + //now block 2 should be reused + auto p4 = pool.allocate(); + REQUIRE(pool.active == 4); + REQUIRE(pool.blocks == 2); + + //now block 3 should be created + auto p5 = pool.allocate(); + REQUIRE(pool.active == 5); + REQUIRE(pool.blocks == 3); + } +} + +TEST_CASE("allocator4") { + MemoryPool pool; + pool.clear(); + + auto p1 = pool.newElement(); + REQUIRE(p1->i == 0); + REQUIRE(p1->title == ""); + + auto p2 = pool.newElement(); + REQUIRE(p2->i == 0); + REQUIRE(p2->title == ""); + p2->i = 2; + REQUIRE(p2->i == 2); + + pool.deleteElement(p2); + + auto p3 = pool.newElement(); + REQUIRE(p3 == p2); + REQUIRE(p3->i == 0); + REQUIRE(p3->title == ""); +} + +TEST_CASE("allocator5") { + MemoryPool pool; + pool.clear(); + + auto p1 = pool.newElement(); + p1->i = 1; + auto p2 = pool.newElement(); + p2->i = 2; + + auto p3 = pool.newElement(); + p3->i = 3; + auto p4 = pool.newElement(); + p4->i = 4; + + auto p5 = pool.newElement(); + p5->i = 5; + auto p6_ = pool.newElement(); + pool.deleteElement(p6_); + + auto p6 = pool.newElement(); + p6->i = 6; + REQUIRE(pool.active == 6); + REQUIRE(pool.blocks == 3); + + REQUIRE(p1->i == 1); + REQUIRE(p2->i == 2); + REQUIRE(p3->i == 3); + REQUIRE(p4->i == 4); + REQUIRE(p5->i == 5); + REQUIRE(p6->i == 6); +} \ No newline at end of file diff --git a/src/tests/test_vm2.cpp b/src/tests/test_vm2.cpp index e8f509f..429453e 100644 --- a/src/tests/test_vm2.cpp +++ b/src/tests/test_vm2.cpp @@ -1,4 +1,5 @@ -#include +#define CATCH_CONFIG_MAIN +#include #include #include #include @@ -15,42 +16,50 @@ using namespace ts::vm2; using std::string; using std::string_view; -TEST(bench, size) { - std::array a1; - std::array a2; - debug("std::vector = {}", sizeof(std::vector)); - debug("std::array = {}", sizeof(std::array)); - debug("std::array = {}", sizeof(std::array)); - debug("TypeBase = {}", sizeof(Type)); -// debug("TypeTuple = {}", sizeof(TypeTuple)); -// debug("TypeTupleMember = {}", sizeof(TypeTupleMember)); - debug("std::vector = {}", sizeof(std::vector)); - debug("std::array = {}", sizeof(std::array)); - debug("std::array = {}", sizeof(std::array)); -} - -TEST(vm2, vm2Base1) { +//TEST_CASE("bench", "size") { +// std::array a1; +// std::array a2; +// debug("std::vector = {}", sizeof(std::vector)); +// debug("std::array = {}", sizeof(std::array)); +// debug("std::array = {}", sizeof(std::array)); +// debug("TypeBase = {}", sizeof(Type)); +//// debug("TypeTuple = {}", sizeof(TypeTuple)); +//// debug("TypeTupleMember = {}", sizeof(TypeTupleMember)); +// debug("std::vector = {}", sizeof(std::vector)); +// debug("std::array = {}", sizeof(std::array)); +// debug("std::array = {}", sizeof(std::array)); +//} + +TEST_CASE("vm2Base1") { string code = R"( const v1: string = "abc"; const v2: number = 123; )"; auto module = std::make_shared(ts::compile(code), "app.ts", ""); run(module); - EXPECT_EQ(module->errors.size(), 0); + REQUIRE(module->errors.size() == 0); ts::vm2::gcStackAndFlush(); //only v1, v2 - EXPECT_EQ(ts::vm2::pool.active, 2); + REQUIRE(ts::vm2::pool.active == 2); ts::bench("first", 1000, [&] { module->clear(); run(module); }); } +TEST_CASE("vm2TwoTests") { + test(R"( +const v1: string = "abc"; +const v2: number = 123; + )", 0); -TEST(vm2, allocator) { + test(R"( +const v1: string = "aa"; +const v2: number = 44; + )", 0); } -TEST(vm2, vm2Union) { +TEST_CASE("vm2Union") { string code = R"( type a = T | (string | number); const v1: a = 'yes'; @@ -61,15 +70,15 @@ const v3: a = false; run(module); module->printErrors(); - EXPECT_EQ(module->errors.size(), 1); + REQUIRE(module->errors.size() == 1); ts::vm2::gcStackAndFlush(); //only v1, v2, v3 plus for each 4 union (true | string | number) - EXPECT_EQ(ts::vm2::pool.active, 3 * 4); + REQUIRE(ts::vm2::pool.active == 3 * 4); testBench(code, 1); } -TEST(vm2, vm2Base2) { +TEST_CASE("vm2Base2") { string code = R"( type a = T extends string ? 'yes' : 'no'; const v1: a = 'no'; @@ -80,15 +89,15 @@ const v3: a = 'nope'; run(module); module->printErrors(); - EXPECT_EQ(module->errors.size(), 1); + REQUIRE(module->errors.size() == 1); //only v1, v2, v3 subroutine cached value should live ts::vm2::gcStackAndFlush(); - EXPECT_EQ(ts::vm2::pool.active, 3); //not 2 or 5, since inline subroutine do not cache + REQUIRE(ts::vm2::pool.active == 3); //not 2 or 5, since inline subroutine do not cache testBench(code, 1); } -TEST(vm2, vm2Base22) { +TEST_CASE("vm2Base22") { string code = R"( type a = K | (T extends string ? 'yes': 'no'); const v1: a = 'no'; @@ -101,16 +110,12 @@ const v5: a = 'nope'; run(module); module->printErrors(); - EXPECT_EQ(module->errors.size(), 1); + REQUIRE(module->errors.size() == 1); - ts::bench("first", 1000, [&] { - ts::vm2::clear(module); -// module->clear(); - run(module); - }); + testBench(code, 1); } -TEST(vm2, gc) { +TEST_CASE("gc") { // The idea of garbage collection is this: Each type has refCount. // As soon as another type wants to hold on it, it increases refCount. // This happens automatically once a user pop() from the stack. The stack itself @@ -129,15 +134,15 @@ const var1: a = false; auto module = std::make_shared(ts::compile(code), "app.ts", code); run(module); module->printErrors(); - EXPECT_EQ(module->errors.size(), 1); + REQUIRE(module->errors.size() == 1); ts::vm2::gcFlush(); //only var1 cached value should live - EXPECT_EQ(ts::vm2::pool.active, 1); + REQUIRE(ts::vm2::pool.active == 1); ts::vm2::clear(module); ts::vm2::gcStackAndFlush(); - EXPECT_EQ(ts::vm2::pool.active, 0); + REQUIRE(ts::vm2::pool.active == 0); ts::bench("first", 1000, [&] { // ts::vm2::clear(module); @@ -147,7 +152,7 @@ const var1: a = false; }); } -TEST(vm2, gcUnion) { +TEST_CASE("gcUnion") { ts::checker::Program program; program.pushOp(OP::Frame); for (auto i = 0; i<10; i++) { @@ -159,12 +164,12 @@ TEST(vm2, gcUnion) { auto module = std::make_shared(program.build(), "app.ts", ""); run(module); - EXPECT_EQ(module->errors.size(), 0); + REQUIRE(module->errors.size() == 0); ts::vm2::gcStackAndFlush(); - EXPECT_EQ(ts::vm2::pool.active, 0); + REQUIRE(ts::vm2::pool.active == 0); } -TEST(vm2, gcTuple) { +TEST_CASE("gcTuple") { ts::checker::Program program; program.pushOp(OP::Frame); for (auto i = 0; i<10; i++) { @@ -176,12 +181,12 @@ TEST(vm2, gcTuple) { auto module = std::make_shared(program.build(), "app.ts", ""); run(module); - EXPECT_EQ(module->errors.size(), 0); + REQUIRE(module->errors.size() == 0); ts::vm2::gcStackAndFlush(); - EXPECT_EQ(ts::vm2::pool.active, 0); + REQUIRE(ts::vm2::pool.active == 0); } -TEST(vm2, gcObject) { +TEST_CASE("gcObject") { ts::checker::Program program; program.pushOp(OP::Frame); for (auto i = 0; i<10; i++) { @@ -196,12 +201,12 @@ TEST(vm2, gcObject) { auto module = std::make_shared(program.build(), "app.ts", ""); run(module); - EXPECT_EQ(module->errors.size(), 0); + REQUIRE(module->errors.size() == 0); ts::vm2::gcStackAndFlush(); - EXPECT_EQ(ts::vm2::pool.active, 0); + REQUIRE(ts::vm2::pool.active == 0); } -TEST(vm2, vm2TemplateLiteral1) { +TEST_CASE("vm2TemplateLiteral1") { string code = R"( type L = `${string}`; const var1: L = 'abc'; @@ -210,7 +215,7 @@ const var2: L = 22; ts::testBench(code, 1); } -TEST(vm2, vm2TemplateLiteral2) { +TEST_CASE("vm2TemplateLiteral2") { string code = R"( type L = `${34}`; const var1: L = '34'; @@ -219,7 +224,7 @@ const var2: L = 34; ts::testBench(code, 1); } -TEST(vm2, vm2TemplateLiteral3) { +TEST_CASE("vm2TemplateLiteral3") { string code = R"( type L = `a${string}`; const var1: L = 'abc'; @@ -228,7 +233,7 @@ const var2: L = 'bbc'; ts::testBench(code, 1); } -TEST(vm2, vm2TemplateLiteralSize) { +TEST_CASE("vm2TemplateLiteralSize") { string code = R"( type A = [1]; type L = `${A['length']}`; @@ -238,7 +243,7 @@ const var2: L = "10"; ts::testBench(code, 1); } -TEST(vm2, vm2TemplateLiteralSizeGc) { +TEST_CASE("vm2TemplateLiteralSizeGc") { string code = R"( type A = [1]; type L = `${A['length']}`; @@ -246,10 +251,10 @@ const var1: L = "1"; )"; ts::test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, 4); //A|var1 (literal+tupleMember+tuple) + L (literal) + REQUIRE(ts::vm2::pool.active == 4); //A|var1 (literal+tupleMember+tuple) + L (literal) } -TEST(vm2, vm2TupleMerge) { +TEST_CASE("vm2TupleMerge") { string code = R"( type A = [1, 2]; type L = [...A, 3]; @@ -262,17 +267,17 @@ const var3: A = [1, 2]; ts::test(code, 1); } -TEST(vm2, vm2Tuple2) { +TEST_CASE("vm2Tuple2") { string code = R"( type A = [1, 2]; const var1: A = [1, 2]; )"; test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, 1 + (2 + 2)); + REQUIRE(ts::vm2::pool.active == 1 + (2 + 2)); } -TEST(vm2, vm2Tuple3) { +TEST_CASE("vm2Tuple3") { string code = R"( type T = [1]; type A = [...T, 2]; @@ -280,10 +285,10 @@ const var1: A = [1, 2]; )"; auto module = test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, (1 + 2) + (1 + 2)); //[1], [1, 2], where "1" in second tuple is shared with first "1" in [1] + REQUIRE(ts::vm2::pool.active == (1 + 2) + (1 + 2)); //[1], [1, 2], where "1" in second tuple is shared with first "1" in [1] } -TEST(vm2, vm2Tuple30) { +TEST_CASE("vm2Tuple30") { string code = R"( type T = 1; type A = [T, 2]; @@ -291,10 +296,10 @@ const var1: A = [1, 2]; )"; auto module = test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, (1 + 2 + 2)); //$1, [$1, 2] + REQUIRE(ts::vm2::pool.active == (1 + 2 + 2)); //$1, [$1, 2] } -TEST(vm2, vm2Tuple31) { +TEST_CASE("vm2Tuple31") { string code = R"( type T = [1]; type A = [...B, 2]; @@ -302,94 +307,94 @@ const var1: A = [1, 2]; )"; test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, (1 + 2) + (1 + 2)); //[1], [1, 2], where "1" in second tuple is shared with first "1" in [1] + REQUIRE(ts::vm2::pool.active == (1 + 2) + (1 + 2)); //[1], [1, 2], where "1" in second tuple is shared with first "1" in [1] } -TEST(vm2, vm2Tuple32) { +TEST_CASE("vm2Tuple32") { string code = R"( type A = [...B, 2]; const var1: A<[1]> = [1, 2]; )"; test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, (1 + 2 + 2)); //[1, 2] + REQUIRE(ts::vm2::pool.active == (1 + 2 + 2)); //[1, 2] } -TEST(vm2, vm2Tuple33) { +TEST_CASE("vm2Tuple33") { string code = R"( type A = [...B, 2]; const var1: A<[]> = [2]; )"; test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, (1 + 2)); // [2] + REQUIRE(ts::vm2::pool.active == (1 + 2)); // [2] } -TEST(vm2, vm2Fn1) { +TEST_CASE("vm2Fn1") { string code = R"( type F = T; const var1: F = 'abc'; )"; test(code, 0); - EXPECT_EQ(ts::vm2::pool.active, 2); + //REQUIRE(ts::vm2::pool.active == 2); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, 1); + REQUIRE(ts::vm2::pool.active == 1); } -TEST(vm2, vm2Fn2) { +TEST_CASE("vm2Fn2") { string code = R"( type F = T; const var1: F = 'abc'; )"; //todo extends not added yet test(code, 0); - EXPECT_EQ(ts::vm2::pool.active, 3); + //REQUIRE(ts::vm2::pool.active == 3); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, 1); + REQUIRE(ts::vm2::pool.active == 1); } -TEST(vm2, vm2Fn3) { +TEST_CASE("vm2Fn3") { string code = R"( type F = T; const var1: F = 'abc'; )"; test(code, 0); - EXPECT_EQ(ts::vm2::pool.active, 2); + //REQUIRE(ts::vm2::pool.active == 2); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, 1); + REQUIRE(ts::vm2::pool.active == 1); } -TEST(vm2, vm2Fn4) { +TEST_CASE("vm2Fn4") { string code = R"( type F1 = [0]; const var1: F1 = [0]; )"; test(code, 0); ts::vm2::gcStackAndFlush(); - EXPECT_EQ(ts::vm2::pool.active, 3); //[0] + REQUIRE(ts::vm2::pool.active == 3); //[0] } -TEST(vm2, vm2Fn4_1) { +TEST_CASE("vm2Fn4_1") { string code = R"( type F1 = [0]; const var1: F1 = [0]; )"; test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, 3); //[0] + REQUIRE(ts::vm2::pool.active == 3); //[0] } -TEST(vm2, vm2Fn4_2) { +TEST_CASE("vm2Fn4_2") { string code = R"( type F1 = [...T, 0]; const var1: F1<[]> = [0]; )"; test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, 3); //[0] + REQUIRE(ts::vm2::pool.active == 3); //[0] } -TEST(vm2, vm2Fn5) { +TEST_CASE("vm2Fn5") { //todo: make sure that temporary parameter are not GC inside the callee. // currently `if (T->users == 1) {` is not true since T gets GC in ::Extends before [...T, 0] is reached // the idea is to flag parameter [] as input, so that it doesn't get GC. We can't increase 'users' since that would mean @@ -401,10 +406,10 @@ const var1: F1<[]> = [0]; )"; test(code, 0); ts::vm2::gcFlush(); - EXPECT_EQ(ts::vm2::pool.active, 3); //[0] + REQUIRE(ts::vm2::pool.active == 3); //[0] } -TEST(vm2, vm2Fn7) { +TEST_CASE("vm2Fn7") { string code = R"( type F1 = [...T, 0]; type T = []; @@ -414,10 +419,10 @@ const var2: T = []; test(code, 0); ts::vm2::gcFlush(); //a new tuple is generated, but the same amount of active elements is active - EXPECT_EQ(ts::vm2::pool.active, 1 + 3); //[] + [0] + REQUIRE(ts::vm2::pool.active == 1 + 3); //[] + [0] } -TEST(vm2, vm2FnArg) { +TEST_CASE("vm2FnArg") { string code = R"( type F1 = [...T, 0]; type F2 = F1 | T; //T won't be stolen in F1 since refCount++ @@ -430,15 +435,15 @@ const var2: F2<[]> = []; //The idea is that for F1<[]> the [] is refCount=0, and for each argument in `type F1<>` the refCount is increased // and dropped at the end (::Return). This makes sure that [] in F1<[]> does not get stolen in F1. // To support stealing in tail calls, the drop (and frame cleanup) happens before the next function is called. - //EXPECT_EQ(ts::vm2::pool.active, 1); + //REQUIRE(ts::vm2::pool.active == 1); } -TEST(vm2, vm2BenchOverhead) { +TEST_CASE("vm2BenchOverhead") { ts::bench("nothing", 1000, [&] { }); } -TEST(vm2, vm2Complex1) { +TEST_CASE("vm2Complex1") { //todo: crashes with BAD_ACCESS. lag wohl daran, dass nicht alles zurückgesetzt wurde string code = R"( type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; //yes @@ -447,7 +452,7 @@ type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum< //type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; //no, A used after ...A //type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; //no, A used after ...A //type StringToNum = (`${A['length']}` extends T ? A['length'] : StringToNum) | A; //no, A used after ...A -const var1: StringToNum<'999', []> = 999; +const var1: StringToNum<'5', []> = 1002; //const var2: StringToNum<'999'> = 1002; )"; //todo: fix reuse of A. We need to mark the argument with a flag though, so we know we can for sure steal its ref and just append it. @@ -455,13 +460,13 @@ const var1: StringToNum<'999', []> = 999; //todo we have to fix that A.users keeps increasing //todo: add tail call optimisation //todo: super instruction for `${A['length']}` and A['length'] - test(code, 0); -// testBench(code, 0); + test(code, 1); + //testBench(code, 1); // testBench(code, 0, 1); // debug("active {}", ts::vm2::pool.active); // ts::vm2::gcFlush(); // debug("active gc {}", ts::vm2::pool.active); -// EXPECT_EQ(ts::vm2::pool.active, 1); +// REQUIRE(ts::vm2::pool.active == 1); // ts::bench("first", 10, [&] { // module->clear(); @@ -469,18 +474,18 @@ const var1: StringToNum<'999', []> = 999; // }); } -TEST(vm2, vm2Cartesian) { +TEST_CASE("vm2Cartesian") { { vm2::CartesianProduct cartesian; //`${'a'}${'b'}` => StringLiteral|StringLiteral cartesian.add(vm2::allocate(TypeKind::Literal)->setLiteral(TypeFlag::StringLiteral, "a")); cartesian.add(vm2::allocate(TypeKind::Literal)->setLiteral(TypeFlag::StringLiteral, "b")); auto product = cartesian.calculate(); - EXPECT_EQ(product.size(), 1); + REQUIRE(product.size() == 1); auto first = product[0]; - EXPECT_EQ(first.size(), 2); - EXPECT_EQ(stringify(first[0]), "\"a\""); - EXPECT_EQ(stringify(first[1]), "\"b\""); + REQUIRE(first.size() == 2); + REQUIRE(stringify(first[0]) == "\"a\""); + REQUIRE(stringify(first[1]) == "\"b\""); } { vm2::CartesianProduct cartesian; @@ -491,20 +496,20 @@ TEST(vm2, vm2Cartesian) { unionType->appendChild(useAsRef(vm2::allocate(TypeKind::Literal)->setLiteral(TypeFlag::StringLiteral, "c"))); cartesian.add(unionType); auto product = cartesian.calculate(); - EXPECT_EQ(product.size(), 2); + REQUIRE(product.size() == 2); auto first = product[0]; - EXPECT_EQ(first.size(), 2); - EXPECT_EQ(stringify(first[0]), "\"a\""); - EXPECT_EQ(stringify(first[1]), "\"b\""); + REQUIRE(first.size() == 2); + REQUIRE(stringify(first[0]) == "\"a\""); + REQUIRE(stringify(first[1]) == "\"b\""); auto second = product[1]; - EXPECT_EQ(second.size(), 2); - EXPECT_EQ(stringify(second[0]), "\"a\""); - EXPECT_EQ(stringify(second[1]), "\"c\""); + REQUIRE(second.size() == 2); + REQUIRE(stringify(second[0]) == "\"a\""); + REQUIRE(stringify(second[1]) == "\"c\""); } } -TEST(vm2, bigUnion) { +TEST_CASE("bigUnion") { ts::checker::Program program; program.pushOp(OP::Frame); @@ -539,16 +544,16 @@ TEST(vm2, bigUnion) { auto module = std::make_shared(program.build(), "app.ts", ""); run(module); module->printErrors(); - EXPECT_EQ(module->errors.size(), 0); + REQUIRE(module->errors.size() == 0); ts::vm2::clear(module); ts::vm2::gcStackAndFlush(); - EXPECT_EQ(ts::vm2::pool.active, 0); + REQUIRE(ts::vm2::pool.active == 0); ts::bench("first", 1000, [&] { module->clear(); // ts::vm2::clear(module); run(module); -// EXPECT_EQ(process(ops), 300 * 6); +// REQUIRE(process(ops) == 300 * 6); }); } diff --git a/src/tests/utils.h b/src/tests/utils.h index 4edcbac..9b83418 100644 --- a/src/tests/utils.h +++ b/src/tests/utils.h @@ -19,7 +19,7 @@ namespace ts { auto module = make_shared(bin, "app.ts", code); vm2::run(module); module->printErrors(); - EXPECT_EQ(expectedErrors, module->errors.size()); + REQUIRE(expectedErrors == module->errors.size()); return module; } @@ -28,7 +28,7 @@ namespace ts { auto module = make_shared(bin, "app.ts", code); vm2::run(module); module->printErrors(); - EXPECT_EQ(expectedErrors, module->errors.size()); + REQUIRE(expectedErrors == module->errors.size()); if (expectedErrors != module->errors.size()) return; auto warmTime = benchRun(iterations, [&module] { @@ -45,7 +45,7 @@ namespace ts { vm2::run(module); }); - fmt::print("{} iterations (it): compile {:.9f}ms/it, cold {:.9f}ms/it, warm {:.9f}ms/it", iterations, compileTime.count() / iterations, coldTime.count() / iterations, warmTime.count() / iterations); + std::cout << fmt::format("{} iterations (it): compile {:.9f}ms/it, cold {:.9f}ms/it, warm {:.9f}ms/it", iterations, compileTime.count() / iterations, coldTime.count() / iterations, warmTime.count() / iterations); } } \ No newline at end of file