diff --git a/.gitmodules b/.gitmodules index daba944..21a84b1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "libs/imgui"] path = libs/imgui url = git@github.com:ocornut/imgui.git +[submodule "libs/asmjit"] + path = libs/asmjit + url = git@github.com:asmjit/asmjit.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 1571b0e..4da14f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,9 +9,8 @@ endif() include_directories(libs/tracy/) include_directories(libs/fmt/include/) -include_directories(libs) -list(APPEND CMAKE_MODULE_PATH libs/) +list(APPEND CMAKE_MODULE_PATH libs) #add_definitions(-DTRACY_ENABLE) #include_directories(libs/tracy/) @@ -19,13 +18,17 @@ list(APPEND CMAKE_MODULE_PATH libs/) add_subdirectory(libs/tracy) add_subdirectory(libs/fmt) +set(ASMJIT_STATIC TRUE) +add_subdirectory(libs/asmjit) + # enable for profiling #add_definitions(-DTRACY_ENABLE) #link_libraries(Tracy::TracyClient) -add_subdirectory(src) +include_directories(libs/asmjit/src) +include_directories(libs/magic_enum) -include_directories(libs) +add_subdirectory(src) add_executable(typescript_main main.cpp) diff --git a/libs/asmjit b/libs/asmjit new file mode 160000 index 0000000..06d0bad --- /dev/null +++ b/libs/asmjit @@ -0,0 +1 @@ +Subproject commit 06d0badec53710a4f572cf5642881ce570c5d274 diff --git a/libs/magic_enum.hpp b/libs/magic_enum/magic_enum.hpp similarity index 100% rename from libs/magic_enum.hpp rename to libs/magic_enum/magic_enum.hpp diff --git a/main.cpp b/main.cpp index ba9831d..1a045af 100644 --- a/main.cpp +++ b/main.cpp @@ -1,59 +1,43 @@ #include -#include -#include #include "./src/core.h" +#include "./src/fs.h" #include "./src/parser2.h" -#include "./src/checker/compiler.h" #include "./src/checker/vm.h" +#include "./src/checker/debug.h" +#include "./src/checker/compiler.h" using namespace ts; -auto readFile(const string &file) { - std::ifstream t; - t.open(file); - t.seekg(0, std::ios::end); - size_t size = t.tellg(); - std::string buffer(size, ' '); - t.seekg(0); - t.read(&buffer[0], size); - return std::move(buffer); -} - -void writeFile(const string &file, const string_view &content) { - std::ofstream t; - t.open(file); - t << content; -} - -bool exists(const string &file) -{ - std::ifstream infile(file); - return infile.good(); -} void run(const string &bytecode, const string &code, const string &fileName) { vm::VM vm; - vm.run(bytecode, code, fileName); - vm.printErrors(); + auto module = make_shared(bytecode, fileName, code); + bench(1, [&]{ + vm.run(module); + vm.printErrors(); + }); } void compileAndRun(const string &code, const string &file, const string &fileName) { - auto bytecode = file + ".tsbytecode"; + auto bytecodePath = file + ".tsb"; auto buffer = readFile(file); checker::Compiler compiler; Parser parser; auto result = parser.parseSourceFile(file, buffer, ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); auto program = compiler.compileSourceFile(result); auto bin = program.build(); - writeFile(bytecode, bin); + writeFile(bytecodePath, bin); + std::filesystem::last_write_time(bytecodePath, std::filesystem::last_write_time(file)); vm::VM vm; - vm.run(bin, code, fileName); + checker::printBin(bin); + auto module = make_shared(bin, fileName, code); + vm.run(module); vm.printErrors(); } int main(int argc, char *argv[]) { - std::string file = "/Users/marc/bude/typescript-cpp/tests/basicError1.ts"; + std::string file = "/Users/marc/bude/typescript-cpp/tests/big1.ts"; auto cwd = std::filesystem::current_path(); if (argc > 1) { @@ -64,7 +48,7 @@ int main(int argc, char *argv[]) { auto bytecode = file + ".tsb"; auto relative = std::filesystem::relative(file, cwd); - if (exists(bytecode)) { + if (exists(bytecode) && std::filesystem::last_write_time(bytecode) == std::filesystem::last_write_time(file)) { run(readFile(bytecode), code, relative.string()); } else { compileAndRun(code, file, relative.string()); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bad3cea..4afd645 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,9 +11,10 @@ add_subdirectory(tests) add_library(typescript utf.h utf.cpp core.h core.cpp utilities.h utilities.cpp node_test.h node_test.cpp syntax_cursor.h syntax_cursor.cpp parser2.h parser2.cpp types.h types.cpp path.h path.cpp factory.h factory.cpp parenthesizer.h parenthesizer.cpp scanner.h scanner.cpp - checker/vm.h checker/vm.cpp checker/instructions.h checker/compiler.h checker/types.h checker/utils.h checker/checks.h checker/debug.h) + checker/instructions.h checker/compiler.h checker/types.h checker/utils.h checker/checks.h checker/debug.h checker/vm2.cpp) # ${CMAKE_CURRENT_SOURCE_DIR}/../libs/tracy/TracyClient.cpp target_link_libraries(typescript fmt) +target_link_libraries(typescript asmjit::asmjit) add_subdirectory(gui) \ No newline at end of file diff --git a/src/checker/MemoryPool.h b/src/checker/MemoryPool.h new file mode 100644 index 0000000..8a50d62 --- /dev/null +++ b/src/checker/MemoryPool.h @@ -0,0 +1,235 @@ +#pragma once + +#include +#include +#include +#include + +template +class MemoryPool { +public: + /* Member types */ + typedef T value_type; + typedef T *pointer; + typedef T &reference; + 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; + }; + + /* Member functions */ + MemoryPool() noexcept; + MemoryPool(const MemoryPool &memoryPool) noexcept; + MemoryPool(MemoryPool &&memoryPool) noexcept; + template + MemoryPool(const MemoryPool &memoryPool) noexcept; + + ~MemoryPool() noexcept; + + MemoryPool &operator=(const MemoryPool &memoryPool) = delete; + MemoryPool &operator=(MemoryPool &&memoryPool) noexcept; + + pointer address(reference x) const noexcept; + const_pointer address(const_reference x) const noexcept; + + // 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); + + size_type max_size() const noexcept; + + template + void construct(U *p, Args &&... args); + + template + void destroy(U *p); + + template + pointer newElement(Args &&... args); + void deleteElement(pointer p); + +private: + union Slot_ { + value_type element; + Slot_ *next; + }; + + 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) { + if (freeSlots_ != nullptr) { + pointer result = reinterpret_cast(freeSlots_); + freeSlots_ = freeSlots_->next; + return result; + } else { + if (currentSlot_ >= lastSlot_) { + allocateBlock(); +// printf("allocate new block\n"); + } + return reinterpret_cast(currentSlot_++); + } +} + +template +inline void +MemoryPool::deallocate(pointer p, size_type n) { + 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 diff --git a/src/checker/check2.h b/src/checker/check2.h new file mode 100644 index 0000000..cbf6333 --- /dev/null +++ b/src/checker/check2.h @@ -0,0 +1,36 @@ +#pragma once + +#include "./types2.h" + +namespace ts::vm2 { + /** + * `left extends right ? true : false` + */ + bool extends(Type *left, Type *right) { + switch (right->kind) { + case TypeKind::Literal: { + switch (left->kind) { + case TypeKind::Literal: + //todo: literal type + return left->hash == right->hash; + } + break; + } + case TypeKind::String: { + switch (left->kind) { + case TypeKind::String: return true; + case TypeKind::Literal: return left->flag | TypeFlag::StringLiteral; + } + break; + } + case TypeKind::Number: { + switch (left->kind) { + case TypeKind::Number: return true; + case TypeKind::Literal: return left->flag | TypeFlag::NumberLiteral; + } + break; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/checker/checks.h b/src/checker/checks.h index af9a6b5..970097a 100644 --- a/src/checker/checks.h +++ b/src/checker/checks.h @@ -4,17 +4,19 @@ #include "../core.h" namespace ts::vm { - string_view getName(const shared &member) { + static auto emptyString = HashString(""); + + HashString &getName(const shared &member) { switch (member->kind) { case TypeKind::MethodSignature: return to(member)->name; case TypeKind::Method: return to(member)->name; case TypeKind::PropertySignature: return to(member)->name; case TypeKind::Property: return to(member)->name; } - return ""; + return emptyString; } - sharedOpt findMember(const vector> &members, const string_view &name) { + sharedOpt findMember(const vector> &members, HashString &name) { for (auto &&member: members) { switch (member->kind) { case TypeKind::MethodSignature: if (to(member)->name == name) return member; @@ -27,6 +29,7 @@ namespace ts::vm { break; } } + debug("member {} not found", name.getString()); return nullptr; } @@ -36,8 +39,8 @@ namespace ts::vm { } struct StackEntry { - shared left; - shared right; + const shared left; + const shared right; }; struct ExtendableStack { @@ -50,11 +53,11 @@ namespace ts::vm { for (auto &&entry: stack) { switch (entry.left->kind) { case TypeKind::Property: { - r += to(entry.left)->name; + r += to(entry.left)->name.getString(); break; } case TypeKind::PropertySignature: { - r += to(entry.left)->name; + r += to(entry.left)->name.getString(); break; } } @@ -64,9 +67,12 @@ namespace ts::vm { } DiagnosticMessage errorMessage() { -// auto [left, right] = stack.back(); - auto [left, right] = stack.front(); + if (stack.empty()) return DiagnosticMessage("Type '{}' is not assignable to type '{}'", 0); + + auto [left, right] = stack.back(); +// auto [left, right] = stack.front(); auto message = fmt::format("Type '{}' is not assignable to type '{}'", stringify(left), stringify(right)); +// auto message = fmt::format("Type '' is not assignable to type ''"); return DiagnosticMessage(message, left->ip); } @@ -77,7 +83,19 @@ namespace ts::vm { void pop() { if (isFailed) return; //we maintain the stack for nice error messages - stack.pop_back(); + if (!stack.empty()) stack.pop_back(); + } + + unsigned int getState() { + return stack.size(); + } + + void revertTo(unsigned int state) { +// stack.resize(state); + } + + void forcePop() { + if (!stack.empty()) stack.pop_back(); } bool failed() { @@ -98,14 +116,184 @@ namespace ts::vm { } }; + inline bool bla = false; + /** * Checks whether left extends right. * * `left extends right ? true : false` */ bool isExtendable(shared &left, shared &right, ExtendableStack &stack) { - if (stack.has(left, right)) return true; - stack.push(left, right); + if (right->kind == TypeKind::Parameter) { + if (left->kind == TypeKind::Undefined && isOptional(right)) return true; + right = to(right)->type; + } + profiler.compare(left, right); + return true; + + switch (right->kind) { + case TypeKind::ObjectLiteral: { + switch (left->kind) { + case TypeKind::ObjectLiteral: { + auto leftTypes = to(left)->types; + auto rightTypes = to(right)->types; + + //check constructor signature + //check index signature + //check call signature + + //check properties + for (auto &&member: rightTypes) { + if (isMember(member)) { + auto leftMember = findMember(leftTypes, getName(member)); + if (!leftMember) return stack.failed(); + if (!isExtendable(leftMember, member, stack)) return stack.failed(); + } + } + return stack.valid(); + } + } + return stack.failed(); + } + case TypeKind::PropertySignature: { + switch (left->kind) { + case TypeKind::Property: { + auto l = to(left); + auto r = to(right); + if (!r->optional && isOptional(l)) return stack.failed(); + return isExtendable(l->type, r->type, stack) ? stack.valid() : stack.failed(); + } + case TypeKind::PropertySignature: { + auto l = to(left); + auto r = to(right); + if (!r->optional && isOptional(l)) return stack.failed(); + return isExtendable(l->type, r->type, stack) ? stack.valid() : stack.failed(); + } + default: { + auto r = to(right); + if (!r->optional && isOptional(left)) return stack.failed(); + return isExtendable(left, r->type, stack) ? stack.valid() : stack.failed(); + } + } + } + case TypeKind::Property: { + switch (left->kind) { + case TypeKind::Property: { + auto l = to(left); + auto r = to(right); + if (!r->optional && isOptional(l)) return stack.failed(); + return isExtendable(l->type, r->type, stack) ? stack.valid() : stack.failed(); + } + case TypeKind::PropertySignature: { + auto l = to(left); + auto r = to(right); + if (!r->optional && isOptional(l)) return stack.failed(); + return isExtendable(l->type, r->type, stack) ? stack.valid() : stack.failed(); + } + default: { + auto r = to(right); + if (!r->optional && isOptional(left)) return stack.failed(); + return isExtendable(left, r->type, stack) ? stack.valid() : stack.failed(); + } + } + } + case TypeKind::String: { + if (left->kind == TypeKind::String) return stack.valid(); + if (left->kind == TypeKind::Literal) return to(left)->type == TypeLiteralType::String ? stack.valid() : stack.failed(); + return stack.failed(); + } + case TypeKind::Number: { + if (left->kind == TypeKind::Number) return stack.valid(); + if (left->kind == TypeKind::Literal) return to(left)->type == TypeLiteralType::Number ? stack.valid() : stack.failed(); + return stack.failed(); + } + case TypeKind::Boolean: { + if (left->kind == TypeKind::Boolean) return stack.valid(); + if (left->kind == TypeKind::Literal) return to(left)->type == TypeLiteralType::Boolean ? stack.valid() : stack.failed(); + return stack.failed(); + } + case TypeKind::Literal: { + if (left->kind == TypeKind::Literal) { +// //todo: this is weirdly slow? +// auto a = to(left)->literal.hash; +// auto b = to(right)->literal.hash; +//// bla = a == b; // ? stack.valid() : stack.failed(); +//// if (!bla) { +//// debug("Not equal {}={} {}={}", to(left)->literal.getString(), to(right)->literal.getString(), a, b); +//// } +// return a == b ? stack.valid() : stack.failed(); + return to(left)->equal(to(right)) ? stack.valid() : stack.failed(); + } + return stack.failed(); + } + case TypeKind::Array: { + if (left->kind == TypeKind::Array) { + return isExtendable(to(left)->type, to(right)->type, stack) ? stack.valid() : stack.failed(); + } else if (left->kind == TypeKind::Tuple) { + auto array = to(right); + auto tuple = to(left); + + if (tuple->types.empty()) return stack.valid(); + if (array->type->kind == TypeKind::Any) return stack.valid(); + auto optional = isOptional(array->type); + + for (auto &&tupleMember: tuple->types) { + if (tupleMember->optional && optional) { + //valid + } else { + if (!isExtendable(tupleMember->type, array->type, stack)) return stack.failed(); + } + } + + return stack.valid(); + } + return stack.failed(); + } + case TypeKind::Union: { + if (left->kind != TypeKind::Union) { + auto rightUnion = to(right); +// if (left->kind == TypeKind::Literal && rightUnion->fastLiteralCheck()) { +// return rightUnion->includes(left) ? stack.valid() : stack.failed(); +// } + + auto valid = false; + for (auto &&l: rightUnion->types) { + if (isExtendable(left, l, stack)) { + valid = true; + break; + } + } + return valid ? stack.valid() : stack.failed(); + } else { + //e.g.: string|number = string|boolean + auto rightTypes = to(right)->types; + auto leftTypes = to(left)->types; + + for (auto &&r: leftTypes) { + bool valid = false; + auto state = stack.getState(); + for (auto &&l: rightTypes) { + if (isExtendable(l, r, stack)) { + valid = true; + break; + } else { + stack.revertTo(state); //we're not interested in failures inside the union, right? + } + } + if (!valid) return stack.failed(); + } + return stack.valid(); + } + } + } + + return false; + } + + bool isExtendable2(shared &left, shared &right, ExtendableStack &stack) { +// if (stack.has(left, right)) return true; +// stack.push(left, right); + if (stack.stack.empty()) stack.push(left, right); if (right->kind == TypeKind::Parameter) { if (left->kind == TypeKind::Undefined && isOptional(right)) return true; @@ -195,16 +383,56 @@ namespace ts::vm { } case TypeKind::Literal: { if (left->kind == TypeKind::Literal) { - return to(left)->type == to(right)->type && to(left)->text() == to(right)->text() ? stack.valid() : stack.failed(); +// return stack.valid(); +// debug("compare {}=={} ({}=={}) is {}", +// to(left)->literal.getString(), +// to(right)->literal.getString(), +// to(left)->literal.getHash(), +// to(right)->literal.getHash(), +// to(left)->equal(to(right)) +// ); +// return stack.valid(); +// return to(left)->literal.hash == to(right)->literal.hash ? stack.valid() : stack.failed(); + return to(left)->equal(to(right)) ? stack.valid() : stack.failed(); + } + return stack.failed(); + } + case TypeKind::Array: { + if (left->kind == TypeKind::Array) { + return isExtendable(to(left)->type, to(right)->type, stack) ? stack.valid() : stack.failed(); + } else if (left->kind == TypeKind::Tuple) { + auto array = to(right); + auto tuple = to(left); + + if (tuple->types.empty()) return stack.valid(); + if (array->type->kind == TypeKind::Any) return stack.valid(); + auto optional = isOptional(array->type); + + for (auto &&tupleMember: tuple->types) { + if (tupleMember->optional && optional) { + //valid + } else { + if (!isExtendable(tupleMember->type, array->type, stack)) return stack.failed(); + } + } + + return stack.valid(); } return stack.failed(); } case TypeKind::Union: { if (left->kind != TypeKind::Union) { + auto valid = false; + auto state = stack.getState(); for (auto &&l: to(right)->types) { - if (isExtendable(left, l, stack)) return stack.valid(); + if (isExtendable(left, l, stack)) { + valid = true; + break; + } else { + stack.revertTo(state); //we're not interested in failures inside the union, right? + } } - return false; + return valid ? stack.valid() : stack.failed(); } else { //e.g.: string|number = string|boolean auto rightTypes = to(right)->types; @@ -212,11 +440,14 @@ namespace ts::vm { for (auto &&r: leftTypes) { bool valid = false; + auto state = stack.getState(); for (auto &&l: rightTypes) { if (isExtendable(l, r, stack)) { valid = true; break; - }; + } else { + stack.revertTo(state); //we're not interested in failures inside the union, right? + } } if (!valid) return stack.failed(); } diff --git a/src/checker/compiler.h b/src/checker/compiler.h index cff6837..6cb7e0a 100644 --- a/src/checker/compiler.h +++ b/src/checker/compiler.h @@ -1,12 +1,12 @@ -#pragma +#pragma once #include #include #include -#include "../types.h" #include "./instructions.h" #include "./utils.h" +#include "../node_test.h" namespace ts::checker { @@ -192,24 +192,24 @@ namespace ts::checker { */ void pushAddress(unsigned int address) { auto &ops = getOPs(); - writeUint32(ops, ops.size(), address); + vm::writeUint32(ops, ops.size(), address); } void pushUint32(unsigned int v) { auto &ops = getOPs(); - writeUint32(ops, ops.size(), v); + vm::writeUint32(ops, ops.size(), v); } void pushUint16(unsigned int v) { auto &ops = getOPs(); - writeUint16(ops, ops.size(), v); + vm::writeUint16(ops, ops.size(), v); } void pushError(ErrorCode code, const shared &node) { //errors need to be part of main sourceMap.push(0, node->pos, node->end); ops.push_back(OP::Error); - writeUint16(ops, ops.size(), (unsigned int)code); + vm::writeUint16(ops, ops.size(), (unsigned int)code); } void pushSymbolAddress(Symbol &symbol) { @@ -221,8 +221,8 @@ namespace ts::checker { frameOffset++; current = current->previous; } - writeUint16(ops, ops.size(), frameOffset); - writeUint16(ops, ops.size(), symbol.index); + vm::writeUint16(ops, ops.size(), frameOffset); + vm::writeUint16(ops, ops.size(), symbol.index); } vector &getOPs() { @@ -339,21 +339,19 @@ namespace ts::checker { vector bin; unsigned int address = 0; - if (storage.size() || subroutines.size()) { - address = 5; //we add JUMP + index when building the program to jump over all subroutines&storages - bin.push_back(OP::Jump); - writeUint32(bin, bin.size(), 0); //set after storage handling - } + address = 5; //we add JUMP + index when building the program to jump over all subroutines&storages + bin.push_back(OP::Jump); + vm::writeUint32(bin, bin.size(), 0); //set after storage handling for (auto &&item: storage) { address += 2 + item.size(); } //set initial jump position to right after the storage data - writeUint32(bin, 1, address); + vm::writeUint32(bin, 1, address); //push all storage data to the binary for (auto &&item: storage) { - writeUint16(bin, bin.size(), item.size()); + vm::writeUint16(bin, bin.size(), item.size()); bin.insert(bin.end(), item.begin(), item.end()); } @@ -366,7 +364,7 @@ namespace ts::checker { //write sourcemap bin.push_back(OP::SourceMap); - writeUint32(bin, bin.size(), sourceMapSize); + vm::writeUint32(bin, bin.size(), sourceMapSize); address += 1 + 4 + sourceMapSize; //OP::SourceMap + uint32 size unsigned int bytecodePosOffset = address; @@ -375,17 +373,17 @@ namespace ts::checker { for (auto &&routine: subroutines) { for (auto &&map: routine->sourceMap.map) { - writeUint32(bin, bin.size(), bytecodePosOffset + map.bytecodePos); - writeUint32(bin, bin.size(), map.sourcePos); - writeUint32(bin, bin.size(), map.sourceEnd); + vm::writeUint32(bin, bin.size(), bytecodePosOffset + map.bytecodePos); + vm::writeUint32(bin, bin.size(), map.sourcePos); + vm::writeUint32(bin, bin.size(), map.sourceEnd); } bytecodePosOffset += routine->ops.size(); } for (auto &&map: sourceMap.map) { - writeUint32(bin, bin.size(), bytecodePosOffset + map.bytecodePos); - writeUint32(bin, bin.size(), map.sourcePos); - writeUint32(bin, bin.size(), map.sourceEnd); + vm::writeUint32(bin, bin.size(), bytecodePosOffset + map.bytecodePos); + vm::writeUint32(bin, bin.size(), map.sourcePos); + vm::writeUint32(bin, bin.size(), map.sourceEnd); } address += 1 + 4; //OP::Main + uint32 address @@ -394,15 +392,15 @@ namespace ts::checker { //after the storage data follows the subroutine meta-data. for (auto &&routine: subroutines) { bin.push_back(OP::Subroutine); - writeUint32(bin, bin.size(), routine->nameAddress); - writeUint32(bin, bin.size(), address); + vm::writeUint32(bin, bin.size(), routine->nameAddress); + vm::writeUint32(bin, bin.size(), address); address += routine->ops.size(); } //after subroutine meta-data follows the actual subroutine code, which we jump over. //this marks the end of the header. bin.push_back(OP::Main); - writeUint32(bin, bin.size(), address); + vm::writeUint32(bin, bin.size(), address); for (auto &&routine: subroutines) { bin.insert(bin.end(), routine->ops.begin(), routine->ops.end()); @@ -410,6 +408,7 @@ namespace ts::checker { //now the main code is added bin.insert(bin.end(), ops.begin(), ops.end()); + bin.push_back(OP::Halt); return string(bin.begin(), bin.end()); } @@ -922,6 +921,12 @@ namespace ts::checker { //todo: handle `as const`, widen if not const break; } + case types::SyntaxKind::ArrayType: { + auto n = to(node); + handle(n->elementType, program); + program.pushOp(OP::Array, node); + break; + } case types::SyntaxKind::TupleType: { program.pushFrame(); auto n = to(node); diff --git a/src/checker/debug.h b/src/checker/debug.h index 88ade49..44ee067 100644 --- a/src/checker/debug.h +++ b/src/checker/debug.h @@ -9,11 +9,6 @@ namespace ts::checker { using std::string_view; using ts::instructions::OP; - inline string_view readStorage(string_view &ops, unsigned int address) { - auto size = readUint16(ops, address); - return ops.substr(address + 2, size); - } - struct PrintSubroutineOp { string text; unsigned int address; @@ -50,7 +45,7 @@ namespace ts::checker { for (unsigned int i = 0; i < end; i++) { if (storageEnd) { while (i < storageEnd) { - auto size = readUint16(bin, i); + auto size = vm::readUint16(bin, i); auto data = bin.substr(i + 2, size); if (print) fmt::print("(Storage ({})\"{}\") ", size, data); result.storages.push_back(string(data)); @@ -83,22 +78,22 @@ namespace ts::checker { switch (op) { case OP::Call: { - params += fmt::format(" &{}[{}]", readUint32(bin, i + 1), readUint16(bin, i + 5)); + params += fmt::format(" &{}[{}]", vm::readUint32(bin, i + 1), vm::readUint16(bin, i + 5)); i += 6; break; } case OP::SourceMap: { - auto size = readUint32(bin, i + 1); + auto size = vm::readUint32(bin, i + 1); auto start = i + 1; i += 4 + size; params += fmt::format(" {}->{} ({})", start, i, size / (4 * 3)); //each entry has 3x 4bytes (uint32) for (unsigned int j = start + 4; j < i; j += 4 * 3) { DebugSourceMapEntry sourceMapEntry{ - .op = (OP)(bin[readUint32(bin, j)]), - .bytecodePos = readUint32(bin, j), - .sourcePos = readUint32(bin, j + 4), - .sourceEnd = readUint32(bin, j + 8), + .op = (OP)(bin[vm::readUint32(bin, j)]), + .bytecodePos = vm::readUint32(bin, j), + .sourcePos = vm::readUint32(bin, j + 4), + .sourceEnd = vm::readUint32(bin, j + 8), }; result.sourceMap.push_back(sourceMapEntry); if (print) debug("Map [{}]{} to {}:{}", sourceMapEntry.bytecodePos, sourceMapEntry.op, sourceMapEntry.sourcePos, sourceMapEntry.sourceEnd); @@ -106,9 +101,9 @@ namespace ts::checker { break; } case OP::Subroutine: { - auto nameAddress = readUint32(bin, i + 1); - auto address = readUint32(bin, i + 5); - string name = nameAddress ? string(readStorage(bin, nameAddress)) : ""; + auto nameAddress = vm::readUint32(bin, i + 1); + auto address = vm::readUint32(bin, i + 5); + string name = nameAddress ? string(vm::readStorage(bin, nameAddress)) : ""; params += fmt::format(" {}[{}]", name, address); i += 8; result.subroutines.push_back({.name = name, .address = address}); @@ -116,7 +111,7 @@ namespace ts::checker { } case OP::Main: case OP::Jump: { - auto address = readUint32(bin, i + 1); + auto address = vm::readUint32(bin, i + 1); params += fmt::format(" &{}", address); i += 4; if (op == OP::Jump) { @@ -132,39 +127,39 @@ namespace ts::checker { break; } case OP::JumpCondition: { - params += fmt::format(" &{}:&{}", readUint16(bin, i + 1), readUint16(bin, i + 3)); + params += fmt::format(" &{}:&{}", vm::readUint16(bin, i + 1), vm::readUint16(bin, i + 3)); i += 4; break; } case OP::Set: case OP::TypeArgumentDefault: case OP::Distribute: { - params += fmt::format(" &{}", readUint32(bin, i + 1)); + params += fmt::format(" &{}", vm::readUint32(bin, i + 1)); i += 4; break; } case OP::FunctionRef: { - params += fmt::format(" &{}", readUint32(bin, i + 1)); + params += fmt::format(" &{}", vm::readUint32(bin, i + 1)); i += 4; break; } case OP::Instantiate: { - params += fmt::format(" {}", readUint16(bin, i + 1)); + params += fmt::format(" {}", vm::readUint16(bin, i + 1)); i += 2; break; } case OP::Error: { - params += fmt::format(" {}", (ErrorCode)readUint16(bin, i + 1)); + params += fmt::format(" {}", (instructions::ErrorCode)vm::readUint16(bin, i + 1)); i += 2; break; } case OP::CallExpression: { - params += fmt::format(" &{}", readUint16(bin, i + 1)); + params += fmt::format(" &{}", vm::readUint16(bin, i + 1)); i += 2; break; } case OP::Loads: { - params += fmt::format(" &{}:{}", readUint16(bin, i + 1), readUint16(bin, i + 3)); + params += fmt::format(" &{}:{}", vm::readUint16(bin, i + 1), vm::readUint16(bin, i + 3)); i += 4; break; } @@ -172,8 +167,8 @@ namespace ts::checker { case OP::NumberLiteral: case OP::BigIntLiteral: case OP::StringLiteral: { - auto address = readUint32(bin, i + 1); - params += fmt::format(" \"{}\"", readStorage(bin, address)); + auto address = vm::readUint32(bin, i + 1); + params += fmt::format(" \"{}\"", vm::readStorage(bin, address)); i += 4; break; } @@ -191,7 +186,7 @@ namespace ts::checker { result.operations.push_back(text); } if (print) { - std::cout << "[" << startI << "] (" << text << ") "; + std::cout << "[" << startI << "](" << text << ") "; } } if (print) fmt::print("\n"); diff --git a/src/checker/instructions.h b/src/checker/instructions.h index 8957c6f..40fe562 100644 --- a/src/checker/instructions.h +++ b/src/checker/instructions.h @@ -1,8 +1,15 @@ #pragma once +#include "../enum.h" + namespace ts::instructions { enum OP { - //type related + Noop, + Jump, //arbitrary jump, used at the beginning to jump over storage-data (storage-data's addresses are constant) + Halt, + SourceMap, //one parameter (size uint32). all subsequent bytes withing the given size is a map op:pos:end, each uint32 + Main, //marks end of meta-data section (subroutine metadata + storage data). has one parameter that points to the actual main code. + Never, Any, Unknown, @@ -41,6 +48,7 @@ namespace ts::instructions { ObjectLiteral, IndexSignature, + Array, Tuple, TupleMember, TupleNamedMember, //has one parameter, the name in the storage @@ -107,11 +115,8 @@ namespace ts::instructions { Frame, //creates a new stack frame Return, - SourceMap, //one parameter (size uint32). all subsequent bytes withing the given size is a map op:pos:end, each uint32 Subroutine, - Jump, //arbitrary jump, used at the beginning to jump over storage-data (storage-data's addresses are constant) - Main, //marks end of meta-data section (subroutine metadata + storage data). has one parameter that points to the actual main code. Distribute, //calls a subroutine for each union member. one parameter (address to subroutine) Call //call a subroutine and push the result on the stack }; @@ -121,6 +126,7 @@ namespace ts::instructions { }; } + template<> struct fmt::formatter: formatter { template diff --git a/src/checker/module2.h b/src/checker/module2.h new file mode 100644 index 0000000..dc50f27 --- /dev/null +++ b/src/checker/module2.h @@ -0,0 +1,198 @@ +#pragma once + +#include +#include "../core.h" +#include "./utils.h" +#include "./types2.h" +#include "./instructions.h" +#include "../utf.h" + +namespace ts::vm2 { + using std::string; + using std::string_view; + using ts::instructions::OP; + using ts::utf::eatWhitespace; + + struct ModuleSubroutine { + string_view name; + unsigned int address; + bool exported = false; + Type *result = nullptr; + Type *narrowed = nullptr; //when control flow analysis sets a new value + ModuleSubroutine(string_view name, unsigned int address): name(name), address(address) {} + }; + + struct FoundSourceMap { + unsigned int pos; + unsigned int end; + + bool found() { + return pos != 0 && end != 0; + } + }; + + /** + * const v2: a = "as" + * ^-----^ this is the identifier source map. + * + * This function makes sure map.pos starts at `v`, basically eating whitespace. + */ + inline void omitWhitespace(const string &code, FoundSourceMap &map) { + map.pos = eatWhitespace(code, map.pos); + } + + struct Module; + + struct DiagnosticMessage { + string message; + unsigned int ip; //ip of the node/OP + Module * module = nullptr; + DiagnosticMessage() {} + explicit DiagnosticMessage(const string &message, int ip): message(message), ip(ip) {} + }; + + struct FoundSourceLineCharacter { + unsigned int line; + unsigned int pos; + unsigned int end; + }; + + struct Module { + const string bin; + string fileName = "index.ts"; + const string code = ""; //for diagnostic messages only + + vector subroutines; + unsigned int mainAddress; + unsigned int sourceMapAddress; + unsigned int sourceMapAddressEnd; + + vector errors; + Module() {} + + Module(const string_view &bin, const string &fileName, const string &code): bin(bin), fileName(fileName), code(code) { + } + + void clear() { + errors.clear(); + subroutines.clear(); + } + + ModuleSubroutine *getSubroutine(unsigned int index) { + return &subroutines[index]; + } + + ModuleSubroutine *getMain() { + return &subroutines.back(); + } + + string findIdentifier(unsigned int ip) { + auto map = findNormalizedMap(ip); + if (!map.found()) return ""; + return code.substr(map.pos, map.end - map.pos); + } + + FoundSourceMap findMap(unsigned int ip) { + unsigned int found = 0; + for (unsigned int i = sourceMapAddress; i < sourceMapAddressEnd; i += 3 * 4) { + auto mapIp = vm::readUint32(bin, i); + if (mapIp == ip) { + found = i; + break; + } +// if (mapIp > ip) break; +// found = i; + } + + if (found) { + return {vm::readUint32(bin, found + 4), vm::readUint32(bin, found + 8)}; + } + return {0, 0}; + } + + FoundSourceMap findNormalizedMap(unsigned int ip) { + auto map = findMap(ip); + if (map.found()) omitWhitespace(code, map); + return map; + } + + /** + * Converts FindSourceMap{x,y} to + */ + FoundSourceLineCharacter mapToLineCharacter(FoundSourceMap map) { + unsigned int pos = 0; + unsigned int line = 0; + while (pos < map.pos) { + std::size_t lineStart = code.find('\n', pos); + if (lineStart == std::string::npos) { + return {.line = line, .pos = map.pos - pos, .end = map.end - pos}; + } else if (lineStart > map.pos) { + //dont overshot + break; + } + pos = lineStart + 1; + line++; + } + return {.line = line, .pos = map.pos - pos, .end = map.end - pos}; + } + + void printErrors() { + for (auto &&e: errors) { + if (e.ip) { + auto map = findNormalizedMap(e.ip); + + if (map.found()) { + std::size_t lineStart = code.rfind('\n', map.pos); + lineStart = lineStart == std::string::npos ? 0 : lineStart + 1; + + std::size_t lineEnd = code.find('\n', map.end); + if (lineEnd == std::string::npos) lineEnd = code.size(); + std::cout << cyan << fileName << ":" << yellow << map.pos << ":" << map.end << reset << " - " << red << "error" << reset << " TS0000: " << e.message << "\n\n"; + std::cout << code.substr(lineStart, lineEnd - lineStart - 1) << "\n"; + auto space = map.pos - lineStart; + std::cout << std::string(space, ' ') << red << "^" << reset << "\n\n"; + continue; + } + } + std::cout << " " << e.message << "\n"; + } + std::cout << "Found " << errors.size() << " errors in " << fileName << "\n"; + } + }; + + inline void parseHeader(shared &module) { + auto &bin = module->bin; + auto end = bin.size(); + for (unsigned int i = 0; i < end; i++) { + const auto op = (OP) bin[i]; + switch (op) { + case OP::Jump: { + i = vm::readUint32(bin, i + 1) - 1; //minus 1 because for's i++ + break; + } + case OP::SourceMap: { + unsigned int size = vm::readUint32(bin, i + 1); + module->sourceMapAddress = i + 1 + 4; + i += 4 + size; + module->sourceMapAddressEnd = i; + break; + } + case OP::Subroutine: { + unsigned int nameAddress = vm::readUint32(bin, i + 1); + auto name = nameAddress ? vm::readStorage(bin, nameAddress) : ""; + unsigned int address = vm::readUint32(bin, i + 5); + i += 8; + module->subroutines.push_back(ModuleSubroutine(name, address)); + break; + } + case OP::Main: { + module->mainAddress = vm::readUint32(bin, i + 1); + module->subroutines.push_back(ModuleSubroutine("main", module->mainAddress)); + return; + } + } + } + + throw std::runtime_error("No OP::Main found"); + } +} \ No newline at end of file diff --git a/src/checker/types.h b/src/checker/types.h index f786fda..f18927b 100644 --- a/src/checker/types.h +++ b/src/checker/types.h @@ -3,16 +3,14 @@ #include "../core.h" #include "./utils.h" #include "../utf.h" +#include "../hash.h" +#include "../enum.h" #include #include #include #include #include - -#define MAGIC_ENUM_RANGE_MIN 0 -#define MAGIC_ENUM_RANGE_MAX 512 - -#include "magic_enum.hpp" +#include namespace ts::checker { } @@ -23,9 +21,34 @@ namespace ts::vm { using std::to_string; using std::make_shared; using std::reference_wrapper; + using ts::utf::eatWhitespace; using namespace ts::checker; - enum class TypeKind: unsigned char { + struct HashString { + uint64_t hash; +// uint64_t i; + string_view text; + + HashString() { } + + HashString(const string_view &text): text(text) { + hash = hash::runtime_hash(text); + } + + string getString() { + return string(text); + } + + bool operator ==(HashString &other) { + return getHash() == other.getHash(); + } + + uint64_t getHash() { + return hash; + } + }; + + enum class TypeKind: int { Never, Any, Unknown, @@ -61,6 +84,7 @@ namespace ts::vm { Intersection, Array, + Tuple, TupleMember, EnumMember, @@ -86,161 +110,64 @@ namespace ts::vm { } }; - struct ModuleSubroutine { - string_view name; - unsigned int address; - bool exported = false; - sharedOpt result = nullptr; - sharedOpt narrowed = nullptr; //when control flow analysis sets a new value - ModuleSubroutine(string_view name, unsigned int address): name(name), address(address) {} - }; +//todo make configurable +//#define TS_PROFILE - struct Module; + struct ProfilerData { + vector> comparisons; + vector instantiations; + unsigned int typeCount = magic_enum::enum_count(); - struct DiagnosticMessage { - string message; - unsigned int ip; //ip of the node/OP - shared module = nullptr; - DiagnosticMessage() {} - explicit DiagnosticMessage(const string &message, unsigned int &ip): message(message), ip(ip) {} - }; - - struct FoundSourceMap { - unsigned int pos; - unsigned int end; - - bool found() { - return pos != 0 && end != 0; + ProfilerData() { + instantiations.resize(typeCount); + comparisons.resize(typeCount); + for (auto &&c: comparisons) c.resize(typeCount); } }; - - struct FoundSourceLineCharacter { - unsigned int line; - unsigned int pos; - unsigned int end; - }; - - /** - * const v2: a = "as" - * ^-----^ this is the identifier source map. - * - * This function makes sure map.pos starts at `v`, basically eating whitespace. - */ - inline void omitWhitespace(const string &code, FoundSourceMap &map) { - map.pos = eatWhitespace(code, map.pos); - } - - struct Module { - const string bin; - string fileName = "index.ts"; - const string code = ""; //for diagnostic messages only - - vector subroutines; - unsigned int mainAddress; - unsigned int sourceMapAddress; - unsigned int sourceMapAddressEnd; - - vector errors; - Module() {} - - Module(const string_view &bin, const string &fileName, const string &code): bin(bin), fileName(fileName), code(code) { - } + struct Profiler { +#ifdef TS_PROFILE + ProfilerData data; +#endif void clear() { - errors.clear(); - subroutines.clear(); - } - - ModuleSubroutine *getSubroutine(unsigned int index) { - return &subroutines[index]; - } - - ModuleSubroutine *getMain() { - return &subroutines.back(); - } - - string findIdentifier(unsigned int ip) { - auto map = findNormalizedMap(ip); - if (!map.found()) return ""; - return code.substr(map.pos, map.end - map.pos); +#ifdef TS_PROFILE + data = ProfilerData(); +#endif } - FoundSourceMap findMap(unsigned int ip) { - unsigned int found = 0; - for (unsigned int i = sourceMapAddress; i < sourceMapAddressEnd; i += 3 * 4) { - auto mapIp = readUint32(bin, i); - if (mapIp == ip) { - found = i; - break; - } -// if (mapIp > ip) break; -// found = i; - } - - if (found) { - return {readUint32(bin, found + 4), readUint32(bin, found + 8)}; - } - return {0, 0}; + void instantiate(TypeKind kind) { +#ifdef TS_PROFILE + data.instantiations[(int)kind]++; +#endif } - FoundSourceMap findNormalizedMap(unsigned int ip) { - auto map = findMap(ip); - if (map.found()) omitWhitespace(code, map); - return map; - } - - /** - * Converts FindSourceMap{x,y} to - */ - FoundSourceLineCharacter mapToLineCharacter(FoundSourceMap map) { - unsigned int pos = 0; - unsigned int line = 0; - while (pos < map.pos) { - std::size_t lineStart = code.find('\n', pos); - if (lineStart == std::string::npos) { - return {.line = line, .pos = map.pos - pos, .end = map.end - pos}; - } else if (lineStart > map.pos) { - //dont overshot - break; - } - pos = lineStart + 1; - line++; - } - return {.line = line, .pos = map.pos - pos, .end = map.end - pos}; - } - - void printErrors() { - for (auto &&e: errors) { - if (e.ip) { - auto map = findNormalizedMap(e.ip); - - if (map.found()) { - std::size_t lineStart = code.rfind('\n', map.pos); - lineStart = lineStart == std::string::npos ? 0 : lineStart + 1; - - std::size_t lineEnd = code.find('\n', map.end); - if (lineEnd == std::string::npos) lineEnd = code.size(); - std::cout << cyan << fileName << ":" << yellow << map.pos << ":" << map.end << reset << " - " << red << "error" << reset << " TS0000: " << e.message << "\n\n"; - std::cout << code.substr(lineStart, lineEnd - lineStart - 1) << "\n"; - auto space = map.pos - lineStart; - std::cout << std::string(space, ' ') << red << "^" << reset << "\n\n"; - continue; - } - } - std::cout << " " << e.message << "\n"; - } - std::cout << "Found " << errors.size() << " errors in " << fileName << "\n"; + void compare(const shared &left, const shared &right) { +#ifdef TS_PROFILE + data.comparisons[(int)left->kind][(int)right->kind]++; +#endif } }; + static Profiler profiler; + template struct BrandKind: Base ... { constexpr static const TypeKind KIND = T; BrandKind() { this->kind = T; + profiler.instantiate(T); } }; + struct Module; + + template + sharedOpt to(const sharedOpt &p) { + if (!p) return nullptr; + if (T::KIND != TypeKind::Unknown && p->kind != T::KIND) return nullptr; + return reinterpret_pointer_cast(p); + } + struct TypeNever: BrandKind {}; struct TypeAny: BrandKind {}; struct TypeUnknown: BrandKind { @@ -264,21 +191,26 @@ namespace ts::vm { explicit TypeTemplateLiteral(const vector> &types): types(types) {} }; - struct TypeUnion: BrandKind { - vector> types; - }; - struct TypeRest: BrandKind { shared type; explicit TypeRest(shared type): type(std::move(type)) { } }; + struct TypeArray: BrandKind { + shared type; + explicit TypeArray(shared type): type(std::move(type)) { + } + }; + struct TypeTupleMember: BrandKind { shared type; bool optional = false; string_view name = ""; + explicit TypeTupleMember() { + } + explicit TypeTupleMember(shared type): type(std::move(type)) { } }; @@ -296,12 +228,26 @@ namespace ts::vm { struct TypeLiteral: BrandKind { public: - string *literalText = nullptr; - string_view literal; + string *dynamicString = nullptr; + HashString literal; TypeLiteralType type; + + TypeLiteral() {} + + explicit TypeLiteral(const HashString literal, TypeLiteralType type): literal(literal), type(type) { + + } + + explicit TypeLiteral(const string_view &literal, TypeLiteralType type): literal(HashString(literal)), type(type) { + + } + void append(const string_view &text) { - if (!literalText) literalText = new string(literal); - literalText->append(text); + if (!dynamicString) { + dynamicString = new string(literal.text); + } + dynamicString->append(text); + literal = HashString(*dynamicString); } void append(int n) { @@ -309,15 +255,56 @@ namespace ts::vm { } virtual ~TypeLiteral() { - delete literalText; + delete dynamicString; + } + + bool equal(const shared &other) { + return type == other->type && literal.getHash() == other->literal.getHash(); +// if (type != other->type) return false; +// if (!literalText && !other->literalText) return literal == other->literal; +// if (!literalText && other->literalText) return literal == *other->literalText; +// if (literalText && !other->literalText) return *literalText == other->literal; +// return *literalText == *other->literalText; } string_view text() { - return literalText ? *literalText : literal; + return dynamicString ? *dynamicString : literal.text; + } + }; + + struct TypeUnion: BrandKind { + vector> types; + bool indexed = false; + bool onlyLiterals = false; + std::unordered_set literalIndex; + + void index() { + onlyLiterals = true; + for (auto &&t: types) { + if (t->kind != TypeKind::Literal) { + onlyLiterals = false; + break; + } + } + if (onlyLiterals) { + for (auto &&t: types) { + literalIndex.insert(to(t)->literal.hash); + } + } } - TypeLiteral() {} - TypeLiteral(const string_view literal, TypeLiteralType type): literal(literal), type(type) {} + bool fastLiteralCheck() { + if (!indexed) index(); + return onlyLiterals; + } + + bool includes(const shared &type) { + if (type->kind == TypeKind::Literal) { + return literalIndex.contains(to(type)->literal.hash); + } + + return false; + } }; struct TypeParameter: BrandKind { @@ -340,7 +327,7 @@ namespace ts::vm { }; struct TypeProperty: BrandKind { - string_view name; //todo change to something that is number | string | symbol, same for MethodSignature + HashString name; //todo change to something that is number | string | symbol, same for MethodSignature bool optional = false; bool readonly = false; shared type; @@ -348,22 +335,23 @@ namespace ts::vm { }; struct TypePropertySignature: BrandKind { - string_view name; //todo change to something that is number | string | symbol, same for MethodSignature + HashString name; //todo change to something that is number | string | symbol, same for MethodSignature bool optional = false; bool readonly = false; shared type; + TypePropertySignature() {} TypePropertySignature(const shared &type): type(type) {} }; struct TypeMethodSignature: BrandKind { - string_view name; + HashString name; bool optional = false; vector> parameters; shared returnType; }; struct TypeMethod: BrandKind { - string_view name; + HashString name; bool optional = false; vector> parameters; shared returnType; @@ -380,13 +368,6 @@ namespace ts::vm { explicit TypeObjectLiteral(vector> types): types(types) {} }; - template - sharedOpt to(const sharedOpt &p) { - if (!p) return nullptr; - if (T::KIND != TypeKind::Unknown && p->kind != T::KIND) return nullptr; - return reinterpret_pointer_cast(p); - } - string stringify(shared type) { //todo: recursive types @@ -437,7 +418,7 @@ namespace ts::vm { } case TypeKind::PropertySignature: { auto n = to(type); - string r = (n->readonly ? "readonly " : "") + string(n->name); + string r = (n->readonly ? "readonly " : "") + n->name.getString(); if (n->optional) r += "?"; return r + ": " + stringify(n->type); } @@ -464,6 +445,9 @@ namespace ts::vm { r += stringify(fn->returnType); return r; } + case TypeKind::Array: { + return "Array<" + stringify(to(type)->type) + ">"; + } case TypeKind::Tuple: { auto tuple = to(type); string r = "["; diff --git a/src/checker/types2.h b/src/checker/types2.h new file mode 100644 index 0000000..3334573 --- /dev/null +++ b/src/checker/types2.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include "../enum.h" + +namespace ts::vm2 { + using std::string; + using std::string_view; + + enum class TypeKind: unsigned char { + Unknown, + Never, + Any, + String, + Number, + Boolean, + Literal, + PropertySignature, + ObjectLiteral, + Union, + Tuple, + TupleMember, + }; + + enum TypeFlag: unsigned int { + Readonly = 1 << 0, + Optional = 1 << 1, + StringLiteral = 1 << 2, + NumberLiteral = 1 << 3, + BooleanLiteral = 1 << 4, + True = 1 << 5, + False = 1 << 6, + UnprovidedArgument = 1 << 7, + }; + + struct Type { + TypeKind kind; + unsigned int ip; + string_view text; + /** see TypeFlag */ + unsigned int flag; + uint64_t hash; +// string_view text2; + Type *type; +// Type * type2; + vector types; + }; + +// struct TypeMeta { +// string_view typeName; +// }; +// +//#define BaseProps(T) \ +// TypeKind kind = T; \ +// unsigned int ip; \ +// TypeMeta meta; +// +// struct Type { +// BaseProps(TypeKind::Unknown); +// }; +// +// struct TypeNever { +// BaseProps(TypeKind::Never); +// }; +// +// struct TypeAny { +// BaseProps(TypeKind::Any); +// }; +// +// struct TypeLiteral { +// BaseProps(TypeKind::Literal); +// unsigned char type; +// string_view value; +// }; +// +// struct TypePropertySignature { +// BaseProps(TypeKind::PropertySignature); +// string_view name; +// Type *type; +// }; +// +// struct TypeObjectLiteral { +// BaseProps(TypeKind::ObjectLiteral); +// vector types; +// }; +// +// struct TypeUnion { +// BaseProps(TypeKind::Union); +// vector types; +// }; +// +// struct TypeTuple { +// BaseProps(TypeKind::Tuple); +// vector types; +// }; +// +// struct TypeTupleMember { +// BaseProps(TypeKind::TupleMember); +// string_view name; +// Type *type; +// }; +} + +template<> +struct fmt::formatter: formatter { + template + auto format(ts::vm2::TypeKind p, FormatContext &ctx) { + return formatter::format(magic_enum::enum_name(p), ctx); + } +}; diff --git a/src/checker/utils.h b/src/checker/utils.h index d77543e..6b047a1 100644 --- a/src/checker/utils.h +++ b/src/checker/utils.h @@ -4,33 +4,38 @@ #include #include -namespace ts::checker { +namespace ts::vm { using std::vector; using std::string_view; - uint32_t readUint32(const vector &bin, unsigned int offset) { + inline uint32_t readUint32(const vector &bin, unsigned int offset) { return *(uint32_t *) (bin.data() + offset); } - uint32_t readUint32(const string_view &bin, unsigned int offset) { + inline uint32_t readUint32(const string_view &bin, unsigned int offset) { return *(uint32_t *) (bin.begin() + offset); } - void writeUint32(vector &bin, unsigned int offset, uint32_t value) { + inline void writeUint32(vector &bin, unsigned int offset, uint32_t value) { if (offset + 4 > bin.size()) bin.resize(bin.size() + 4); *(uint32_t *) (bin.data() + offset) = value; } - uint16_t readUint16(const vector &bin, unsigned int offset) { + inline uint16_t readUint16(const vector &bin, unsigned int offset) { return *(uint16_t *) (bin.data() + offset); } - uint16_t readUint16(const string_view &bin, unsigned int offset) { + inline uint16_t readUint16(const string_view &bin, unsigned int offset) { return *(uint16_t *) (bin.begin() + offset); } - void writeUint16(vector &bin, unsigned int offset, uint16_t value) { + inline void writeUint16(vector &bin, unsigned int offset, uint16_t value) { if (offset + 2 > bin.size()) bin.resize(bin.size() + 2); *(uint16_t *) (bin.data() + offset) = value; } + + inline string_view readStorage(const string_view &bin, const uint32_t offset) { + const auto size = readUint16(bin, offset); + return string_view(reinterpret_cast(bin.data() + offset + 2), size); + } } \ No newline at end of file diff --git a/src/checker/vm.h b/src/checker/vm.h index e1c5b04..2d6c67f 100644 --- a/src/checker/vm.h +++ b/src/checker/vm.h @@ -24,11 +24,6 @@ namespace ts::vm { using instructions::OP; using instructions::ErrorCode; - inline string_view readStorage(const string_view &bin, const uint32_t offset) { - const auto size = readUint16(bin, offset); - return string_view(reinterpret_cast(bin.data() + offset + 2), size); - } - inline bool isConditionTruthy(shared node) { if (auto n = to(node)) return n->text() == "true"; return false; @@ -282,22 +277,27 @@ namespace ts::vm { unsigned int sp = 0; //stack pointer unsigned int initialSp = 0; //initial stack pointer unsigned int variables = 0; //the amount of registered variable slots on the stack. will be subtracted when doing popFrame() - vector variableIPs; //only used when stepper is active +// vector variableIPs; //only used when stepper is active sharedOpt loop = nullptr; - Frame *previous = nullptr; - ModuleSubroutine *subroutine; +// ModuleSubroutine *subroutine; unsigned int size() { return sp - initialSp; } - explicit Frame(Frame *previous = nullptr): previous(previous) { - if (previous) { - sp = previous->sp; - initialSp = previous->sp; - subroutine = previous->subroutine; - } + void fromFrame(Frame &previous) { + sp = previous.sp; + initialSp = previous.sp; +// subroutine = previous.subroutine; } +// +// explicit Frame(Frame *previous = nullptr): previous(previous) { +// if (previous) { +// sp = previous->sp; +// initialSp = previous->sp; +// subroutine = previous->subroutine; +// } +// } }; static unsigned char emptyTuple[] = { @@ -319,40 +319,50 @@ namespace ts::vm { shared stringToNum(VM &vm); - inline void parseHeader(shared &module) { - auto &bin = module->bin; - for (unsigned int i = 0; i < bin.size(); i++) { - const auto op = (OP) bin[i]; - switch (op) { - case OP::Jump: { - i = readUint32(bin, i + 1) - 1; //minus 1 because for's i++ - break; - } - case OP::SourceMap: { - unsigned int size = readUint32(bin, i + 1); - module->sourceMapAddress = i + 1 + 4; - i += 4 + size; - module->sourceMapAddressEnd = i; - break; - } - case OP::Subroutine: { - unsigned int nameAddress = readUint32(bin, i + 1); - auto name = nameAddress ? readStorage(bin, nameAddress) : ""; - unsigned int address = readUint32(bin, i + 5); - i += 8; - module->subroutines.push_back(ModuleSubroutine(name, address)); - break; - } - case OP::Main: { - module->mainAddress = readUint32(bin, i + 1); - module->subroutines.push_back(ModuleSubroutine("main", module->mainAddress)); - return; - } - } + class MemoryPool { + public: + vector typeLiterals; + shared defaultTypeLiteral = make_shared(); + shared defaultTypeTupleMember = make_shared(); + shared defaultTypeObjectLiteral = make_shared(); + shared defaultPropertySignature = make_shared(); + shared defaultUnknown = make_shared(); + + vector unknowns; + + MemoryPool() { + unknowns.reserve(1000); +// typeLiterals.reserve(1000); } - throw runtime_error("No OP::Main found"); - } + void makeUnknown() { + unknowns.emplace_back(); +// return defaultUnknown; +// return make_shared(); + } + + shared makePropertySignature(const shared &type) { + defaultPropertySignature->type = type; + return defaultPropertySignature; + } + + shared makeTupleMember(const shared &type) { + defaultTypeTupleMember->type = type; + return defaultTypeTupleMember; + } + + shared makeObjectLiteral() { + return defaultTypeObjectLiteral; + } + + shared makeTypeLiteral(const string_view &literal, TypeLiteralType type) { +// return make_shared(literal, type); + return defaultTypeLiteral; +// auto t = make_shared(std::move(typeLiterals.emplace_back(literal, type))); +// typeLiterals.pop_back(); +// return t; + } + }; class VM { public: @@ -360,8 +370,8 @@ namespace ts::vm { * Linked list of subroutines to execute. For each external call this subroutine will be changed. */ sharedOpt subroutine = nullptr; - Frame *frame = nullptr; - + MemoryPool pool; + vector frames; vector> stack; //when a OP is processes, its instruction pointer is stores here, and used in push() to set the ip to the new type generated by this OP. //diagnostics/debugger can use this information to map the type to the sourcecode. @@ -378,7 +388,6 @@ namespace ts::vm { } ~VM() { - delete frame; } void printErrors() { @@ -406,11 +415,12 @@ namespace ts::vm { next->end = module->bin.size(); next->previous = subroutine; subroutine = next; - frame = new Frame(frame); - frame->subroutine = subroutine->subroutine; + frames.emplace_back(); + if (frames.size() > 1) frames.back().fromFrame(frames[frames.size() - 2]); } void run(shared module) { + profiler.clear(); prepare(module); process(); } @@ -435,13 +445,14 @@ namespace ts::vm { next->previous = subroutine; next->depth = subroutine ? subroutine->depth + 1 : 0; - frame = new Frame(frame); - frame->subroutine = next->subroutine; + frames.emplace_back(); + if (frames.size() > 1) frames.back().fromFrame(frames[frames.size() - 2]); +// frames.back().subroutine = next->subroutine; if (arguments) { //we move x arguments from the old stack to the new one - frame->previous->sp -= arguments; - frame->initialSp -= arguments; + frames[frames.size() - 2].sp -= arguments; + frames.back().initialSp -= arguments; } if (loopRunning) subroutine->ip++; //`subroutine` is set to something new in next line, so for() increments its ip, not ours @@ -455,59 +466,58 @@ namespace ts::vm { * Read frame types without popping frame. */ span> readFrame() { - auto start = frame->initialSp + frame->variables; - return {stack.data() + start, frame->sp - start}; + auto start = frames.back().initialSp + frames.back().variables; + return {stack.data() + start, frames.back().sp - start}; } span> popFrame() { // auto frameSize = frame->initialSp - frame->sp; // while (frameSize--) stack.pop(); - auto start = frame->initialSp + frame->variables; - span> sub{stack.data() + start, frame->sp - start}; + auto start = frames.back().initialSp + frames.back().variables; + span> sub{stack.data() + start, frames.back().sp - start}; // std::span> sub{stack.begin() + frame->initialSp, frameSize}; - auto previous = frame->previous; - delete frame; - frame = previous; + frames.pop_back(); return sub; } unsigned int frameSize() { - return frame->initialSp - frame->sp; + return frames.back().initialSp - frames.back().sp; } void pushFrame() { - frame = new Frame(frame); - frame->subroutine = subroutine->subroutine; + frames.emplace_back(); + if (frames.size() > 1) frames.back().fromFrame(frames[frames.size() - 2]); +// frames.back().subroutine = subroutine->subroutine; } shared &last() { - if (frame->sp == 0) throw runtime_error("stack is empty. Can not return last"); - return stack[frame->sp - 1]; + if (frames.back().sp == 0) throw runtime_error("stack is empty. Can not return last"); + return stack[frames.back().sp - 1]; } - shared &pop() { - if (frame->sp == 0) throw runtime_error("stack is empty. Can not pop"); - frame->sp--; - if (frame->sp < frame->initialSp) { + shared pop() { + if (frames.back().sp == 0) throw runtime_error("stack is empty. Can not pop"); + frames.back().sp--; + if (frames.back().sp < frames.back().initialSp) { throw runtime_error("Popped through frame"); } - return stack[frame->sp]; + return std::move(stack[frames.back().sp]); } sharedOpt frameEntryAt(unsigned int position) { - auto i = frame->initialSp + position; - if (i > frame->sp) return nullptr; + auto i = frames.back().initialSp + position; + if (i > frames.back().sp) return nullptr; return stack[i]; } void push(const shared &type) { - type->ip = ip; - if (frame->sp >= stack.size()) { - stack.push_back(type); - } else { - stack[frame->sp] = type; - } - frame->sp++; +// type->ip = ip; +// if (frames.back().sp >= stack.size()) { +// stack.push_back(type); +// } else { +// stack[frames.back().sp] = type; +// } +// frames.back().sp++; } void print() { @@ -521,22 +531,22 @@ namespace ts::vm { current = current->previous; } - auto currentFrame = frame; - auto frameId = 0; - while (currentFrame) { - debug(" # Frame {}: {} ({}->{}) stack frame entries ({})", - frameId, currentFrame->sp - currentFrame->initialSp, currentFrame->initialSp, currentFrame->sp, - currentFrame->loop ? "loop" : ""); - if (currentFrame->sp && currentFrame->initialSp != currentFrame->sp) { -// auto sub = slice(stack, currentFrame->initialSp, currentFrame->sp); -// auto j = currentFrame->initialSp; -// for (auto &&i: sub) { -// debug(" - {}: {}", j++, i ? stringify(i) : "empty"); -// } - } - currentFrame = currentFrame->previous; - frameId++; - } +// auto currentFrame = frame; +// auto frameId = 0; +// while (currentFrame) { +// debug(" # Frame {}: {} ({}->{}) stack frame entries ({})", +// frameId, currentFrame->sp - currentFrame->initialSp, currentFrame->initialSp, currentFrame->sp, +// currentFrame->loop ? "loop" : ""); +// if (currentFrame->sp && currentFrame->initialSp != currentFrame->sp) { +//// auto sub = slice(stack, currentFrame->initialSp, currentFrame->sp); +//// auto j = currentFrame->initialSp; +//// for (auto &&i: sub) { +//// debug(" - {}: {}", j++, i ? stringify(i) : "empty"); +//// } +// } +// currentFrame = currentFrame->previous; +// frameId++; +// } } void report(DiagnosticMessage message) { @@ -548,458 +558,479 @@ namespace ts::vm { report(DiagnosticMessage(message, node->ip)); } + void extends() { + auto right = pop(); + } + void process() { while (subroutine) { + auto &bin = subroutine->module->bin; for (; subroutine->active && subroutine->ip < subroutine->end; subroutine->ip++) { ip = subroutine->ip; - const auto op = (OP) subroutine->module->bin[subroutine->ip]; + const auto op = (OP) bin[subroutine->ip]; // debug("[{}] OP {} ({} -> {})", subroutine->depth, op, subroutine->ip, (unsigned int) op); switch (op) { - case OP::Jump: { - subroutine->ip = readUint32(subroutine->module->bin, 1) - 1; //minus 1 because for's i++ - break; - } - case OP::Extends: { - auto right = pop(); - auto left = pop(); -// debug("{} extends {} => {}", stringify(left), stringify(right), isAssignable(right, left)); - const auto valid = isExtendable(left, right); - push(make_shared(valid ? "true" : "false", TypeLiteralType::Boolean)); - break; - } - case OP::Distribute: { - if (!frame->loop) { - auto type = pop(); - pushFrame(); - frame->loop = make_shared(type); - } - - auto next = frame->loop->next(); - if (!next) { - //done - auto types = popFrame(); - if (types.empty()) { - push(make_shared()); - } else if (types.size() == 1) { - push(types[0]); - } else { - auto result = make_shared(); - for (auto &&v: types) { - if (v->kind != TypeKind::Never) result->types.push_back(v); - } - push(result); - } - //jump over parameter - subroutine->ip += 4; - } else { - //next - const auto loopProgram = readUint32(subroutine->module->bin, subroutine->ip + 1); - subroutine->ip--; //we jump back if the loop is not done, so that this section is executed when the following call() is done - push(next); - call(subroutine->module, loopProgram, 1); - } - break; - } - case OP::JumpCondition: { - auto condition = pop(); - const auto leftProgram = subroutine->parseUint16(); - const auto rightProgram = subroutine->parseUint16(); -// debug("{} ? {} : {}", stringify(condition), leftProgram, rightProgram); - call(subroutine->module, isConditionTruthy(condition) ? leftProgram : rightProgram); - break; - } - case OP::Return: { - auto previous = frame->previous; - //the current frame could not only have the return value, but variables and other stuff, - //which we don't want. So if size is bigger than 1, we move last stack entry to first - // | [T] [T] [R] | - if (frame->size() > 1) { - stack[frame->initialSp] = stack[frame->sp - 1]; - } - previous->sp++; //consume the new stack entry from the function call, making it part of the caller frame - delete frame; - frame = previous; - subroutine->active = false; - subroutine->ip++; //we jump directly to another subroutine, so for()'s increment doesn't apply - subroutine = subroutine->previous; - subroutine->ip--; //for()'s increment applies the wrong subroutine, so we decrease first - if (subroutine->typeArguments == 0) { - subroutine->subroutine->result = last(); - } -// debug("routine {} result: {}", subroutine->name, vm::stringify(last())); - break; - } - case OP::TypeArgument: { - if (frame->size() <= subroutine->typeArguments) { - auto unknown = make_shared(); - unknown->unprovidedArgument = true; - push(unknown); - } - subroutine->typeArguments++; - frame->variables++; - break; - } - case OP::TypeArgumentDefault: { - auto t = frameEntryAt(subroutine->typeArguments - 1); - //t is always set because TypeArgument ensures that - if (t->kind == TypeKind::Unknown && to(t)->unprovidedArgument) { - frame->sp--; //remove unknown type from stack - const auto address = subroutine->parseUint32(); - call(subroutine->module, address, 0); //the result is pushed on the stack - } else { - subroutine->ip += 4; //jump over address - } - break; - } - case OP::TemplateLiteral: { - handleTemplateLiteral(); - break; - } - case OP::Var: { - frame->variables++; - push(make_shared()); - break; - } - case OP::Loads: { - const auto frameOffset = subroutine->parseUint16(); - const auto varIndex = subroutine->parseUint16(); - if (frameOffset == 0) { - push(stack[frame->initialSp + varIndex]); - } else if (frameOffset == 1) { - push(stack[frame->previous->initialSp + varIndex]); - } else if (frameOffset == 2) { - push(stack[frame->previous->previous->initialSp + varIndex]); - } else { - throw runtime_error("frame offset not implement"); - } -// debug("load var {}/{}", frameOffset, varIndex); - break; - } - case OP::Frame: { - pushFrame(); - break; - } - case OP::FunctionRef: { - const auto address = subroutine->parseUint32(); - push(make_shared(address)); - break; - } - case OP::Dup: { - push(last()); - break; - } - case OP::Widen: { - push(widen(pop())); - break; - } - case OP::Set: { - const auto address = subroutine->parseUint32(); - subroutine->module->getSubroutine(address)->narrowed = pop(); - break; - } - case OP::Instantiate: { - const auto arguments = subroutine->parseUint16(); - auto ref = pop(); //FunctionRef/Class - - switch (ref->kind) { - case TypeKind::FunctionRef: { - auto a = to(ref); - call(subroutine->module, a->address, arguments); - break; - } - default: { - throw runtime_error(fmt::format("Can not instantiate {}", ref->kind)); - } - } - break; - } - case OP::PropertySignature: { - auto name = pop(); - auto type = pop(); - auto prop = make_shared(type); - auto valid = true; - switch (name->kind) { - case TypeKind::Literal: { - prop->name = to(name)->text(); //do we need a copy? - break; - } - default: { - valid = false; - report("A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type", type); - } - } - if (valid) { - push(prop); - } - break; + case OP::Halt: { + return; } - case OP::ObjectLiteral: { - auto types = popFrame(); - auto objectLiteral = make_shared(); - pushObjectLiteralTypes(objectLiteral, types); - push(objectLiteral); + case OP::Noop: { break; } - case OP::CallExpression: { - const auto parameterAmount = subroutine->parseUint16(); - - span> parameters{stack.data() + frame->sp - parameterAmount, parameterAmount}; - frame->sp -= parameterAmount; - - auto typeToCall = pop(); - switch (typeToCall->kind) { - case TypeKind::Function: { - auto fn = to(typeToCall); - //it's important to handle parameters/typeArguments first before changing the stack with push() - //since we have a span. - auto end = fn->parameters.size(); - for (unsigned int i = 0; i < end; i++) { - auto parameter = fn->parameters[i]; - auto optional = isOptional(parameter); - if (i > parameters.size() - 1) { - //parameter not provided - if (!optional && !parameter->initializer) { - report(fmt::format("An argument for '{}' was not provided.", parameter->name), parameter); - } - break; - } - auto lvalue = parameters[i]; - auto rvalue = reinterpret_pointer_cast(parameter); - ExtendableStack stack; - if (!isExtendable(lvalue, rvalue, stack)) { - //rerun again with - report(stack.errorMessage()); - } - } - - //we could convert parameters to a tuple and then run isExtendable() on two tuples - break; - } - default: { - throw runtime_error(fmt::format("CallExpression on {} not handled", typeToCall->kind)); - } - } - break; - } - case OP::Call: { - const auto address = subroutine->parseUint32(); - const auto arguments = subroutine->parseUint16(); - call(subroutine->module, address, arguments); - break; - } - case OP::Function: { - //OP::Function has always its own subroutine, so it doesn't have it so own stack frame. - //thus we readFrame() and not popFrame() (since Op::Return pops frame already). - auto types = readFrame(); - auto function = make_shared(); - if (types.size() > 1) { - auto end = std::prev(types.end()); - for (auto iter = types.begin(); iter != end; ++iter) { - function->parameters.push_back(reinterpret_pointer_cast(*iter)); - } - } else if (types.empty()) { - throw runtime_error("No types given for function"); - } - function->returnType = types.back(); - push(function); - break; - } - case OP::Parameter: { - const auto address = subroutine->parseUint32(); - auto name = readStorage(subroutine->module->bin, address); - push(make_shared(name, pop())); - break; - } - case OP::Assign: { - auto rvalue = pop(); - auto lvalue = pop(); - ExtendableStack stack; - if (!isExtendable(lvalue, rvalue, stack)) { - auto error = stack.errorMessage(); - error.ip = ip; - report(error); - } - break; - } - case OP::IndexAccess: { - auto right = pop(); - auto left = pop(); - -// if (!isType(left)) { -// push({ kind: TypeKind::never }); +// case OP::Jump: { +// subroutine->ip = readUint32(bin, 1) - 1; //minus 1 because for's i++ +// break; +// } +// case OP::Extends: { +// auto right = pop(); +// auto left = pop(); +//// debug("{} extends {} => {}", stringify(left), stringify(right), isAssignable(right, left)); +// const auto valid = isExtendable(left, right); +// push(make_shared(valid ? "true" : "false", TypeLiteralType::Boolean)); +// break; +// } +// case OP::Distribute: { +// if (!frames.back().loop) { +// auto type = pop(); +// pushFrame(); +// frames.back().loop = make_shared(type); +// } +// +// auto next = frames.back().loop->next(); +// if (!next) { +// //done +// auto types = popFrame(); +// if (types.empty()) { +// push(make_shared()); +// } else if (types.size() == 1) { +// push(types[0]); +// } else { +// auto result = make_shared(); +// for (auto &&v: types) { +// if (v->kind != TypeKind::Never) result->types.push_back(v); +// } +// push(result); +// } +// //jump over parameter +// subroutine->ip += 4; // } else { - - //todo: we have to put all members of `left` on the stack, since subroutines could be required - // to resolve super classes. - auto t = indexAccess(left, right); -// if (isWithAnnotations(t)) { -// t.indexAccessOrigin = { container: left as TypeObjectLiteral, index: right as Type }; +// //next +// const auto loopProgram = readUint32(bin, subroutine->ip + 1); +// subroutine->ip--; //we jump back if the loop is not done, so that this section is executed when the following call() is done +// push(next); +// call(subroutine->module, loopProgram, 1); +// } +// break; +// } +// case OP::JumpCondition: { +// auto condition = pop(); +// const auto leftProgram = subroutine->parseUint16(); +// const auto rightProgram = subroutine->parseUint16(); +//// debug("{} ? {} : {}", stringify(condition), leftProgram, rightProgram); +// call(subroutine->module, isConditionTruthy(condition) ? leftProgram : rightProgram); +// break; +// } +// case OP::Return: { +// //the current frame could not only have the return value, but variables and other stuff, +// //which we don't want. So if size is bigger than 1, we move last stack entry to first +// // | [T] [T] [R] | +// if (frames.back().size() > 1) { +// stack[frames.back().initialSp] = stack[frames.back().sp - 1]; +// } +// frames.pop_back(); +// frames.back().sp++; //consume the new stack entry from the function call, making it part of the caller frame +// subroutine->active = false; +// subroutine->ip++; //we jump directly to another subroutine, so for()'s increment doesn't apply +// subroutine = subroutine->previous; +// subroutine->ip--; //for()'s increment applies the wrong subroutine, so we decrease first +// if (subroutine->typeArguments == 0) { +// subroutine->subroutine->result = last(); +// } +//// debug("routine {} result: {}", subroutine->name, vm::stringify(last())); +// break; +// } +// case OP::TypeArgument: { +// if (frames.back().size() <= subroutine->typeArguments) { +// auto unknown = make_shared(); +// unknown->unprovidedArgument = true; +// push(unknown); +// } +// subroutine->typeArguments++; +// frames.back().variables++; +// break; +// } +// case OP::TypeArgumentDefault: { +// auto t = frameEntryAt(subroutine->typeArguments - 1); +// //t is always set because TypeArgument ensures that +// if (t->kind == TypeKind::Unknown && to(t)->unprovidedArgument) { +// frames.back().sp--; //remove unknown type from stack +// const auto address = subroutine->parseUint32(); +// call(subroutine->module, address, 0); //the result is pushed on the stack +// } else { +// subroutine->ip += 4; //jump over address +// } +// break; +// } +// case OP::TemplateLiteral: { +// handleTemplateLiteral(); +// break; +// } +// case OP::Var: { +// frames.back().variables++; +// push(make_shared()); +// break; +// } +// case OP::Loads: { +// const auto frameOffset = subroutine->parseUint16(); +// const auto varIndex = subroutine->parseUint16(); +// if (frameOffset == 0) { +// push(stack[frames.back().initialSp + varIndex]); +// } else if (frameOffset == 1) { +// push(stack[frames[frames.size() - 2].initialSp + varIndex]); +// } else if (frameOffset == 2) { +// push(stack[frames[frames.size() - 2].initialSp + varIndex]); +// } else { +// throw runtime_error("frame offset not implement"); +// } +//// debug("load var {}/{}", frameOffset, varIndex); +// break; +// } +// case OP::Frame: { +// pushFrame(); +// break; +// } +// case OP::FunctionRef: { +// const auto address = subroutine->parseUint32(); +// push(make_shared(address)); +// break; +// } +// case OP::Dup: { +// push(last()); +// break; +// } +// case OP::Widen: { +// push(widen(pop())); +// break; +// } +// case OP::Set: { +// const auto address = subroutine->parseUint32(); +// subroutine->module->getSubroutine(address)->narrowed = pop(); +// break; +// } +// case OP::Instantiate: { +// const auto arguments = subroutine->parseUint16(); +// auto ref = pop(); //FunctionRef/Class +// +// switch (ref->kind) { +// case TypeKind::FunctionRef: { +// auto a = to(ref); +// call(subroutine->module, a->address, arguments); +// break; +// } +// default: { +// throw runtime_error(fmt::format("Can not instantiate {}", ref->kind)); // } - -// t.parent = undefined; - push(t); // } - break; - } - case OP::Rest: { - push(make_shared(pop())); - break; - } - case OP::TupleMember: { - push(make_shared(pop())); - break; - } - case OP::TupleNamedMember: { - break; - } - case OP::Tuple: { - auto types = popFrame(); - - //short path for [...x, y]; - if (types.size() == 2 && to(types[0])->type->kind == TypeKind::Rest) { - //todo: we need a heuristic to determine if the array `x` actually can be mutated - auto t = to(to(types[0])->type); - if (t->type->kind == TypeKind::Tuple && to(types[1])->type->kind != TypeKind::Rest) { - auto result = to(t->type); - result->types.push_back(to(types[1])); - push(result); - break; - } - } - - auto tuple = make_shared(); - for (auto &&type: types) { - if (type->kind != TypeKind::TupleMember) { - debug("No tuple member in stack frame, but {}", type->kind); - continue; - } - auto member = reinterpret_pointer_cast(type); - if (member->type->kind == TypeKind::Rest) { - auto rest = to(member->type); - if (rest->type->kind == TypeKind::Tuple) { - for (auto &&sub: to(rest->type)->types) { - tuple->types.push_back(sub); - } - } else { - tuple->types.push_back(member); - } - } else { - tuple->types.push_back(member); - } - } - push(tuple); - break; - } - case OP::Initializer: { - auto t = pop(); - auto l = last(); - switch (l->kind) { - case TypeKind::Parameter:to(l)->initializer = t; - break; - } - break; - } - case OP::Optional: { - auto l = last(); - switch (l->kind) { - case TypeKind::TupleMember: to(l)->optional = true; - break; - case TypeKind::Parameter: to(l)->optional = true; - break; - case TypeKind::PropertySignature: to(l)->optional = true; - break; - } - break; - } - case OP::Readonly: { - auto l = last(); - switch (l->kind) { - case TypeKind::PropertySignature: to(l)->readonly = true; - break; - } - break; - } - case OP::True: push(make_shared("true", TypeLiteralType::Boolean)); - break; - case OP::False: push(make_shared("false", TypeLiteralType::Boolean)); - break; - case OP::String: push(make_shared()); - break; - case OP::Number: push(make_shared()); - break; - case OP::Boolean: push(make_shared()); - break; - case OP::Unknown: push(make_shared()); - break; - case OP::Undefined: push(make_shared()); - break; - case OP::Void: push(make_shared()); - break; - case OP::Never: push(make_shared()); - break; - case OP::Any: push(make_shared()); - break; - case OP::Symbol: push(make_shared()); - break; - case OP::Object: push(make_shared()); - break; - case OP::Union: { - auto types = popFrame(); - if (types.size() == 2) { - if (types[0]->kind == TypeKind::Literal && types[1]->kind == TypeKind::Literal) { - auto first = to(types[0]); - auto second = to(types[1]); - if (first->type == TypeLiteralType::Boolean && second->type == TypeLiteralType::Boolean) { - if (first->text() != second->text()) { - push(make_shared()); - break; - } - } - } - } - auto t = make_shared(); - for (auto &&v: types) t->types.push_back(v); - push(t); - break; - } - case OP::NumberLiteral: { - const auto address = subroutine->parseUint32(); - push(make_shared(readStorage(subroutine->module->bin, address), TypeLiteralType::Number)); - break; - } - case OP::BigIntLiteral: { - const auto address = subroutine->parseUint32(); - push(make_shared(readStorage(subroutine->module->bin, address), TypeLiteralType::Bigint)); - break; - } - case OP::StringLiteral: { - const auto address = subroutine->parseUint32(); - auto text = readStorage(subroutine->module->bin, address); - push(make_shared(text, TypeLiteralType::String)); - break; - } - case OP::Error: { - const auto code = (instructions::ErrorCode) subroutine->parseUint16(); - switch (code) { - case ErrorCode::CannotFind: { - report(DiagnosticMessage(fmt::format("Cannot find name '{}'", subroutine->module->findIdentifier(ip)), ip)); - break; - } - default: { - report(DiagnosticMessage(fmt::format("{}", code), ip)); - } - } - break; - } - default: { - throw runtime_error(fmt::format("unhandled op {}", op)); - } +// break; +// } +// case OP::PropertySignature: { +// auto name = pop(); +// auto type = pop(); +// auto prop = pool.makePropertySignature(type); +// auto valid = true; +// switch (name->kind) { +// case TypeKind::Literal: { +// prop->name = to(name)->text(); //do we need a copy? +// break; +// } +// default: { +// valid = false; +// report("A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type", type); +// } +// } +// if (valid) { +// push(prop); +// } +// break; +// } +// case OP::ObjectLiteral: { +// auto types = popFrame(); +// auto objectLiteral = pool.makeObjectLiteral(); +// pushObjectLiteralTypes(objectLiteral, types); +// push(objectLiteral); +// break; +// } +// case OP::CallExpression: { +// const auto parameterAmount = subroutine->parseUint16(); +// +// span> parameters{stack.data() + frames.back().sp - parameterAmount, parameterAmount}; +// frames.back().sp -= parameterAmount; +// +// auto typeToCall = pop(); +// switch (typeToCall->kind) { +// case TypeKind::Function: { +// auto fn = to(typeToCall); +// //it's important to handle parameters/typeArguments first before changing the stack with push() +// //since we have a span. +// auto end = fn->parameters.size(); +// for (unsigned int i = 0; i < end; i++) { +// auto parameter = fn->parameters[i]; +// auto optional = isOptional(parameter); +// if (i > parameters.size() - 1) { +// //parameter not provided +// if (!optional && !parameter->initializer) { +// report(fmt::format("An argument for '{}' was not provided.", parameter->name), parameter); +// } +// break; +// } +// auto lvalue = parameters[i]; +// auto rvalue = reinterpret_pointer_cast(parameter); +// ExtendableStack stack; +// if (!isExtendable(lvalue, rvalue, stack)) { +// //rerun again with +// report(stack.errorMessage()); +// } +// } +// +// //we could convert parameters to a tuple and then run isExtendable() on two tuples +// break; +// } +// default: { +// throw runtime_error(fmt::format("CallExpression on {} not handled", typeToCall->kind)); +// } +// } +// break; +// } +// case OP::Call: { +// const auto address = subroutine->parseUint32(); +// const auto arguments = subroutine->parseUint16(); +// call(subroutine->module, address, arguments); +// break; +// } +// case OP::Function: { +// //OP::Function has always its own subroutine, so it doesn't have it so own stack frame. +// //thus we readFrame() and not popFrame() (since Op::Return pops frame already). +// auto types = readFrame(); +// auto function = make_shared(); +// if (types.size() > 1) { +// auto end = std::prev(types.end()); +// for (auto iter = types.begin(); iter != end; ++iter) { +// function->parameters.push_back(reinterpret_pointer_cast(*iter)); +// } +// } else if (types.empty()) { +// throw runtime_error("No types given for function"); +// } +// function->returnType = types.back(); +// push(function); +// break; +// } +// case OP::Parameter: { +// const auto address = subroutine->parseUint32(); +// auto name = readStorage(bin, address); +// push(make_shared(name, pop())); +// break; +// } +// case OP::Assign: { +// auto rvalue = pop(); +// auto lvalue = pop(); +// ExtendableStack stack; +// if (!isExtendable(lvalue, rvalue, stack)) { +// auto error = stack.errorMessage(); +// error.ip = ip; +// report(error); +// } +// break; +// } +// case OP::IndexAccess: { +// auto right = pop(); +// auto left = pop(); +// +//// if (!isType(left)) { +//// push({ kind: TypeKind::never }); +//// } else { +// +// //todo: we have to put all members of `left` on the stack, since subroutines could be required +// // to resolve super classes. +// auto t = indexAccess(left, right); +//// if (isWithAnnotations(t)) { +//// t.indexAccessOrigin = { container: left as TypeObjectLiteral, index: right as Type }; +//// } +// +//// t.parent = undefined; +// push(t); +//// } +// break; +// } +// case OP::Rest: { +// push(make_shared(pop())); +// break; +// } +// case OP::Array: { +// push(make_shared(pop())); +// break; +// } +// case OP::TupleMember: { +// push(pool.makeTupleMember(pop())); +// break; +// } +// case OP::TupleNamedMember: { +// break; +// } +// case OP::Tuple: { +// auto types = popFrame(); +// +// //short path for [...x, y]; +//// if (types.size() == 2 && to(types[0])->type->kind == TypeKind::Rest) { +//// //todo: we need a heuristic to determine if the array `x` actually can be mutated +//// auto t = to(to(types[0])->type); +//// if (t->type->kind == TypeKind::Tuple && to(types[1])->type->kind != TypeKind::Rest) { +//// auto result = to(t->type); +//// result->types.push_back(to(types[1])); +//// push(result); +//// break; +//// } +//// } +// +//// auto tuple = make_shared(); +//// for (auto &&type: types) { +//// if (type->kind != TypeKind::TupleMember) { +//// debug("No tuple member in stack frame, but {}", type->kind); +//// continue; +//// } +//// auto member = reinterpret_pointer_cast(type); +//// if (member->type->kind == TypeKind::Rest) { +//// auto rest = to(member->type); +//// if (rest->type->kind == TypeKind::Tuple) { +//// for (auto &&sub: to(rest->type)->types) { +//// tuple->types.push_back(sub); +//// } +//// } else { +//// tuple->types.push_back(member); +//// } +//// } else { +//// tuple->types.push_back(member); +//// } +//// } +//// push(tuple); +// push(make_shared()); +// break; +// } +// case OP::Initializer: { +// auto t = pop(); +// auto l = last(); +// switch (l->kind) { +// case TypeKind::Parameter:to(l)->initializer = t; +// break; +// } +// break; +// } +// case OP::Optional: { +// auto l = last(); +// switch (l->kind) { +// case TypeKind::TupleMember: to(l)->optional = true; +// break; +// case TypeKind::Parameter: to(l)->optional = true; +// break; +// case TypeKind::PropertySignature: to(l)->optional = true; +// break; +// } +// break; +// } +// case OP::Readonly: { +// auto l = last(); +// switch (l->kind) { +// case TypeKind::PropertySignature: to(l)->readonly = true; +// break; +// } +// break; +// } +// case OP::True: push(make_shared("true", TypeLiteralType::Boolean)); +// break; +// case OP::False: push(make_shared("false", TypeLiteralType::Boolean)); +// break; +// case OP::String: push(make_shared()); +// break; +// case OP::Number: push(make_shared()); +// break; +// case OP::Boolean: push(make_shared()); +// break; +// case OP::Unknown: { +// pool.makeUnknown(); +//// push(); +// break; +// } +// case OP::Undefined: push(make_shared()); +// break; +// case OP::Void: push(make_shared()); +// break; +// case OP::Never: push(make_shared()); +// break; +// case OP::Any: push(make_shared()); +// break; +// case OP::Symbol: push(make_shared()); +// break; +// case OP::Object: push(make_shared()); +// break; +// case OP::Union: { +// auto types = popFrame(); +// if (types.size() == 2) { +// //convert `true|false` into `boolean +// if (types[0]->kind == TypeKind::Literal && types[1]->kind == TypeKind::Literal) { +// auto first = to(types[0]); +// auto second = to(types[1]); +// if (first->type == TypeLiteralType::Boolean && second->type == TypeLiteralType::Boolean) { +// if (!first->equal(second)) { +// push(make_shared()); +// break; +// } +// } +// } +// } +//// auto t = make_shared(); +//// t->types.insert(t->types.begin(), types.begin(), types.end()); +//// push(t); +// push(make_shared()); +// break; +// } +// case OP::NumberLiteral: { +// const auto address = subroutine->parseUint32(); +// push(make_shared(readStorage(bin, address), TypeLiteralType::Number)); +// break; +// } +// case OP::BigIntLiteral: { +// const auto address = subroutine->parseUint32(); +// push(make_shared(readStorage(bin, address), TypeLiteralType::Bigint)); +// break; +// } +// case OP::StringLiteral: { +//// const auto address = subroutine->parseUint32(); +// subroutine->ip += 4; +//// auto text = readStorage(bin, address); +// push(pool.makeTypeLiteral("", TypeLiteralType::String)); +//// push(make_shared()); +// break; +// } +// case OP::Error: { +// const auto code = (instructions::ErrorCode) subroutine->parseUint16(); +// switch (code) { +// case ErrorCode::CannotFind: { +// report(DiagnosticMessage(fmt::format("Cannot find name '{}'", subroutine->module->findIdentifier(ip)), ip)); +// break; +// } +// default: { +// report(DiagnosticMessage(fmt::format("{}", code), ip)); +// } +// } +// break; +// } +// default: { +// throw runtime_error(fmt::format("unhandled op {}", op)); +// } } if (stepper) { if (op == instructions::TypeArgument) { - frame->variableIPs.push_back(ip); +// frames.back().variableIPs.push_back(ip); } subroutine->ip++; // debug("Routine {} (ended={})", subroutine->depth, subroutine->ip == subroutine->end); @@ -1025,8 +1056,8 @@ namespace ts::vm { // to(types[0])->type = TypeLiteralType::String; auto t = to(types[0]); auto res = make_shared(t->literal, TypeLiteralType::String); - if (t->literalText) { - res->literalText = new string(*t->literalText); + if (t->dynamicString) { + res->dynamicString = new string(*t->dynamicString); } push(res); return; diff --git a/src/checker/vm2.cpp b/src/checker/vm2.cpp new file mode 100644 index 0000000..b0a77ee --- /dev/null +++ b/src/checker/vm2.cpp @@ -0,0 +1,398 @@ +#include "./vm2.h" +#include "../hash.h" +#include "./check2.h" + +namespace ts::vm2 { + + void prepare(shared &module) { + parseHeader(module); +// if (activeSubroutine) throw std::runtime_error("Subroutine already running"); + +// activeSubroutineIdx = 0; + activeSubroutine = activeSubroutines.reset(); + frame = frames.reset(); +// frameIdx = 0; + + activeSubroutine->module = module.get(); + activeSubroutine->ip = module->mainAddress; + activeSubroutine->depth = 0; + } + + Type *pop() { + return stack[--sp]; + } + + void push(Type *type) { + stack[sp++] = type; //reinterpret_cast(type); + } + + std::span popFrame() { + auto start = frame->initialSp + frame->variables; + std::span sub{stack.data() + start, sp - start}; + frame = frames.pop(); //&frames[--frameIdx]; + sp = frame->initialSp; + return sub; + } + + void moveFrame(std::vector to) { + auto start = frame->initialSp + frame->variables; +// std::span sub{stack.data() + start, frame->sp - start}; + to.insert(to.begin(), stack.begin() + start, stack.begin() + sp - start); + + frame = frames.pop(); //&frames[--frameIdx]; + sp = frame->initialSp; + } + + void gcFlush() { + for (unsigned int i = 0; i<100; i++) { +// switch (gcQueue[i]->kind) { +// case TypeKind::PropertySignature: { +// pool.propertySignature.deleteElement(reinterpret_cast(gcQueue[i])); +// break; +// } +// case TypeKind::Literal: { +// pool.literal.deleteElement(reinterpret_cast(gcQueue[i])); +// break; +// } +// case TypeKind::ObjectLiteral: { +// pool.objectLiteral.deleteElement(reinterpret_cast(gcQueue[i])); +// break; +// } +// case TypeKind::Union: { +// pool.unions.deleteElement(reinterpret_cast(gcQueue[i])); +// break; +// } +// case TypeKind::Unknown: { +// pool.unknown.deleteElement(reinterpret_cast(gcQueue[i])); +// break; +// } +// case TypeKind::Never: { +// pool.never.deleteElement(reinterpret_cast(gcQueue[i])); +// break; +// } +// default: { +// debug("gc kind {}:{}", i, (int)gcQueue[i]->kind); +// throw std::runtime_error("Unhandled kind for gc"); +// } +// } + } + gcQueueIdx = 0; + } + + void gc(Type *type) { +// debug("gc {}:{}", gcQueueIdx, (int)type->kind); + if (gcQueueIdx>=maxGcSize) { + //garbage collect now + gcFlush(); + } + gcQueue[gcQueueIdx++] = type; + } + + inline void report(DiagnosticMessage message) { + message.module = activeSubroutine->module; + message.module->errors.push_back(message); + } + + inline void report(const string &message, Type *node) { + report(DiagnosticMessage(message, node->ip)); + } + + inline void report(const string &message) { + report(DiagnosticMessage(message, activeSubroutine->ip)); + } + + inline void pushFrame() { + auto next = frames.push(); ///&frames[frameIdx++]; + next->initialSp = sp; + frame = next; + } + + //Returns true if it actually jumped to another subroutine, false if it just pushed its cached type. + inline bool call(unsigned int address, unsigned int arguments) { + auto routine = activeSubroutine->module->getSubroutine(address); + if (routine->narrowed) { + push(routine->narrowed); + return false; + } + if (routine->result && arguments == 0) { + push(routine->result); + return false; + } +// printf("Call!"); + + activeSubroutine->ip++; + auto nextActiveSubroutine = activeSubroutines.push(); //&activeSubroutines[++activeSubroutineIdx]; + nextActiveSubroutine->ip = routine->address; + nextActiveSubroutine->module = activeSubroutine->module; + nextActiveSubroutine->depth = activeSubroutine->depth + 1; + activeSubroutine = nextActiveSubroutine; + + auto nextFrame = frames.push(); //&frames[++frameIdx]; + nextFrame->initialSp = sp; + if (arguments) { + //we move x arguments from the old stack frame to the new one + nextFrame->initialSp -= arguments; + } + frame = nextFrame; + return true; + } + + inline bool isConditionTruthy(Type *type) { + return type->flag & TypeFlag::True; + } + + void process() { + start: + auto &bin = activeSubroutine->module->bin; + while (true) { +// debug("[{}] OP {} {}", activeSubroutine->depth, activeSubroutine->ip, (OP)bin[activeSubroutine->ip]); + switch ((OP) bin[activeSubroutine->ip]) { + case OP::Halt: { +// activeSubroutine = activeSubroutines.reset(); +// frame = frames.reset(); + return; + } + case OP::Never: { + stack[sp++] = allocate(TypeKind::Never); + break; + } + case OP::Any: { + auto item = allocate(TypeKind::Any); + stack[sp++] = allocate(TypeKind::Any); + break; + } + case OP::Frame: { + pushFrame(); + break; + } + case OP::Assign: { + auto rvalue = pop(); + auto lvalue = pop(); + if (!extends(lvalue, rvalue)) { +// auto error = stack.errorMessage(); +// error.ip = ip; + report("not assignable"); + } +// ExtendableStack stack; +// if (!isExtendable(lvalue, rvalue, stack)) { +// auto error = stack.errorMessage(); +// error.ip = ip; +// report(error); +// } + gc(lvalue); + gc(rvalue); + break; + } + case OP::Return: { + //the current frame could not only have the return value, but variables and other stuff, + //which we don't want. So if size is bigger than 1, we move last stack entry to first + // | [T] [T] [R] | + if (frame->size()>1) { + stack[frame->initialSp] = stack[sp - 1]; + } + sp = frame->initialSp + 1; + frame = frames.pop(); //&frames[--frameIdx]; + activeSubroutine = activeSubroutines.pop(); //&activeSubroutines[--activeSubroutineIdx]; + goto start; + break; + } + case OP::Call: { + const auto address = activeSubroutine->parseUint32(); + const auto arguments = activeSubroutine->parseUint16(); + if (call(address, arguments)) { + goto start; + } + break; + } + case OP::JumpCondition: { + auto condition = pop(); + const auto leftProgram = activeSubroutine->parseUint16(); + const auto rightProgram = activeSubroutine->parseUint16(); +// debug("{} ? {} : {}", stringify(condition), leftProgram, rightProgram); + if (call(isConditionTruthy(condition) ? leftProgram : rightProgram, 0)) { + goto start; + } + break; + } + case OP::Extends: { + auto right = pop(); + auto left = pop(); +// debug("{} extends {} => {}", stringify(left), stringify(right), isAssignable(right, left)); + const auto valid = extends(left, right); + auto item = allocate(TypeKind::Literal); + item->flag |= TypeFlag::BooleanLiteral; + item->flag |= valid ? TypeFlag::True : TypeFlag::False; + push(item); +// push(make_shared(valid ? "true" : "false", TypeLiteralType::Boolean)); + break; + } + case OP::Distribute: { + if (!frame->loop) { + auto type = pop(); + if (type->kind == TypeKind::Union) { + pushFrame(); + frame->loop = loops.push(); // new LoopHelper(type); + frame->loop->set(type->types); + } else { + push(type); + const auto loopProgram = vm::readUint32(bin, activeSubroutine->ip + 1); + //jump over parameter + activeSubroutine->ip += 4; + if (call(loopProgram, 1)) { + goto start; + } + break; + } + } + + auto next = frame->loop->next(); + if (!next) { + //done + auto types = popFrame(); + if (types.empty()) { + push(allocate(TypeKind::Never)); + } else if (types.size() == 1) { + push(types[0]); + } else { + auto result = allocate(TypeKind::Union); + for (auto &&v: types) { + if (v->kind != TypeKind::Never) result->types.push_back(v); + } + push(result); + } + loops.pop(); + frame->loop = nullptr; + //jump over parameter + activeSubroutine->ip += 4; + } else { + //next + const auto loopProgram = vm::readUint32(bin, activeSubroutine->ip + 1); + push(next); +// debug("distribute jump {}", activeSubroutine->ip); + activeSubroutine->ip--; //we jump back if the loop is not done, so that this section is executed again when the following call() is done + if (call(loopProgram, 1)) { + goto start; + } + break; +// call(subroutine->module, loopProgram, 1); + } + break; + } + case OP::Loads: { + const auto frameOffset = activeSubroutine->parseUint16(); + const auto varIndex = activeSubroutine->parseUint16(); + if (frameOffset == 0) { + push(stack[frame->initialSp + varIndex]); + } else if (frameOffset == 1) { + push(stack[frames.at(frames.i - 2)->initialSp + varIndex]); + } else if (frameOffset == 2) { + push(stack[frames.at(frames.i - 2)->initialSp + varIndex]); + } else { + throw std::runtime_error("frame offset not implement"); + } +// debug("load var {}/{}", frameOffset, varIndex); + break; + break; + } + case OP::TypeArgument: { + if (frame->size()<=activeSubroutine->typeArguments) { + auto unknown = allocate(TypeKind::Unknown); + unknown->flag |= TypeFlag::UnprovidedArgument; + push(unknown); + } + activeSubroutine->typeArguments++; + frame->variables++; + break; + } + case OP::String: { + stack[sp++] = allocate(TypeKind::String); + break; + } + case OP::Number: { + stack[sp++] = allocate(TypeKind::Number); + break; + } + case OP::Boolean: { + stack[sp++] = allocate(TypeKind::Boolean); + break; + } + case OP::NumberLiteral: { + auto item = allocate(TypeKind::Literal); + const auto address = activeSubroutine->parseUint32(); + item->text = vm::readStorage(bin, address); + item->hash = hash::runtime_hash(item->text); + item->flag |= TypeFlag::NumberLiteral; + stack[sp++] = item; + break; + } + case OP::StringLiteral: { + auto item = allocate(TypeKind::Literal); + const auto address = activeSubroutine->parseUint32(); + item->text = vm::readStorage(bin, address); + item->hash = hash::runtime_hash(item->text); + item->flag |= TypeFlag::StringLiteral; + stack[sp++] = item; + break; + } + case OP::PropertySignature: { + auto nameLiteral = pop(); + auto type = pop(); + auto valid = true; + string_view name; + switch (nameLiteral->kind) { + case TypeKind::Literal: { + name = nameLiteral->text; //do we need a copy? + break; + } + default: { + valid = false; + report("A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type", type); + } + } + gc(nameLiteral); + if (valid) { + auto item = allocate(TypeKind::PropertySignature); + item->type = type; + item->text = name; + push(item); + } else { + //popped types need either be consumed (owned) or deallocated. + gc(type); + } + break; + } + case OP::ObjectLiteral: { + auto item = allocate(TypeKind::ObjectLiteral); + auto types = popFrame(); + item->types.insert(item->types.begin(), types.begin(), types.end()); + stack[sp++] = item; + break; + } + case OP::Union: { + auto item = allocate(TypeKind::Union); + auto types = popFrame(); + item->types.insert(item->types.begin(), types.begin(), types.end()); + stack[sp++] = item; + break; + } + case OP::TupleMember: { + auto item = allocate(TypeKind::TupleMember); + item->type = pop(); + stack[sp++] = item; + break; + } + case OP::Tuple: { + auto item = allocate(TypeKind::Tuple); + auto types = popFrame(); + item->types.insert(item->types.begin(), types.begin(), types.end()); + stack[sp++] = item; + break; + } + default: { + debug("OP {} not handled!", (OP) bin[activeSubroutine->ip]); + } + } + activeSubroutine->ip++; + } + } +}; \ No newline at end of file diff --git a/src/checker/vm2.h b/src/checker/vm2.h new file mode 100644 index 0000000..1b6d886 --- /dev/null +++ b/src/checker/vm2.h @@ -0,0 +1,503 @@ +#pragma once + +#include +#include +#include "./MemoryPool.h" +#include +#include +#include +#include +#include "../core.h" +#include "./utils.h" +#include "./types2.h" +#include "./module2.h" +#include "./instructions.h" + +namespace ts::vm2 { + using instructions::OP; + using std::string_view; + + constexpr auto memoryDefault = 4096 * 10; + +// struct TypeMemoryPool2 { +// MemoryPool unknown; +// MemoryPool never; +// MemoryPool any; +// MemoryPool literal; +// MemoryPool objectLiteral; +// MemoryPool unions; +// MemoryPool tuple; +// MemoryPool tupleMember; +// MemoryPool propertySignature; +// }; + + constexpr auto poolUSize = sizeof(Type) * 1000; + inline MemoryPool poolU; + + inline Type *allocate(TypeKind kind) { + auto item = poolU.allocate(); + item->kind = kind; + return item; + } + + /** + * For each active subroutine this object is created. + */ + struct ActiveSubroutine { + Module *module; +// ActiveSubroutine *previous = nullptr; + + unsigned int ip = 0; //current instruction pointer +// unsigned int index = 0; + unsigned int depth = 0; +// bool active = true; + + unsigned int typeArguments = 0; + +// explicit ProgressingSubroutine(shared module, ModuleSubroutine *subroutine): module(module), subroutine(subroutine) {} + + uint32_t parseUint32() { + auto val = vm::readUint32(module->bin, ip + 1); + ip += 4; + return val; + } + + uint16_t parseUint16() { + auto val = vm::readUint16(module->bin, ip + 1); + ip += 2; + return val; + } + }; + + constexpr auto maxGcSize = 4069; + inline std::array gcQueue; + inline unsigned int gcQueueIdx; + + // The stack does not own Type + inline std::array stack; + inline unsigned int sp = 0; + + struct LoopHelper { + std::span types; + unsigned int i = 0; + + void set(std::span types) { + this->types = types; + i = 0; + } + + Type *next() { + if (i == types.size()) return nullptr; + return types[i++]; + } + }; + + struct Frame { + unsigned int initialSp = 0; //initial stack pointer + unsigned int variables = 0; //the amount of registered variable slots on the stack. will be subtracted when doing popFrame() + LoopHelper *loop = nullptr; + + unsigned int size() { + return sp - initialSp; + } + }; + + template + struct StackPool { + std::array values; + unsigned int i; + + T *at(unsigned int pos) { + return &values[pos]; + } + + T *reset() { + i = 0; + return &values[0]; + } + + T *push() { + return &values[++i]; + } + + T *pop() { + return &values[--i]; + } + }; + + constexpr auto stackSize = 4069; + inline StackPool frames; + inline StackPool activeSubroutines; + inline StackPool loops; + +// inline std::array frames; +// inline unsigned int frameIdx; + +// inline std::array activeSubroutines; +// inline unsigned int activeSubroutineIdx; + + inline Frame *frame = nullptr; + inline ActiveSubroutine *activeSubroutine = nullptr; +// inline TypeMemoryPool2 pool; + + void process(); + + void prepare(shared &module); + + std::span popFrame(); + + static void run(shared module) { +// profiler.clear(); +// pool = TypeMemoryPool2(); + poolU = MemoryPool(); + gcQueueIdx = 0; + sp = 0; + prepare(module); + process(); + } + + void call(shared &module, unsigned int index = 0, unsigned int arguments = 0); + +// struct Type2 { +// string_view typeName; +// TypeKind kind = TypeKind::Never; +//// unsigned int kind = (int)TypeKind::Never; +// //this is the OP position (instruction pointer) of the bytecode. sourcemap is necessary to map it to source positions. +// unsigned int ip = 0; +// }; +// unsigned int called = 0; +// +// typedef void (*instruction_t)(); +// static instruction_t program[] = { +// 0, 0, 0, 0, 0 +// }; +// instruction_t *s = nullptr; +// const char *ip = nullptr; +// const char *bin = nullptr; +// +// inline uint32_t readUint32() { +// return *(uint32_t *) (ip); +// } +// +// static void Noop() { +// ++ip; +// called++; +// (*program[*ip])(); +// } +// +// static void Main() { +// ++ip; +// auto a = readUint32(); +// ip += a; +// (*program[*ip])(); +// } +// +// static void SourceMap() { +// auto size = readUint32(); +// ip += 4 + size; +// +// (*program[*ip])(); +// } +// +// static void Jump() { +// ++ip; +// auto a = readUint32(); +// ip += a; +// (*program[*ip])(); +// } +// +// static void Halt() { +// } +// +//// static instruction_t program[] = { +//// Noop, +//// Jump, +//// Halt, +//// SourceMap, +//// Main +//// }; +// +// void jump(shared &module) { +// //https://christopherschwaab.wordpress.com/2018/04/13/generating-a-threaded-arm-interpreter-with-templates/ +// // We're using the GNU C, labels-as-values extension here. +// void *prog[] = {&&PUSH, (void*)6, +// &&PUSH, (void*)7, +// &&MUL, &&PRINT, &&HALT}; +// +// void **vPC = prog; +// void *stack[4], **sp = stack; +// +// goto +// **vPC++; +// +// PUSH: +// *sp++ = *vPC++; +// goto +// **vPC++; +// +// MUL: +// *(sp - 1) = (void *) (*(long *) (sp - 1) * *(long *) (sp - 2)); +// --sp; +// goto +// **vPC++; +// +// PRINT: +// printf("%li\n", *(long *) sp--); +// goto +// **vPC++; +// +// HALT: +// return; +// } +// +// void run(shared &module) { +// program[0] = Noop; +// program[1] = Jump; +// program[2] = Halt; +// program[3] = SourceMap; +// program[4] = Main; +// +// prepare(module); +// bin = &module->bin[0]; +// s = &program[0]; +// ip = bin + module->mainAddress; +// //replace instruction with actual function addresses +// (*program[*ip])(); +// } +// +// void naive(shared &module) { +// prepare(module); +// auto end = module->bin.size(); +// auto &bin = module->bin; +//// auto a = module->bin[0]; +//// +//// std::array stack; +//// unsigned int sp = 0; +//// +//// std::array pool; +//// unsigned int pp = 0; +//// +//// vector unknowns; +//// unknowns.reserve(300 * 6); +//// +//// auto TypeUknown; +// int ip = module->mainAddress; +// +// for (; ip < end; ip++) { +// const auto op = (OP) bin[ip]; +// +// switch (op) { +// case OP::StringLiteral: { +// ip += 4; +// break; +// } +// case OP::Unknown: { +// called += 4; +// break; +// } +// case OP::Noop: { +//// called = 1800; +//// (TypeUnknown *)(a) = TypeUnknown(); +//// unknowns.emplace_back(); +//// ip++; +//// stack[sp++] = pool[pp++]; +//// stack[0].unprovidedArgument +//// unknowns.push_back(&stack[u++]); +//// unknowns.push_back(std::move(u)); +//// volatile auto a = unknowns[u++]; +//// volatile auto a = new TypeUnknown2(); +//// volatile auto a = std::make_shared(); +// break; +// } +// case OP::Jump: { +// ip = ts::checker::readUint32(bin, 1) - 1; //minus 1 because for's i++ +// break; +// } +// } +// } +// } +// +//// unsigned int called = 0; +// __attribute__((noinline)) static void calledFunc() { +// called++; +//// printf("Hello world\n"); +// } +// +// // Signature of the generated function. +// typedef int (*Func)(void); +// +// // Runtime designed for JIT - it hold relocated functions and controls their lifetime. +// JitRuntime rt; +// +// Func jitBuild() { +// // Holds code and relocation information during code generation. +// CodeHolder code; +// +// // Code holder must be initialized before it can be used. The simples way to initialize +// // it is to use 'Environment' from JIT runtime, which matches the target architecture, +// // operating system, ABI, and other important properties. +// code.init(rt.environment()); +// +// // Emitters can emit code to CodeHolder - let's create 'x86::Assembler', which can emit +// // either 32-bit (x86) or 64-bit (x86_64) code. The following line also attaches the +// // assembler to CodeHolder, which calls 'code.attach(&a)' implicitly. +// a64::Compiler cc(&code); +// +//// FileLogger logger(stdout); +//// code.setLogger(&logger); +// +// // Use the x86::Assembler to emit some code to .text section in CodeHolder: +//// a.mov(x86::eax, 1); // Emits 'mov eax, 1' - moves one to 'eax' register. +//// a.ret(); // Emits 'ret' - returns from a function. +// cc.addFunc(FuncSignatureT()); +// +//// arm::Gp r = cc.newUInt32("r"); +//// arm::Gp fn2 = cc.newUIntPtr("fn"); +//// cc.mov(fn2, (uint64_t) calledFunc); +// +// //found: You can use imm_ptr(puts), that would convert the function pointer into an immediate value +// arm::Gp pCalled = cc.newUIntPtr("called"); +//// arm::Gp pByOne = cc.newUIntPtr("byOne"); +// cc.mov(pCalled, 0); +// +// for (auto i = 0; i < 1; i++) { +// cc.add(pCalled, pCalled, 1); +// } +// +// arm::Gp r = cc.newUIntPtr("r"); +// cc.mov(r, (uint64_t) &called); +// cc.str(pCalled, arm::ptr(r)); +// +//// cc.str(pCalled, arm::ptr((uint64_t)&called)); +// +//// cc.str(pCalled, arm::ptr(&called)); +// +//// InvokeNode *invokeNode; +// +//// for (auto i = 0; i < 300; i++) { +//// cc.invoke(&invokeNode, fn2, FuncSignatureT(CallConvId::kHost)); +//// cc.invoke(&invokeNode, fn2, FuncSignatureT(CallConvId::kHost)); +//// cc.invoke(&invokeNode, fn2, FuncSignatureT(CallConvId::kHost)); +//// cc.invoke(&invokeNode, fn2, FuncSignatureT(CallConvId::kHost)); +//// cc.invoke(&invokeNode, fn2, FuncSignatureT(CallConvId::kHost)); +//// cc.invoke(&invokeNode, fn2, FuncSignatureT(CallConvId::kHost)); +//// } +// +// cc.ret(); +// cc.endFunc(); +// cc.finalize(); +// +// // 'x86::Assembler' is no longer needed from here and can be destroyed or explicitly +// // detached via 'code.detach(&a)' - which detaches an attached emitter from code holder. +// +// // Now add the generated code to JitRuntime via JitRuntime::add(). This function would +// // copy the code from CodeHolder into memory with executable permission and relocate it. +// Func fn; +// Error err = rt.add(&fn, &code); +// +// // It's always a good idea to handle errors, especially those returned from the Runtime. +// if (err) { +// printf("AsmJit failed: %s\n", DebugUtils::errorAsString(err)); +// throw runtime_error("error"); +// } +// +// return fn; +// } +// +// void jitTest() { +// +// auto fn = jitBuild(); +// +// // CodeHolder is no longer needed from here and can be safely destroyed. The runtime now +// // holds the relocated function, which we have generated, and controls its lifetime. The +// // function will be freed with the runtime, so it's necessary to keep the runtime around. +// // +// // Use 'code.reset()' to explicitly free CodeHolder's content when necessary. +// +// // Execute the generated function and print the resulting '1', which it moves to 'eax'. +// int result = fn(); +// +//// CodeHolder code; +//// JitRuntime runtime; +//// +//// a64::Compiler cc(&code); +//// +//// // Call a function. +//// InvokeNode* invokeNode; +//// cc.invoke(&invokeNode, imm((void*)calledFunc), FuncSignatureT(CallConvId::kHost)); +////// cc.ret(); +//// cc.finalize(); +//// +//// code.init(runtime.environment()); +//// +//// Func func; +//// runtime.add(&func, &code); +//// +//// int result = func(); +// } +// +// void jit(shared &module) { +//// a64::Compiler c; +//// c.add(Jump); +// +//// JitRuntime rt; // Runtime specialized for JIT code execution. +//// CodeHolder code; // Holds code and relocation information. +//// code.init(rt.environment()); // Initialize code to match the JIT environment. +//// a64::Assembler a(&code); // Create and attach x86::Assembler to code. +//// a.mov(a64::eax, 1); // Move one to eax register. +//// a.ret(); // Return from function. +//// // ===== x86::Assembler is no longer needed from here and can be destroyed ===== +//// Func fn; // Holds address to the generated function. +//// Error err = rt.add(&fn, &code); // Add the generated code to the runtime. +//// if (err) return 1; // Handle a possible error returned by AsmJit. +//// // ===== CodeHolder is no longer needed from here and can be destroyed ===== +//// int result = fn(); // Execute the generated code. +//// printf("%d\n", result); // Print the resulting "1". +//// // All classes use RAII, all resources will be released before `main()` returns, +//// // the generated function can be, however, released explicitly if you intend to +//// // reuse or keep the runtime alive, which you should in a production-ready code. +//// rt.release(fn); +// +// +//// end = module->bin.size(); +// +//// auto &bin = module->bin; +//// auto a = module->bin[0]; +// +//// std::array stack; +//// unsigned int sp = 0; +//// +//// std::array pool; +//// unsigned int pp = 0; +// +//// vector unknowns; +//// unknowns.reserve(300*6); +// +//// auto TypeUknown; +//// +//// for (; ip < end; ip++) { +//// const auto op = (OP) bin[ip]; +//// +//// switch (op) { +//// case OP::Noop: { +////// (TypeUnknown *)(a) = TypeUnknown(); +////// unknowns.emplace_back(); +////// stack[sp++] = pool[pp++]; +////// stack[0].unprovidedArgument +////// unknowns.push_back(&stack[u++]); +////// unknowns.push_back(std::move(u)); +////// volatile auto a = unknowns[u++]; +////// volatile auto a = new TypeUnknown2(); +////// volatile auto a = std::make_shared(); +//// break; +//// } +//// case OP::Jump: { +//// ip = ts::checker::readUint32(bin, 1) - 1; //minus 1 because for's i++ +//// break; +//// } +//// } +//// } +//// +//// return ip; +// } +} \ No newline at end of file diff --git a/src/core.h b/src/core.h index c0ca893..3ee01de 100644 --- a/src/core.h +++ b/src/core.h @@ -30,18 +30,6 @@ namespace ts { using std::map; using std::cout; - inline constexpr unsigned const_hash(const char *input) { - return *input ? static_cast(*input) + 33 * const_hash(input + 1) : 5381; - } - - inline constexpr unsigned const_hash(const string &input) { - return const_hash(input.c_str()); - } - - inline consteval unsigned operator ""_hash(const char *s, size_t) { - return const_hash(s); - } - //compatible with JavaScript's String.substr string substr(const string &str, int start, optional len = {}); @@ -260,9 +248,13 @@ namespace ts { return std::chrono::high_resolution_clock::now() - start; } - inline void bench(int iterations, const function &callback) { + inline void bench(string title, int iterations, const function &callback) { auto took = benchRun(iterations, callback); - fmt::print("{} iterations took {}ms, {}ms per iteration", iterations, took.count(), took.count()/iterations); + fmt::print("{} {} iterations took {:.12f}ms, {:.12f}ms per iteration\n", title, iterations, took.count(), took.count()/iterations); + } + + inline void bench(int iterations, const function &callback) { + bench("", iterations, callback); } const std::string red("\033[0;31m"); diff --git a/src/enum.h b/src/enum.h new file mode 100644 index 0000000..f032fa4 --- /dev/null +++ b/src/enum.h @@ -0,0 +1,4 @@ + +#define MAGIC_ENUM_RANGE_MIN 0 +#define MAGIC_ENUM_RANGE_MAX 512 +#include "magic_enum.hpp" \ No newline at end of file diff --git a/src/fs.h b/src/fs.h new file mode 100644 index 0000000..2653318 --- /dev/null +++ b/src/fs.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +using std::string; +using std::string_view; + +inline auto fileRead(const string &file) { + std::ifstream t; + t.open(file); + t.seekg(0, std::ios::end); + size_t size = t.tellg(); + std::string buffer(size, ' '); + t.seekg(0); + t.read(&buffer[0], size); + return std::move(buffer); +} + +inline void fileWrite(const string &file, const string_view &content) { + std::ofstream t; + t.open(file); + t << content; +} + +inline bool fileExists(const string &file) +{ + std::ifstream infile(file); + return infile.good(); +} \ No newline at end of file diff --git a/src/gui/debugger_main.cpp b/src/gui/debugger_main.cpp index 61d503e..65f713c 100644 --- a/src/gui/debugger_main.cpp +++ b/src/gui/debugger_main.cpp @@ -1,11 +1,13 @@ #include #include +#include #include "./app.h" #include "../parser2.h" #include "../checker/compiler.h" #include "../checker/vm.h" #include "../checker/debug.h" +#include "../fs.h" #include "TextEditor.h" using std::string; @@ -42,7 +44,7 @@ int main() { editor.SetErrorMarkers(markers); editor.SetShowWhitespaces(false); - editor.SetText(R"( + string code = R"( // Here you can see in real-time what branch the conditional type takes type isNumber = T extends number ? df : "no"; const v2: isNumber = "yes"; @@ -52,7 +54,14 @@ const v2: isNumber = "yes"; type NoNumber = T extends number ? never : T; type Primitive = string | number | boolean; const v3: NoNumber = 34; -)"); +)"; + + editor.SetText(code); + + auto codePath = "debugger.code.txt"; + if (fileExists(codePath)) { + editor.SetText(fileRead(codePath)); + } auto fontDefault = ImGui::GetIO().Fonts->AddFontFromFileTTF("/System/Library/Fonts/SFNS.ttf", 32.0); auto fontMono = ImGui::GetIO().Fonts->AddFontFromFileTTF("/System/Library/Fonts/SFNSMono.ttf", 30.0); @@ -75,23 +84,24 @@ const v3: NoNumber = 34; { checker::Compiler compiler; Parser parser; - auto result = parser.parseSourceFile(fileName, editor.GetText(), ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); + auto result = parser.parseSourceFile(fileName, code, ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); auto program = compiler.compileSourceFile(result); auto bin = program.build(); - module = make_shared(std::move(bin), fileName, editor.GetText()); + module = make_shared(std::move(bin), fileName, code); debugBinResult = checker::parseBin(module->bin); extractErrors(); } auto runProgram = [&] { + debug("runProgram"); checker::Compiler compiler; Parser parser; - auto iterations = 1000; + auto iterations = 100; auto start = std::chrono::high_resolution_clock::now(); shared result; for (auto i = 0; i < iterations; i++) { - result = parser.parseSourceFile(fileName, editor.GetText(), ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); + result = parser.parseSourceFile(fileName, code, ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); } lastExecution.parseTime = (std::chrono::high_resolution_clock::now() - start) / iterations; @@ -110,7 +120,7 @@ const v3: NoNumber = 34; } lastExecution.binaryTime = (std::chrono::high_resolution_clock::now() - start) / iterations; - module = make_shared(std::move(bin), fileName, editor.GetText()); + module = make_shared(std::move(bin), fileName, code); debugBinResult = checker::parseBin(module->bin); start = std::chrono::high_resolution_clock::now(); @@ -122,7 +132,6 @@ const v3: NoNumber = 34; } lastExecution.checkTime = (std::chrono::high_resolution_clock::now() - start) / iterations; -// vm.printErrors(); editor.highlights.clear(); extractErrors(); }; @@ -153,6 +162,9 @@ const v3: NoNumber = 34; // ImGui::Text("%.1f FPS", ImGui::GetIO().Framerate); if (editor.IsTextChanged()) { + debug("IsTextChanged"); + code = editor.GetText(); + fileWrite(codePath, code); runProgram(); } @@ -166,7 +178,7 @@ const v3: NoNumber = 34; { ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_FirstUseEver); - if (ImGui::Begin("Run", nullptr)) { + if (ImGui::Begin("Profiler", nullptr)) { if (ImGui::Button("Execute")) { runProgram(); debugActive = false; @@ -205,6 +217,60 @@ const v3: NoNumber = 34; ImGui::Text(fmt::format("Checking\n{:.6f}ms", lastExecution.checkTime.count()).c_str()); ImGui::EndGroup(); + ImGui::Separator(); + + ImGui::Text("Instantiations"); + ImGui::BeginTable("instantiations", 2, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders); + ImGui::TableSetupColumn("type", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 60); + ImGui::TableSetupColumn("count", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoSort); + ImGui::TableHeadersRow(); + +#ifdef TS_PROFILE + for (unsigned int type = 0; type < vm::profiler.data.typeCount; type++) { + auto count = vm::profiler.data.instantiations[type]; + if (!count) continue; + + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::Text(string(magic_enum::enum_name((vm::TypeKind)type)).c_str()); + + ImGui::TableNextColumn(); + ImGui::Text("%lld", count); + } +#endif + + ImGui::EndTable(); + + ImGui::Text("Comparisons"); + ImGui::BeginTable("comparison", 3, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders); + ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 60); + ImGui::TableSetupColumn("right", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 60); + ImGui::TableSetupColumn("count", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoSort); + ImGui::TableHeadersRow(); + +#ifdef TS_PROFILE + for (unsigned int left = 0; left < vm::profiler.data.typeCount; left++) { + for (unsigned int right = 0; right < vm::profiler.data.typeCount; right++) { + auto count = vm::profiler.data.comparisons[left][right]; + if (!count) continue; + + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::Text(string(magic_enum::enum_name((vm::TypeKind)left)).c_str()); + + ImGui::TableNextColumn(); + ImGui::Text(string(magic_enum::enum_name((vm::TypeKind)right)).c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%lld", count); + } + } +#endif + + ImGui::EndTable(); + + ImGui::PopFont(); // // ImGui::SameLine(); @@ -311,12 +377,12 @@ const v3: NoNumber = 34; if (debugVM.subroutine) { - auto current = debugVM.frame; vector frames; - while (current) { - frames.push_back(current); - current = current->previous; - } +// auto current = debugVM.frame; +// while (current) { +// frames.push_back(current); +// current = current->previous; +// } ImGui::Text("Stack (%d/%d)", frames.size(), debugVM.stack.size()); static auto showNonVariables = false; @@ -333,9 +399,9 @@ const v3: NoNumber = 34; auto frame = (*it); ImGui::PushID(i); - if (!frame->subroutine->name.empty()) { - lastName = frame->subroutine->name; - } +// if (!frame->subroutine->name.empty()) { +// lastName = frame->subroutine->name; +// } if (ImGui::Selectable((string(lastName)).c_str(), selectedFrame == frame)) { selectedFrame = frame; } @@ -366,10 +432,10 @@ const v3: NoNumber = 34; ImGui::Text(" "); ImGui::SameLine(); if (i < selectedFrame->variables) { - auto ip = selectedFrame->variableIPs[i]; - auto identifier = module->findIdentifier(ip); - ImGui::Text(identifier.c_str()); - ImGui::SameLine(); +// auto ip = selectedFrame->variableIPs[i]; +// auto identifier = module->findIdentifier(ip); +// ImGui::Text(identifier.c_str()); +// ImGui::SameLine(); } auto stype = vm::stringify(type); if (stype.size() > 20) stype = stype.substr(0, 20) + "..."; diff --git a/src/gui/graph/LICENSE b/src/gui/graph/LICENSE new file mode 100644 index 0000000..aefba70 --- /dev/null +++ b/src/gui/graph/LICENSE @@ -0,0 +1,13 @@ +https://github.com/gml4gtk/demekgraph + +Copyright 2020 Miroslav Demek + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/src/gui/graph/crossing.hpp b/src/gui/graph/crossing.hpp new file mode 100644 index 0000000..12a0e19 --- /dev/null +++ b/src/gui/graph/crossing.hpp @@ -0,0 +1,303 @@ +#pragma once + +#include +#include +#include +#include + +#include "layering.hpp" +#include "utils.hpp" +#include "report.hpp" + +namespace detail { + +// ---------------------------------------------------------------------------------------------- +// ------------------------------- COUNTING CROSSINGS ----------------------------------------- +// ---------------------------------------------------------------------------------------------- + +/** + * Counts the number of crossings between edges indident on and + * if would be to the left of . + * Takes into account both the ingoing and outgoing edges. + * + * and need to be on the same layer + */ +int crossing_number(const hierarchy& h, vertex_t u, vertex_t v) { + int count = 0; + for (auto out_u : h.g.out_neighbours(u)) { + for (auto out_v : h.g.out_neighbours(v)) { + if (h.pos[out_v] < h.pos[out_u]) { + ++count; + } + } + } + for (auto in_u : h.g.in_neighbours(u)) { + for (auto in_v : h.g.in_neighbours(v)) { + if (h.pos[in_v] < h.pos[in_u]) { + ++count; + } + } + } + return count; +} + +// counts the number of crossings between layers with index 'layer' and 'layer - 1' +int count_layer_crossings(const hierarchy& h, int layer) { + const std::vector& upper = h.layers[layer - 1]; + int count = 0; + + for (int i = 0; i < upper.size() - 1; ++i) { + for ( auto u : h.g.out_neighbours(upper[i]) ) { + int j = h.pos[u]; + for (int s = i + 1; s < upper.size(); ++s) { + for ( auto v : h.g.out_neighbours(upper[s]) ) { + int t = h.pos[v]; + if ( (s > i && t < j) || (s < i && t > j) ) { + ++count; + } + } + } + } + } + + return count; +} + + +// counts the total number of crossings in the hierarchy +int count_crossings(const hierarchy& h) { + int count = 0; + for (int i = 1; i < h.size(); ++i) { + count += count_layer_crossings(h, i); + } + return count; +} + + +// ---------------------------------------------------------------------------------------------- +// ------------------------------- CROSSING REDUCTION ----------------------------------------- +// ---------------------------------------------------------------------------------------------- + +/** + * Interface for a crossing reduction algorithm. + * It reorders each layer of the hierarchy to avoid crossings. + */ +struct crossing_reduction { + + /** + * Executes the algorithm. + * It should leave h in a consistent state - ranking, layers and pos all agree with each other. + */ + virtual void run(hierarchy& h) = 0; + virtual ~crossing_reduction() = default; +}; + + +/** + * Barycentric heurictic for crossing reduction. + * Vertices on each layer are order based on their barycenters - the average position of their neighbours. + */ +class barycentric_heuristic : public crossing_reduction { + unsigned random_iters = 1; + unsigned forgiveness = 7; + bool trans = true; + + std::mt19937 mt; + + vertex_map best_order; + int min_cross; + +public: + barycentric_heuristic() = default; + barycentric_heuristic(int rnd_iters, int max_fails, bool do_transpose) + : random_iters(rnd_iters), forgiveness(max_fails), trans(do_transpose) {} + + void run(hierarchy& h) override { +#ifdef REPORTING + report::base = min_cross; + report::random_runs = max_iters; + report::forgivness = forgiveness; + report::transpose = trans; + report::random_final.clear(); + report::iters = 0; +#endif + + min_cross = init_order(h); + best_order = h.pos; + int base = min_cross; + for (int i = 0; i < random_iters; ++i) { + reduce(h, base); + + if (i != random_iters - 1) { + for (auto& l : h.layers) { + std::shuffle(l.begin(), l.end(), mt); + } + h.update_pos(); + base = init_order(h); + } + } + + for (auto u : h.g.vertices()) { + h.layer(u)[ best_order[u] ] = u; + } + h.update_pos(); + +#ifdef REPORTING + report::final = min_cross; +#endif + } + + int init_order(hierarchy& h) { + barycenter(h, 0); + return count_crossings(h); + } + +private: + // attempts to reduce the number of crossings + void reduce(hierarchy& h, int local_min) { + auto local_order = h.pos; + int fails = 0; + + for (int i = 0; ; ++i) { + + barycenter(h, i); + + if (trans) { +#ifdef FAST + fast_transpose(h); +#else + transpose(h); +#endif + } + + int cross = count_crossings(h); + //std::cout << cross << "\n"; + if (cross < local_min) { + fails = 0; + local_order = h.pos; + local_min = cross; + } else { + fails++; + } + + if (fails >= forgiveness) { + break; + } + } + + if (local_min < min_cross) { + best_order = local_order; + min_cross = local_min; + } + +#ifdef REPORTING + report::iters += i; + report::random_final.push_back(local_min); +#endif + } + + void barycenter(hierarchy& h, int i) { + vertex_map weights(h.g); + + if (i % 2 == 0) { // top to bottom + for (int j = 1; j < h.size(); ++j) { + reorder_layer(h, weights, j, true); + } + } else { // from bottom up + for (int j = h.size() - 2; j >= 0; --j) { + reorder_layer(h, weights, j, false); + } + } + } + + // reorders vertices on a layer 'i' based on their weights + void reorder_layer(hierarchy& h, vertex_map& weights, int i, bool downward) { + auto& layer = h.layers[i]; + for (vertex_t u : layer) { + weights[u] = weight( h.pos, u, downward ? h.g.in_neighbours(u) : h.g.out_neighbours(u) ); + } + std::sort(layer.begin(), layer.end(), [&weights] (const auto& u, const auto& v) { + return weights[u] < weights[v]; + }); + + h.update_pos(); + } + + // calculates the weight of vertex as an average of the positions of its neighbour + template + float weight(const vertex_map& positions, vertex_t u, const T& neighbours) { + unsigned count = 0; + unsigned sum = 0; + for (auto v : neighbours) { + sum += positions[v]; + count++; + } + if (count == 0) { + return positions[u]; + } + return sum / (float)count; + } + + // Heuristic for reducing crossings which repeatedly attempts to swap all ajacent vertices. + void transpose(hierarchy& h) { + bool improved = true; + int k = 0; + while (improved) { + improved = false; + + for (auto& layer : h.layers) { + for (int i = 0; i < layer.size() - 1; ++i) { + int old = crossing_number(h, layer[i], layer[i + 1]); + int next = crossing_number(h, layer[i + 1], layer[i]); + + if ( old > next ) { + improved = true; + h.swap(layer[i], layer[i + 1]); + } + } + } + k++; + } + } + + void fast_transpose(hierarchy& h) { + //std::cout << h << "\n"; + vertex_map eligible(h.g, true); + + bool improved = true; + int iters = 0; + while (improved) { + iters++; + improved = false; + for (auto& layer : h.layers) { + assert(layer.size() >= 1); + for (int i = 0; i < layer.size() - 1; ++i) { + if (eligible.at( layer[i] )) { + int old = crossing_number(h, layer[i], layer[i + 1]); + int next = crossing_number(h, layer[i + 1], layer[i]); + int diff = old - next; + + if ( diff > 0 ) { + improved = true; + h.swap(layer[i], layer[i + 1]); + + if (i > 0) eligible.set( layer[i - 1], true ); + eligible.set( layer[i + 1], true ); + + for (auto u : h.g.neighbours(layer[i])) { + eligible.set( u, true ); + } + for (auto u : h.g.neighbours(layer[i+1])) { + eligible.set( u, true ); + } + } + eligible.set( layer[i], false ); + } + } + } + } + } + +}; + +} // namespace detail \ No newline at end of file diff --git a/src/gui/graph/cycle.hpp b/src/gui/graph/cycle.hpp new file mode 100644 index 0000000..d8c6de9 --- /dev/null +++ b/src/gui/graph/cycle.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +#include "subgraph.hpp" + +namespace detail { + +/** + * Holds the edges that were reversed to remove cycles. + * This struct contains the edges in their reversed form + * i.e. if edge (u, v) cuased a cycle, it is saved as (v, u) + */ +struct feedback_set { + edge_set reversed; + edge_set removed; + std::vector loops; +}; + + +/** + * Interface for a cycle removal algorithm. + */ +struct cycle_removal { + + /** + * Modifies the input graph by reversing certain edges to remove cycles. + * + * @return the reversed edges + */ + virtual feedback_set run(subgraph& g) = 0; + + virtual ~cycle_removal() = default; +}; + + +/** + * Algorithm for removing cycles in a graph using depth first search. + */ +class dfs_removal : public cycle_removal { + + enum class state : char { done, in_progress, unvisited }; + vertex_map marks; + +public: + feedback_set run(subgraph& g) override { + marks.init(g, state::unvisited); + + // find edges participating in cycles + std::vector to_reverse; + std::vector to_remove; + for (auto u : g.vertices()) { + if (marks[u] == state::unvisited) { + dfs(g, u, to_reverse, to_remove); + } + } + + // remove or reverse the edges + feedback_set reversed_edges; + + for (auto e : to_remove) { + g.remove_edge(e); + if (e.from == e.to) + reversed_edges.loops.push_back(e.from); + else + reversed_edges.removed.insert(reversed(e)); + } + + for (auto e : to_reverse) { + g.remove_edge(e); + g.add_edge(reversed(e)); + reversed_edges.reversed.insert(reversed(e)); + } + + return reversed_edges; + } + +private: + + void dfs(subgraph& g, vertex_t u, std::vector& to_reverse, std::vector& to_remove) { + marks[u] = state::in_progress; + + for (auto v : g.out_neighbours(u)) { + if (u == v) { // a loop + to_remove.push_back( {u, u} ); + } else if (marks[v] == state::in_progress) { // there is a cycle + if (g.has_edge(v, u)) { // two-cycle + to_remove.push_back( {u, v} ); + } else { // regular cycle + to_reverse.push_back( {u, v} ); + } + } else if (marks[v] == state::unvisited) { + dfs(g, v, to_reverse, to_remove); + } + } + + marks[u] = state::done; + } +}; + +} //namespace detail diff --git a/src/gui/graph/graph.hpp b/src/gui/graph/graph.hpp new file mode 100644 index 0000000..51a1bfa --- /dev/null +++ b/src/gui/graph/graph.hpp @@ -0,0 +1,143 @@ +#pragma once + +#include +#include +#include +#include + +#include "utils.hpp" +#include "types.hpp" + +/** + * Basic clas for representing a directed graph. + */ +class graph { +public: + std::vector nodeSizes; + + /** + * Add a new vertex to the graph. + * + * @return the identifier of the vertex + */ + vertex_t add_node(float size = 0) { + nodeSizes.push_back(size); + m_out_neighbours.emplace_back(); + m_in_neighbours.emplace_back(); + return m_out_neighbours.size() - 1; + } + + /** + * Add a new edge to the graph. + * + * The behavious is undefined if the same edge is added twice + * or if an identifier other then one returned by add_node() is used. + * + * @param from the identifier of the starting vertex + * @param to the identifier of the ending vertex + * + * @return a reference to the graph for chaining multiple calls + */ + graph& add_edge(vertex_t from, vertex_t to) { + m_out_neighbours[from].push_back(to); + m_in_neighbours[to].push_back(from); + return *this; + } + + /** + * Get the number of vertices in the graph. + * + * @return the number of vertices + */ + unsigned size() const { return m_out_neighbours.size(); } + + /** + * Get an immutable list of all successors. + */ + const std::vector& out_neighbours(vertex_t u) const { return m_out_neighbours[u]; } + /** + * Get an immutable list of all predecessors. + */ + const std::vector& in_neighbours(vertex_t u) const { return m_in_neighbours[u]; } + + /** + * Get an implementation defined object which can be used for iterating through the vertices. + * + * The returned object can be used in a range for loop. + * It provides begin() and end() methods which yield forward iterators for iterating through the vertices. + */ + range vertices() const { return range(0, size(), 1); } + + /** + * Remove the given edge. + * Slow operation - should be avoided if possible. + */ + void remove_edge(vertex_t from, vertex_t to) { + remove_neighour(m_out_neighbours[from], to); + remove_neighour(m_in_neighbours[to], from); + } + + friend std::ostream& operator<<(std::ostream& out, const graph& g) { + for (auto u : g.vertices()) { + out << u << ": ["; + + const char* sep = ""; + for (auto v : g.out_neighbours(u)) { + out << sep << v; + sep = ", "; + } + + out << "]\n"; + } + return out; + } + +private: + std::vector< std::vector > m_out_neighbours; + std::vector< std::vector > m_in_neighbours; + + void remove_neighour(std::vector& neighbours, vertex_t u) { + auto it = std::find(neighbours.begin(), neighbours.end(), u); + if (it != neighbours.end()) { + neighbours.erase(it); + } + } + +}; + +/** + * Builder class for creating graphs by hand. + * + * Should be used with care since the node identifiers are chosen by the caller. + * The chosen identifiers should be a consecutive sequence of numbers [0, n-1] where n is the number of vertices in the graph. + * Otherwise, the unused identifiers in the range [0, max_id] will be added into the graph as vertices + * without any edges which is probably not what you want. + * + * For example if edges (0,2), (2,3), (0,3) are added, + * the resulting graph will contain an aditional vertex with identifier 1. + */ +struct graph_builder { + graph g; + + /** + * Add a new edges with the given identifiers. + */ + graph_builder& add_edge(vertex_t u, vertex_t v) { + add_vertex(u); + add_vertex(v); + g.add_edge(u, v); + return *this; + } + + /** + * Get the resulting graph. + */ + graph build() { return g; } + +private: + void add_vertex(vertex_t u) { + while (u >= g.size()) { + g.add_node(); + } + } +}; diff --git a/src/gui/graph/interface.hpp b/src/gui/graph/interface.hpp new file mode 100644 index 0000000..bcdec09 --- /dev/null +++ b/src/gui/graph/interface.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include "graph.hpp" +#include "layout.hpp" +#include "types.hpp" diff --git a/src/gui/graph/layering.hpp b/src/gui/graph/layering.hpp new file mode 100644 index 0000000..2c73669 --- /dev/null +++ b/src/gui/graph/layering.hpp @@ -0,0 +1,660 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "subgraph.hpp" +#include "report.hpp" + +namespace detail { + +// ---------------------------------------------------------------------------------------------- +// -------------------------------------- HIERARCHY ------------------------------------------- +// ---------------------------------------------------------------------------------------------- + +/** + * Represents the partitioning of a graph into a set of layers. + * Each vertex is in exatly one layer, which is its rank. + */ +struct hierarchy { + vertex_map ranking; + vertex_map pos; + std::vector< std::vector > layers; + subgraph& g; + + hierarchy(subgraph& g) : hierarchy(g, -1) {} + hierarchy(subgraph& g, int val) : ranking(g, val), g(g) {} + + /** + * Calculates the span of an edge - the number of layers the edge crosses. + * The span can be negative if the edge goes from higher layer to a lower one. + */ + int span(vertex_t u, vertex_t v) const { return ranking[v] - ranking[u]; } + + // number of layers + int size() const { return layers.size(); } + + // get the layer of the given vertex + std::vector& layer(vertex_t u) { return layers[ranking[u]]; } + const std::vector& layer(vertex_t u) const { return layers[ranking[u]]; } + + // recalculate positions according to the current layers + void update_pos() { + for (const auto& l : layers) { + int i = 0; + for (auto u : l) { + pos[u] = i++; + } + } + } + + // get the successor/predecessor of u on its layer + vertex_t next(vertex_t u) const { return layers[ ranking[u] ][ pos[u] + 1 ]; } + vertex_t prev(vertex_t u) const { return layers[ ranking[u] ][ pos[u] - 1 ]; } + + // true iff u has a successor/predecessor on its layer + bool has_next(vertex_t u) const { return pos[u] < layer(u).size() - 1; } + bool has_prev(vertex_t u) const { return pos[u] > 0; } + + // Swap two vertices. Updates all necessary attributes. + void swap(vertex_t u, vertex_t v) { + std::swap(pos[u], pos[v]); + std::swap(ranking[u], ranking[v]); + + layers[ ranking[u] ][ pos[u] ] = u; + layers[ ranking[v] ][ pos[v] ] = v; + } + + // is i a valid index in the given layer? + bool valid_pos(int layer_idx, int i) const { + return i >= 0 && i < layers[layer_idx].size(); + } +}; + +std::ostream& operator<<(std::ostream& out, const hierarchy& h) { + /*out << "ranking: ["; + for (auto u : h.g.vertices()) { + out << u << "(" << h.ranking[u] << "), "; + } + out << "]\n"; + for (const auto& l : h.layers) { + for (auto u : l) { + out << u << " "; + } + out << "\n"; + } + return out;*/ + + for (auto& l : h.layers) { + for (auto u : l) { + out << u << " "; + } + out << "\n"; + } + return out; +} + +// ------------------------------------------------------------------------------------------ +// -------------------------------- SPLITING LONG EDGES ----------------------------------- +// ------------------------------------------------------------------------------------------ + + +/** + * Edge which crosses several layers and had to by split into multiple "subedges". + */ +struct long_edge { + edge orig; + std::vector path; + + /* so emplace_back can be used */ + long_edge(edge orig, std::vector path) : orig(orig), path(std::move(path)) {} +}; + +/** + * Splits all edges that span across more than one layer in the provided hierarchy + * into segments that each span across just one layer. + * ( aka converts the hierachy into proper hierarchy ) + * + * @return list of edges which had to be split + */ +std::vector< long_edge > add_dummy_nodes(hierarchy& h) { + std::vector< long_edge > split_edges; + + // find edges to be split + for (auto u : h.g.vertices()) { + for (auto v : h.g.out_neighbours(u)) { + int span = h.span(u, v); + if (span > 1) { + split_edges.emplace_back( edge{u, v}, std::vector{} ); + } + } + } + + // split the found edges + for (auto& [ orig, path ] : split_edges) { + int span = h.span(orig.from, orig.to); + path.push_back(orig.from); + + vertex_t s = orig.from; + for (int i = 0; i < span - 1; ++i) { + vertex_t t = h.g.add_dummy(); + + h.ranking.add_vertex(t); + h.ranking[t] = h.ranking[s] + 1; + h.layers[ h.ranking[t] ].push_back(t); + h.pos.insert( t, h.layers[ h.ranking[t] ].size() - 1 ); + + h.g.add_edge(s, t); + path.push_back(t); + + s = t; + } + path.push_back(orig.to); + h.g.add_edge(s, orig.to); + h.g.remove_edge(orig); + } + + return split_edges; +} + +// -------------------------------------------------------------------------------------- +// ------------------------------ LAYERING -------------------------------------------- +// -------------------------------------------------------------------------------------- + + +/** + * Interface for an algorithm which constructs a hierarchy for a given graph. + */ +struct layering { + virtual hierarchy run(detail::subgraph&) = 0; + virtual ~layering() = default; +}; + + +struct tree_edge { + vertex_t u, v; + int dir; +}; + +std::ostream& operator<<(std::ostream& out, tree_edge e) { + out << edge{e.u, e.v} << "| " << e.dir; + return out; +} + + +struct tree_node { + std::optional parent = std::nullopt; + vertex_t u; + + int cut_value = 0; + int out_cut_value = 0; + + std::vector children; + + // for marking the order of nodes using pre-order traversal + int min; + int order; +}; + + +struct tight_tree { + vertex_map nodes; + vertex_t root; + hierarchy* h; + + tight_tree() = default; + + tight_tree(hierarchy* h, vertex_t root) : nodes(h->g), root(root), h(h) { + for ( auto u : h->g.vertices() ) { + nodes[u].u = u; + } + } + + std::vector& children(vertex_t u) { return nodes[u].children; } + const std::vector& children(vertex_t u) const { return nodes[u].children; } + + vertex_t parent(vertex_t u) const { return *nodes[u].parent; } + + tree_node& node(vertex_t u) { return nodes[u]; } + const tree_node& node(vertex_t u) const { return nodes[u]; } + + int cut_val(vertex_t u, vertex_t v) const { + assert(u == *nodes[v].parent); + return nodes[v].cut_value; + } + + void cut_val(vertex_t u, vertex_t v, int val) { + assert(u == *nodes[v].parent); + nodes[v].cut_value = val; + } + + int out_cut_val(vertex_t u, vertex_t v) const { + assert(u == *nodes[v].parent); + return nodes[v].out_cut_value; + } + + void out_cut_val(vertex_t u, vertex_t v, int val) { + assert(u == *nodes[v].parent); + nodes[v].out_cut_value = val; + } + + void add_child(vertex_t parent, vertex_t child) { + nodes[parent].children.push_back(child); + nodes[child].parent = parent; + } + + void remove_child(vertex_t parent, vertex_t child) { + unlink_child(parent, child); + nodes[child].parent = -1; + } + + void unlink_child(vertex_t parent, vertex_t child) { + nodes[parent].children.erase(std::find(nodes[parent].children.begin(), nodes[parent].children.end(), child)); + } + + /** + * Find which component of the tree falls in, if the tree is split by removing . + * Postorder **must** by calculated first. + */ + vertex_t component(edge e, vertex_t u) { + if (nodes[e.to].min > nodes[u].min || nodes[e.to].order < nodes[u].order) { + return e.from; + } + return e.to; + } + + // 1 if (parent, u) is an edge, -1 if (u, parent) is an edge + int dir(vertex_t u, vertex_t v) const { return sgn( h->span(u, v) ); } + + // find a common ancestor of two vertices, postorder **must** be calculated first + vertex_t common_acestor(vertex_t u, vertex_t v) { + vertex_t ancestor = u; + + int left_min; + int right_order; + if (nodes[u].order < nodes[v].order) { + left_min = nodes[u].min; + right_order = nodes[v].order; + } else { + left_min = nodes[v].min; + right_order = nodes[u].order; + } + + // the common acestor is the first node such that min <= left_min && order >= right_order + while (nodes[ancestor].min > left_min || nodes[ancestor].order < right_order) { + // no need to check if parent exists, root has the smalles min and the largest order + ancestor = *nodes[ancestor].parent; + } + + return ancestor; + } + + template + void for_each(F f, vertex_t u) const { + f(u); + for ( const auto& child : children(u) ) { + for_each(f, child); + } + } + + /** + * Performs a postorder search of the subtree rooted at . + * The first leaf node is given the order . + */ + int postorder_search(vertex_t u, int order) { + node(u).min = order; + for (auto child : children(u)) { + order = postorder_search(child, order); + } + node(u).order = order; + return order + 1; + } + + void print(vertex_t u, int depth, std::ostream& out = std::cout) const { + int indent = 4; + for (int i = 0; i < depth; ++i) { + for (int j = 0; j < indent; ++j) { + out << " "; + } + } + out << u << "("; + out << "dir=" << (u == root ? 0 : dir(parent(u), u)) << " "; + out << "cut=" << node(u).cut_value << " "; + out << "out=" << node(u).out_cut_value << " "; + out << "ord=" << node(u).order << " "; + out << "min=" << node(u).min << " "; + out << ")\n"; + + for ( const auto& child : children(u) ) { + print(child, depth + 1, out); + } + } + + friend std::ostream& operator<<(std::ostream& out, const tight_tree& tree) { + tree.print(tree.root, 0, out); + return out; + } + + /** + * Swaps a non-tree edge for a tree edge and restructures the tree so + * that is the predecessor of all edges in the subtree rooted at + */ + void swap_edges(edge entering, edge leaving) { + vertex_t parent = entering.from; + vertex_t u = entering.to; + while(u != leaving.from) { + //std::cout << u << "\n"; + vertex_t tmp = *nodes[u].parent; + nodes[u].parent = parent; + nodes[parent].children.push_back(u); + parent = u; + u = tmp; + unlink_child(u, parent); + } + } +}; + + +/** + * Network simple algorithm for layering a graph. + */ +class network_simplex_layering : public layering { + tight_tree tree; + +public: + + hierarchy run(subgraph& g) override { + if (g.size() == 0) { + return hierarchy(g); + } + auto h = init_hierarchy(g); + + init_tree(h); + init_cut_values(); + + optimize_edge_length(g, h); + +#ifdef DEBUG_LABELS + for (int i = 0; i < g.size(); ++i) { + debug_labels[i] = std::to_string(i) + "(" + + std::to_string(tree.node(i).parent ? *tree.node(i).parent : -1) + ", " + + std::to_string(tree.node(i).cut_value) + ")"; + } +#endif + + calculate_layers(h); + + return h; + } + +private: + + void calculate_layers(hierarchy& h) { + int min = std::numeric_limits::max(); + int max = std::numeric_limits::min(); + for (auto u : h.g.vertices()) { + if (h.ranking[u] > max) + max = h.ranking[u]; + if (h.ranking[u] < min) + min = h.ranking[u]; + } + + h.layers.resize(max - min + 1); + h.pos.resize(h.g); + + for (auto u : h.g.vertices()) { + h.ranking[u] -= min; + h.layers[ h.ranking[u] ].push_back(u); + h.pos[u] = h.layers[ h.ranking[u] ].size() - 1; + } + } + + + /** + * Assignes each vertex a layer, such that each edge goes from a lower layer to higher one + * and the source vertices are at the lowest layer. + * The resulting ranking is not normalized, meaning that the lowest layer doesnt have to be 0. + * It is also not neccesarly the optimal ranking. + * + * @param g the graph whose vertices are to be assigned to layers + * @return resulting hierarchy, only the ranking of nodes is defined, layers and pos are undefined + */ + hierarchy init_hierarchy(subgraph& g) { + hierarchy h(g, -1); + int curr = 0; + unsigned processed = 0; + + std::vector to_rank; + while (processed < g.size()) { + to_rank.clear(); + for (auto u : g.vertices()) { + if (h.ranking[u] == -1) { + // check if there are any edges going from unranked vertices + bool source = true; + for (auto v : g.in_neighbours(u)) { + if (h.ranking[v] == -1) { + source = false; + break; + } + } + if (source) { + to_rank.push_back(u); + ++processed; + } + } + } + for (auto u : to_rank) { + h.ranking[u] = curr; + } + ++curr; + } + return h; + } + + /** + * Constructs a tree of all vertices + * reachable from the root through tight edges. + */ + int basic_tree(vertex_map& done, const hierarchy& h, vertex_t root) { + done.set(root, true); + int added = 1; + + for ( auto u : h.g.neighbours(root) ) { + int span = h.span(root, u); + if ( !done.at(u) && std::abs(span) == 1 ) { + tree.add_child(root, u); + added += basic_tree(done, h, u); + } + } + + return added; + } + + /** + * Finds a spanning tree of tight edges. + * Tight edge is any edge (u, v) for which ranking[v] - ranking[u] == 1. + * + * @param g the graph + * @param ranking ranking of the vertices of the graph + * @return the spanning tree + */ + void init_tree(hierarchy& h) { + const subgraph& g = h.g; + tree = tight_tree( &h, g.vertex(0) ); + vertex_map done(g, false); + + int finished = basic_tree(done, h, tree.root); + + while(finished < g.size()) { + // in the underlying undirected graph find edge (u, v) with the smallest span + // such that u is already in the tree and v is not in the tree + edge e = { 0, 0 }; + int min_span = std::numeric_limits::max(); + for ( auto u : g.vertices() ) { + if ( done.at(u) ) { + for ( auto v : g.neighbours(u) ) { + int span = h.span(u, v); + if ( !done.at(v) && std::abs(span) < std::abs(min_span) ) { + e = { u, v }; + min_span = span; + } + } + } + } + + // make the edge tight by moving all the vertices in the tree + int dir = sgn(min_span); + min_span -= dir; + for (auto u : g.vertices()) { + if (done.at(u)) { + h.ranking[u] += min_span; + } + } + tree.add_child(e.from, e.to); + done.set(e.to, true); + ++finished; + } + + tree.postorder_search(tree.root, 0); + } + + + // Calculates the initial cut values of all edges in the tight tree. + void init_cut_values() { + for ( auto child : tree.children(tree.root) ) { + init_cut_values(tree.root, child); + } + } + + // Calculates cut values of all edges in the subtree rooted at and then the cut value of (, ). + void init_cut_values(vertex_t u, vertex_t v) { + for (auto child : tree.children(v)) { + init_cut_values(v, child); + } + set_cut_value(u, v); + } + + /** + * Calculates cut value of the edge (u, v). + * Requires the cut values of the edges between and its children to be already calculated. + */ + void set_cut_value(vertex_t u, vertex_t v) { + subgraph& g = tree.h->g; + int val = 0; + + for (auto child : tree.children(v)) { + val += tree.dir(u, v) * tree.dir(v, child) * tree.out_cut_val(v, child); + } + + for (auto x : g.neighbours(v)) { + if (tree.component( {u, v}, x ) == u) { + val += tree.dir(x, v) * tree.dir(u, v); + } + } + tree.cut_val(u, v, val); + + for (auto x : g.neighbours(u)) { + if (tree.component( {u, v}, x ) == v) { + val -= tree.dir(u, x) * tree.dir(u, v); + } + } + tree.out_cut_val(u, v, val); + } + + void switch_tree_edges(tree_edge orig, tree_edge entering) { + auto ancestor = tree.common_acestor(entering.u, entering.v); + + tree.swap_edges({entering.u, entering.v},{orig.u, orig.v}); + + tree.postorder_search(ancestor, tree.node(ancestor).min); + fix_cut_values(ancestor, orig.u); + fix_cut_values(ancestor, orig.v); + } + + void fix_cut_values(vertex_t root, vertex_t u) { + while (u != root) { + vertex_t parent = *tree.node(u).parent; + set_cut_value(parent, u); + u = parent; + } + } + + void move_subtree(hierarchy& h, vertex_t root, int d) { + h.ranking[root] += d; + for (auto child : tree.children(root)) { + move_subtree(h, child, d); + } + } + + tree_edge find_entering_edge(const subgraph& g, hierarchy& h, tree_edge leaving) { + tree_edge entering { 0, 0, -leaving.dir }; + int span = std::numeric_limits::max(); + + vertex_t start_component = leaving.dir == 1 ? leaving.v : leaving.u; + vertex_t end_component = start_component == leaving.u ? leaving.v : leaving.u; + + for (auto u : g.vertices()) { + if (tree.component({leaving.u, leaving.v}, u) != start_component) + continue; + + for (auto v : g.out_neighbours(u)) { + if (tree.component({leaving.u, leaving.v}, v) == end_component && h.span(u, v) < span) { + entering.u = entering.dir == 1 ? u : v; + entering.v = entering.dir == 1 ? v : u; + span = h.span(u, v); + } + } + } + + assert(span < std::numeric_limits::max()); + + return entering; + } + + std::optional find_leaving_edge(hierarchy& h) { + for (auto u : h.g.vertices()) { + for (auto v : h.g.out_neighbours(u)) { + if (v != tree.root && tree.parent(v) == u && tree.node(v).cut_value < 0) { + return tree_edge{ u, v, 1 }; + } + if (u != tree.root && tree.parent(u) == v && tree.node(u).cut_value < 0) { + return tree_edge{ v, u, -1 }; + } + } + } + + return std::nullopt; + } + + void optimize_edge_length(const subgraph& g, hierarchy& h) { + int iters = 0; + + while(true) { + auto leaving = find_leaving_edge(h); + if (!leaving) + break; + + tree_edge entering = find_entering_edge(g, h, *leaving); + + switch_tree_edges(*leaving, entering); + + int d = h.span( entering.u, entering.v ); + d = -d + sgn(d); + move_subtree(h, entering.v, d); + + iters++; + } + +#ifdef REPORTING + report::simplex::iters = iters; +#endif + } + +}; + + +} // namespace detail diff --git a/src/gui/graph/layout.hpp b/src/gui/graph/layout.hpp new file mode 100644 index 0000000..3daf656 --- /dev/null +++ b/src/gui/graph/layout.hpp @@ -0,0 +1,171 @@ +#ifndef LAYOUT_HPP +#define LAYOUT_HPP + +#pragma once + +#include +#include + +#include "interface.hpp" +#include "subgraph.hpp" + +#include "cycle.hpp" +#include "layering.hpp" +#include "positioning.hpp" +#include "crossing.hpp" +#include "router.hpp" + +#ifdef CONTROL_CROSSING +bool crossing_enabled = true; +#endif + + +class sugiyama_layout { +public: + sugiyama_layout(graph g) : g(g), original_vertex_count(g.size()) { build(); } + + sugiyama_layout(graph g, attributes attr) + : g(g) + , original_vertex_count(g.size()) + , attrs(attr) { build(); } + + /** + * Returns the positions and sizes of all the vertices in the graph. + */ + const std::vector& vertices() const { return nodes; } + + /** + * Returns the control points for all the edges in the graph. + */ + const std::vector& edges() const { return paths; } + + float width() const { return size.x; } + float height() const { return size.y; } + vec2 dimensions() const { return size; } + + const attributes& attribs() const { return attrs; } + +private: + graph g; + unsigned original_vertex_count; + + detail::vertex_map boxes; + + // the final positions of vertices and control points of edges + std::vector< node > nodes; + std::vector< path > paths; + vec2 size = { 0, 0 }; + + // attributes controling spacing + attributes attrs; + + // algorithms for individual steps of sugiyama framework + std::unique_ptr< detail::cycle_removal > cycle_module = + std::make_unique< detail::dfs_removal >(); + + std::unique_ptr< detail::layering > layering_module = + std::make_unique< detail::network_simplex_layering >(); + + std::unique_ptr< detail::crossing_reduction > crossing_module = + std::make_unique< detail::barycentric_heuristic >(); + + std::unique_ptr< detail::positioning > positioning_module = + std::make_unique< detail::fast_and_simple_positioning >(attrs, nodes, boxes); + + std::unique_ptr< detail::edge_router > routing_module = + std::make_unique< detail::router >(nodes, paths, attrs); + + + void build() { + std::vector< detail::subgraph > subgraphs = detail::split(g); + init_nodes(); + + vec2 start { 0, 0 }; + for (auto& g : subgraphs) { + vec2 dim = process_subgraph(g, start); + start.x += dim.x + attrs.node_dist; + size.x += dim.x + attrs.node_dist; + size.y = std::max(size.y, dim.y); + } + + size.x -= attrs.node_dist; + nodes.resize(original_vertex_count); + } + + + vec2 process_subgraph(detail::subgraph& g, vec2 start) { + + auto reversed_edges = cycle_module->run(g); + detail::hierarchy h = layering_module->run(g); + + auto long_edges = add_dummy_nodes(h); + update_reversed_edges(reversed_edges, long_edges); + update_dummy_nodes(); + +#ifdef CONTROL_CROSSING + if (crossing_enabled) { + crossing->run(h); + } +#else + crossing_module->run(h); +#endif + enlarge_loop_boxes(reversed_edges); + + vec2 dimensions = positioning_module->run(h, start); + + routing_module->run(h, reversed_edges); + + return dimensions; + } + + void update_dummy_nodes() { + boxes.resize(g, { {0, 0}, { 0, 0} }); + + auto i = nodes.size(); + nodes.resize(g.size()); + for (; i < nodes.size(); ++i) { + nodes[i].u = i; + nodes[i].size = 0; + } + } + + + void enlarge_loop_boxes(const detail::feedback_set& r) { + for (auto u : r.loops) { + boxes[u].size.x += attrs.loop_size; + } + } + + + void init_nodes() { + nodes.resize( g.size() ); + boxes.resize( g ); + for ( auto u : g.vertices() ) { + nodes[u].u = u; + nodes[u].size = g.nodeSizes[u];// || attrs.default_node_size; + boxes[u] = { { 2*nodes[u].size, 2*nodes[u].size }, + { nodes[u].size, nodes[u].size } }; + } + } + + + /** + * Checks if any of the reversed edges have been split into a path. + * For each such edge (u, v) saves the first vertex on the path + * from 'u' to 'v' instead of 'v'. + * When reversing the path back, the rest of the path can be easily determined by folowing the dummy nodes + * until the first non-dummy node is reached. + */ + void update_reversed_edges(detail::feedback_set& reversed_edges, const std::vector< detail::long_edge >& long_edges) { + for (const auto& elem : long_edges) { + if (reversed_edges.reversed.remove(elem.orig)) { + reversed_edges.reversed.insert(elem.path[0], elem.path[1]); + } else if (reversed_edges.removed.remove(elem.orig)) { + reversed_edges.removed.insert(elem.path[0], elem.path[1]); + } + } + } +}; + + +#endif diff --git a/src/gui/graph/positioning.hpp b/src/gui/graph/positioning.hpp new file mode 100644 index 0000000..75e8d69 --- /dev/null +++ b/src/gui/graph/positioning.hpp @@ -0,0 +1,467 @@ +#pragma once + +#include +#include +#include +#include + +#include "utils.hpp" +#include "subgraph.hpp" +#include "vec2.hpp" +#include "layering.hpp" + +#ifdef DEBUG_COORDINATE +int produce_layout = 0; +#endif + +namespace detail { + +/** + * Bounding box of a vertex. + * The center is relative to upper left corner. + */ +struct bounding_box { + vec2 size = { 0, 0 }; + vec2 center = { 0, 0 }; +}; + +/** + * Interface for a positioning algorithm that determines the final positions of nodes. + * The y coordinates of nodes on the same layer have to be the same. + */ +struct positioning { + virtual vec2 run(detail::hierarchy& h, vec2 origin) = 0; + virtual ~positioning() = default; +}; + + +/** + * Naive positioning for testing purpouses. + */ +struct test_positioning : public positioning { + std::vector& nodes; + attributes attr; + + test_positioning(attributes attr, std::vector& nodes) + : nodes(nodes) + , attr(attr) {} + + vec2 run(detail::hierarchy& h, vec2 origin) override { + float y = origin.y + attr.layer_dist; + float width = 0; + for (auto layer : h.layers) { + float x = origin.x; + for (auto u : layer) { + x += attr.node_dist + nodes[u].size; + nodes[u].pos = { x, y }; +// nodes[u].size = attr.node_size; + x += nodes[u].size; + } + if ((x - origin.x) > width) { + width = x; + } + y += attr.layer_dist; + } + return { width, y - origin.y }; + } +}; + + +class fast_and_simple_positioning : public positioning { + std::vector& nodes; + attributes attr; + const detail::vertex_map& boxes; + + enum orient { upper_left, lower_left, upper_right, lower_right }; + + std::array< detail::vertex_map, 4 > medians; + std::array< detail::vertex_map, 4 > root; + std::array< detail::vertex_map, 4 > align; + std::array< detail::vertex_map, 4 > sink; // the class of a block + std::array< detail::vertex_map, 4 > shift; + std::array< detail::vertex_map< std::optional >, 4 > x; + + std::array< float, 4 > max; + std::array< float, 4 > min; + + edge_set conflicting; + +public: + fast_and_simple_positioning(attributes attr, + std::vector& nodes, + const detail::vertex_map& boxes) + : nodes(nodes) + , attr(attr) + , boxes(boxes) + { } + + void init(const detail::hierarchy& h) { + for (int i = 0; i < 4; ++i) { + medians[i].resize(h.g); + root[i].resize(h.g); + align[i].resize(h.g); + sink[i].resize(h.g); + shift[i].resize(h.g); + x[i].resize(h.g); + + min[i] = std::numeric_limits::max(); + max[i] = std::numeric_limits::lowest(); + } + + for (auto u : h.g.vertices()) { + for (int j = 0; j < 4; ++j) { + root[j][u] = u; + align[j][u] = u; + sink[j][u] = u; + shift[j][u] = 0; + } + } + } + + vec2 run(detail::hierarchy& h, vec2 origin) override { + init(h); + init_medians(h); + + mark_conflicts(h); + + for (int i = 0; i < 4; ++i) { + vertical_align(h, static_cast(i)); + horizontal_compaction(h, static_cast(i)); + } + +#ifdef DEBUG_COORDINATE + if(produce_layout < 4) { + std::cout << "TYPE: " << produce_layout << "\n"; + for (auto u : h.g.vertices()) { + if (root[produce_layout][u] == u) { + vertex_t v = u; + const char* sep = ""; + do { + std::cout << sep << v; + sep = " -> "; + v = align[produce_layout][v]; + } while (v != u); + std::cout << "\n"; + } + } + std::cout << "\n"; + } +#endif + + return assign_final_coordinates(h, origin); + } + + vec2 assign_final_coordinates(const hierarchy& h, vec2 origin) { + align_layouts(h); + + float y = origin.y; + std::vector vals(4); + for (const auto& layer : h.layers) { + y += biggestSize(nodes, layer); + + for (auto u : layer) { +#ifdef DEBUG_COORDINATE + if(produce_layout < 4) { + nodes[u].pos = vec2{ *x[produce_layout][u] + shift[produce_layout], y }; + } else { +#endif + vals = { *x[0][u], *x[1][u], *x[2][u], *x[3][u] }; + std::sort(vals.begin(), vals.end()); + nodes[u].pos = { (vals[1] + vals[2])/2, y }; +#ifdef DEBUG_COORDINATE + } +#endif + } + y += biggestSize(nodes, layer) + attr.layer_dist; + } + float height = y - attr.layer_dist; + float width = normalize(h, origin.x); + + return { width, height }; + } + + void align_layouts(const hierarchy& h) { + orient min_width_layout = static_cast(0); + for (int i = 1; i < 4; ++i) { + if ( max[min_width_layout] - min[min_width_layout] > max[i] - min[i] ) { + min_width_layout = static_cast(i); + } + } + + for (int i = 0; i < 4; ++i) { + float d = left(static_cast(i)) ? + min[min_width_layout] - min[i] : + max[min_width_layout] - max[i]; + + for (auto u : h.g.vertices()) { + *x[i][u] += d; + } + } + } + + + float normalize(const detail::hierarchy& h, float start) { + float min = std::numeric_limits::max(); + float max = std::numeric_limits::lowest(); + + for(auto u : h.g.vertices()) { + if (nodes[u].pos.x + boxes[u].size.x - boxes[u].center.x > max) { + max = nodes[u].pos.x + boxes[u].size.x - boxes[u].center.x; + } + if (nodes[u].pos.x - boxes[u].center.x < min) { + min = nodes[u].pos.x - boxes[u].center.x; + } + } + + for(auto u : h.g.vertices()) { + nodes[u].pos.x = start + nodes[u].pos.x - min; + } + return max - min; + } + + + void init_medians(const detail::hierarchy& h) { + std::vector neighbours; + for (auto u : h.g.vertices()) { + { + neighbours.insert(neighbours.begin(), h.g.out_neighbours(u).begin(), h.g.out_neighbours(u).end()); + auto [ left, right ] = median(h, u, neighbours); + medians[orient::lower_left][u] = left; + medians[orient::lower_right][u] = right; + neighbours.clear(); + } + + neighbours.insert(neighbours.begin(), h.g.in_neighbours(u).begin(), h.g.in_neighbours(u).end()); + auto [ left, right ] = median(h, u, neighbours); + medians[orient::upper_left][u] = left; + medians[orient::upper_right][u] = right; + neighbours.clear(); + } + } + + template< typename Neighbours > + std::pair median(const hierarchy& h, vertex_t u, Neighbours neigh) { + int count = neigh.size(); + int m = count / 2; + if (count == 0) { + return { u, u }; + } + std::nth_element( + neigh.begin(), + neigh.begin() + m, + neigh.end(), + [&h] (auto u, auto v) { + return h.pos[u] < h.pos[v]; + } + ); + vertex_t right = *(neigh.begin() + m); + if (count % 2 == 1) { + return { right, right }; + } + std::nth_element( + neigh.begin(), + neigh.begin() + m - 1, + neigh.end(), + [&h] (auto u, auto v) { + return h.pos[u] < h.pos[v]; + } + ); + return { *(neigh.begin() + m - 1), right }; + } + + + // Is 'u' tail of an inner segment? + bool is_inner(const detail::hierarchy& h, vertex_t u) { + if (h.g.out_degree(u) != 1 || !h.g.is_dummy(u)) { + return false; + } + return h.g.is_dummy( *h.g.out_neighbours(u).begin() ); + } + + /** + * Vertex 'u' must be a tail of inner segment. + * Returns the position of the head of this inner segment on its layer. + */ + int inner_pos(const hierarchy& h, vertex_t u) { + return h.pos[ *h.g.out_neighbours(u).begin() ]; + } + + /** + * Saves edges causing type 1 conflicts. + * Type 1 conflict occur when non-inner edge crosses inner segment. + * Inner segment is an edge between two dummy vertices. + */ + void mark_conflicts(const hierarchy& h) { + if (h.size() < 4) { + return; + } + for (int i = 1; i < h.size() - 2; ++i) { + int last_pos = 0; + int p = 0; + + for ( int j = 0; j < h.layers[i].size(); ++j ) { + auto& lay = h.layers[i]; + vertex_t u = lay[j]; + + if ( j == h.layers[i].size() - 1 || is_inner(h, u) ) { + int curr_pos = h.layers[i + 1].size(); + + if (is_inner(h, u)) { + curr_pos = inner_pos(h, u); + } + + while(p <= j) { + vertex_t pth = h.layers[i][p]; + for (auto v : h.g.out_neighbours(pth)) { + if (h.pos[v] < last_pos || h.pos[v] > curr_pos) { + conflicting.insert(pth, v); + } + } + ++p; + } + last_pos = curr_pos; + } + } + } + } + + // for each vertex choose the vertex it will be verticaly aligned to + void vertical_align(const detail::hierarchy& h, orient dir) { + detail::vertex_map& align = this->align[dir]; + detail::vertex_map& root = this->root[dir]; + + for ( auto l : idx_range(h.size(), !up(dir)) ) { + auto layer = h.layers[l]; + + int m_pos = left(dir) ? 0 : h.g.size(); + int d = left(dir) ? 1 : -1; + + for ( auto k : idx_range(layer.size(), !left(dir)) ) { + vertex_t u = layer[k]; + + for ( auto m : { medians[dir][u], medians[invert_horizontal(dir)][u] } ) { + if (m != u && !is_conflicting(u, m, dir) && d*h.pos[m] >= d*m_pos) { + align[m] = u; + root[u] = root[m]; + align[u] = root[m]; + + m_pos = h.pos[m] + d; + break; + } + } + } + } + } + + void horizontal_compaction(const hierarchy& h, orient dir) { + auto& sink = this->sink[dir]; + auto& root = this->root[dir]; + auto& shift = this->shift[dir]; + auto& x = this->x[dir]; + + for (auto u : h.g.vertices()) { + if (root[u] == u) { + place_block(h, u, dir); + } + } + + for (auto i : idx_range(h.size(), up(dir))) { + auto layer = h.layers[i]; + for (auto u : layer) { + x[u] = *x[ root[u] ] + shift[ sink[ root[u] ] ]; + + if (*x[u] > max[dir]) { + max[dir] = *x[u]; + } + + if (*x[u] < min[dir]) { + min[dir] = *x[u]; + } + } + } + } + + void place_block(const detail::hierarchy& h, vertex_t u, orient type) { + if (x[type][u]) { + return; + } + + auto& sink = this->sink[type]; + auto& root = this->root[type]; + auto& shift = this->shift[type]; + auto& align = this->align[type]; + auto& x = this->x[type]; + + x[u] = 0; + int d = left(type) ? -1 : 1; + vertex_t w = u; + do { + if ( !is_last_idx(h.pos[w], h.layer(w).size(), !left(type)) ) { + vertex_t v = h.layer(w)[ h.pos[w] + d ]; + vertex_t rv = root[v]; + + place_block(h, rv, type); + + if (sink[u] == u) + sink[u] = sink[rv]; + + if (sink[u] != sink[rv]) { + float new_shift = shift[ sink[rv] ] + *x[rv] - *x[u] - d*(left(type) ? node_dist(v, w) : node_dist(w, v)); + shift[ sink[u] ] = left(type) ? std::max(shift[ sink[u] ], new_shift) + : std::min(shift[ sink[u] ], new_shift); + } else { + float new_x = *x[rv] - d*(left(type) ? node_dist(v, w) : node_dist(w, v)); + x[u] = !left(type) ? std::min(*x[u], new_x) + : std::max(*x[u], new_x); + } + } + w = align[w]; + } while (w != u); + } + + + bool left(orient dir) const { return dir == orient::lower_left || dir == orient::upper_left; } + bool up(orient dir) const { return dir == orient::upper_left || dir == orient::upper_right; } + + // do the vertex 'u' and its median 'med' participate in type 1 conflict? + bool is_conflicting(vertex_t u, vertex_t med, orient dir) { + return ( up(dir) && conflicting.contains(med, u) ) || + ( !up(dir) && conflicting.contains(u, med) ); + } + + float node_dist(vertex_t u, vertex_t v) { + return boxes[u].size.x - boxes[u].center.x + + boxes[v].center.x + + attr.node_dist; + } + + // Get the range of indexes. + range idx_range(std::size_t size, bool desc) const { + if ( desc ) { + return { static_cast(size) - 1, -1, -1 }; + } + return { 0, static_cast(size), 1 }; + } + + // Is i one of the endpoints of the interval <0, size - 1> + bool is_last_idx(int i, std::size_t size, bool desc) const { + return (desc && i == size - 1) || (!desc && i == 0); + } + + orient invert_horizontal(orient dir) { + switch(dir) { + case orient::upper_left: + return orient::upper_right; + case orient::upper_right: + return orient::upper_left; + case orient::lower_left: + return orient::lower_right; + case orient::lower_right: + return orient::lower_left; + } + assert(false); + } + +}; + +} // namespace detail \ No newline at end of file diff --git a/src/gui/graph/report.hpp b/src/gui/graph/report.hpp new file mode 100644 index 0000000..edb2cf3 --- /dev/null +++ b/src/gui/graph/report.hpp @@ -0,0 +1,25 @@ +#pragma once + +#ifdef REPORTING + +namespace report { + + // network simplex + namespace simplex { + inline int iters = 0; + } + + //namespace crossing { + // crossing stuff + inline int random_runs = -1; + inline int forgivness = -1; + inline bool transpose = false; + + inline int base = 0; + inline int final = 0; + inline int iters = 0; + inline std::vector random_final; + //} +} + +#endif diff --git a/src/gui/graph/router.hpp b/src/gui/graph/router.hpp new file mode 100644 index 0000000..ed5112d --- /dev/null +++ b/src/gui/graph/router.hpp @@ -0,0 +1,388 @@ +#pragma once + +#include "types.hpp" +#include "subgraph.hpp" + +#include +#include +#include +#include + + +namespace detail { + +struct edge_router { + virtual void run(const hierarchy& h, const feedback_set& rev) = 0; + virtual ~edge_router() = default; +}; + +class router : public edge_router { + const float min_sep = 5; + const float loop_angle_sep = 5; + + const attributes& attr; + std::vector& nodes; + std::vector& links; + + vertex_map< std::array< float, 4> > shifts; + + vertex_map< bool > has_loop; + +public: + router(std::vector& nodes, std::vector& paths, const attributes& attr) + : attr(attr) + , nodes(nodes) + , links(paths) {} + + void run(const hierarchy& h, const feedback_set& rev) override { + init(h, rev); + calculate_shifts(h); + make_paths(h, rev); + } + +private: + void init(const hierarchy& h, const feedback_set& rev) { + shifts.init( h.g, { 0 } ); + + has_loop.init(h.g, false); + for (auto u : rev.loops) { + has_loop.set(u, true); + } + } + + void calculate_shifts(const hierarchy& h) { + for (const auto l : h.layers) { + for (auto u : l) { + for (auto v : h.g.out_neighbours(u)) { + if (!h.g.is_dummy(u) || !h.g.is_dummy(v)) + set_regular_shifts(h, {u, v}); + } + } + } + unify_dummy_shifts(h); + } + + void make_paths(const hierarchy& h, const feedback_set& rev) { + for (auto u : h.g.vertices()) { + for (auto v : h.g.out_neighbours(u)) { + if (!h.g.is_dummy(u)) make_path(h, rev, u, v); + } + } + + for (auto u : rev.loops) { + make_loop_square(u); + } + } + + void set_regular_shifts(const hierarchy& h, edge e) { + auto dirs = get_dirs(e); + if (dirs.x == 0) + return; + + check_loop(e); + check_loop(reversed(e)); + + // find the possible candidates for an intersection + // if the adjacent node is a dummy vertex we want to check for an intersection with the closest regular vertex as well + auto up = next_non_dummy(h, e.from, dirs.x); + auto down = next_non_dummy(h, e.to, -dirs.x); + auto up_d = next_vertex(h, e.from, dirs.x); + auto down_d = next_vertex(h, e.to, -dirs.x); + + // try all combinations + set_shift(h, e, up, down, dirs); + set_shift(h, e, up, down_d, dirs); + set_shift(h, e, up_d, down, dirs); + set_shift(h, e, up_d, down_d, dirs); + } + + void set_shift(const hierarchy& h, edge e, std::optional up, std::optional down, vec2 dirs) { + auto from = get_center(e.from, dirs); + auto to = get_center(e.to, -dirs); + + // set it to something which the edge will never intersect + vec2 c_up = pos(e.from) - vec2{ dirs.x*10, 0}; + float r_up = 0; + // set it to the actual value if one exists + if (up) { + c_up = pos(*up); + r_up = h.g.is_dummy(*up) ? shifts[*up][dir_idx(dirs)] : nodes[*up].size; + } + + // set it to something which the edge will never intersect + vec2 c_down = pos(e.from) - vec2{ dirs.x*10, 0}; + float r_down = 0; + // set it to the actual value if one exists + if (down) { + c_down = pos(*down); + r_down = h.g.is_dummy(*down) ? shifts[*down][dir_idx(-dirs)] : nodes[*down].size; + } + + bool can_inter_up = sgn(c_up.x - from.x) != sgn(c_up.x - to.x); + bool can_inter_down = sgn(c_down.x - from.x) != sgn(c_down.x - to.x); + + float node_size = attr.default_node_size; //todo: adjust + + // if it is possible for the edge to intersect the vertex + if ( can_inter_up || can_inter_down ) { + float s = get_shift(e.from, dirs); + float t = get_shift(e.to, -dirs); + + bool up_done = false, down_done = false; + while (!up_done || !down_done) { + if (can_inter_up && s <= node_size && line_point_dist(from, to, c_up) <= r_up + min_sep) { + s += 5; + from = pos(e.from) + vec2{ 0, dirs.y*s }; + up_done = false; + } else { + up_done = true; + } + + if (can_inter_down && t <= node_size && line_point_dist(to, from, c_down) <= r_down + min_sep) { + t += 5; + to = pos(e.to) + vec2{ 0, -dirs.y*t }; + down_done = false; + } else { + down_done = true; + } + } + + // clip it + if (s > node_size) s = node_size; + if (t > node_size) t = node_size; + + shifts[e.from][dir_idx(dirs)] = std::max(shifts[e.from][dir_idx(dirs)], s); + shifts[e.to][dir_idx(-dirs)] = std::max(shifts[e.to][dir_idx(-dirs)], t); + } + } + + float get_shift(edge e) { return get_shift(e.from, get_dirs(e)); } + float get_shift(vertex_t u, vec2 dirs) { return get_shift(u, dir_idx(dirs)); } + float get_shift(vertex_t u, int quadrant) { return shifts[u][quadrant]; } + + void unify_dummy_shifts(const hierarchy& h) { + for (int layer_idx = 0; layer_idx < h.size(); ++layer_idx) { + const auto& l = h.layers[layer_idx]; + int j = -1; + for (int i = 0; i < l.size(); ++i) { + if ( !h.g.is_dummy(l[i]) ) { + set_sequence_shifts(h, layer_idx, j + 1, i - 1); + j = i; + } + } + set_sequence_shifts(h, layer_idx, j + 1, l.size() - 1); + } + } + + void set_sequence_shifts(const hierarchy& h, int layer, int start, int end) { + if (end < start) + return; + + const auto& l = h.layers[layer]; + + std::array s = { 0 }; + for (int i = start; i <= end; ++i) { + vertex_t u = l[i]; + assert( h.g.is_dummy(u) ); + + s[0] = std::max(s[0], shifts[u][0]); + s[1] = std::max(s[1], shifts[u][1]); + s[2] = std::max(s[2], shifts[u][2]); + s[3] = std::max(s[3], shifts[u][3]); + } + + for (int i = start; i <= end; ++i) { + shifts[l[i]][0] = s[0]; + shifts[l[i]][1] = s[1]; + shifts[l[i]][2] = s[2]; + shifts[l[i]][3] = s[3]; + } + + fix_regular_ends(h, layer, start, end); + } + + void fix_regular_ends(const hierarchy& h, int layer, int start, int end) { + const auto& l = h.layers[layer]; + + if (h.has_prev(l[start])) { + auto u = h.prev(l[start]); + for (auto v : h.g.neighbours(u)) { + set_regular_shifts(h, { u, v }); + } + } + + if (h.has_next(l[end])) { + auto u = h.next(l[end]); + for (auto v : h.g.neighbours(u)) { + set_regular_shifts(h, { u, v }); + } + } + } + + void check_loop(edge e) { + auto dirs = get_dirs(e); + if (!has_loop.at(e.from) || dir_idx(dirs) == 2 || dir_idx(dirs) == 3) + return; + + float s = get_shift(e.from, dirs); + auto from = get_center(e.from, dirs); + auto to = get_center(e.to, -dirs); + auto intersection = *line_circle_intersection(from, to, nodes[e.from].pos, nodes[e.from].size); + float a = angle(pos(e.from), intersection); + + if (a > attr.loop_angle - loop_angle_sep) { + a = attr.loop_angle - loop_angle_sep; + auto p = angle_point(a, e.from, dirs); + + float t = (pos(e.from).x - p.x)/(p.x - to.x); + s = fabs( pos(e.from).y - (p.y + t*(p.y - to.y)) ); + + assert(s >= 0 && s <= nodes[e.from].size); + + shifts[e.from][dir_idx(dirs)] = s; + } + } + + vec2 get_center(vertex_t u, vec2 dirs) { + return nodes[u].pos + vec2{ 0, dirs.y*get_shift(u, dirs) }; + } + + + float angle(vec2 from, vec2 to) { + auto dir = to - from; + return to_degrees( std::atan(fabs(dir.x)/fabs(dir.y)) ); + } + + vec2 angle_point(float angle, vertex_t u, vec2 dirs) { + return pos(u) + vec2{ dirs.x * nodes[u].size * std::sin(to_radians(angle)), + dirs.y * nodes[u].size * std::cos(to_radians(angle)) }; + } + + + /** + * Get the index of the quadrant points to. + * The indexes are as follows: + * + * 2 | 1 + * ---+---> + * 3 | 0 + * v + */ + int dir_idx(vec2 dirs) const { + if (dirs.x == 1) + return dirs.y != 1; + return 2 + (dirs.y == 1); + } + + vec2 pos(vertex_t u) const { return nodes[u].pos; } + vec2 get_dirs(vec2 v) const { return { sgn(v.x), sgn(v.y) }; } + vec2 get_dirs(edge e) const { return get_dirs(pos(e.to) - pos(e.from)); } + int get_xdir(edge e) const { return sgn(pos(e.to).x - pos(e.from).x); } + + // finds next vertex in the desired direction which is not a dummy vertex + std::optional next_non_dummy(const hierarchy& h, vertex_t u, int d) { + int i = h.pos[u] + d; + while (h.valid_pos(h.ranking[u], i) && h.g.is_dummy( h.layer(u)[i] )) { + i += d; + } + + if (h.valid_pos(h.ranking[u], i)) { + return h.layer(u)[i]; + } + return std::nullopt; + } + + std::optional next_vertex(const hierarchy& h, vertex_t u, int d) { + if (d == -1 && h.has_prev(u)) { + return h.prev(u); + } + if (d == 1 && h.has_next(u)) { + return h.next(u); + } + return std::nullopt; + } + + vertex_t next(const subgraph& g, vertex_t u) { return *g.out_neighbours(u).begin(); } + vertex_t prev(const subgraph& g, vertex_t u) { return *g.in_neighbours(u).begin(); } + + + void make_path(const hierarchy& h, const feedback_set& rev, vertex_t u, vertex_t v) { + auto& g = h.g; + path l{ u, v, {} }; + auto orig = edge{ u, v }; + + l.points.push_back( calculate_port_shifted(u, nodes[v].pos - nodes[u].pos) ); + + while (g.is_dummy(v)) { + auto s = shifts[v][dir_idx(get_dirs(edge{v, u}))]; + if (s > 0) + l.points.push_back( nodes[v].pos + vec2{ 0, -s } ); + + l.points.push_back( nodes[v].pos ); + + auto n = next(g, v); + s = shifts[v][dir_idx(get_dirs(edge{v, n}))]; + if (s > 0) + l.points.push_back( nodes[v].pos + vec2{ 0, s } ); + + u = v; + v = n; + } + + l.points.push_back( calculate_port_shifted(v, nodes[u].pos - nodes[v].pos) ); + l.to = v; + + if (rev.reversed.contains(orig)) { + reverse(l); + } else if (rev.removed.contains(orig)) { + l.bidirectional = true; + } + + links.push_back(std::move(l)); + } + + void reverse(path& l) { + std::swap(l.from, l.to); + for (int i = 0; i < l.points.size()/2; ++i) { + std::swap(l.points[i], l.points[l.points.size() - i - 1]); + } + } + + + void make_loop_square(vertex_t u) { + path l{ u, u, {} }; + l.points.resize(4); + + l.points[0] = angle_point(attr.loop_angle, u, { 1, -1 }); + l.points[3] = angle_point(attr.loop_angle, u, { 1, 1 }); + + l.points[1] = vec2{ pos(u).x + nodes[u].size + attr.loop_size/2, l.points[0].y }; + l.points[2] = vec2{ pos(u).x + nodes[u].size + attr.loop_size/2, l.points[3].y }; + + links.push_back(std::move(l)); + } + + // calculate the port using shifts + vec2 calculate_port_shifted(vertex_t u, vec2 dir) { + vec2 dirs { sgn(dir.x), sgn(dir.y) }; + auto s = get_shift(u, dirs); + auto center = get_center(u, dirs); + if (s == nodes[u].size) { + return center; + } + return *line_circle_intersection(center, center + dir, pos(u), nodes[u].size); + } + + // calculate the port as if the edge was at the center + vec2 calculate_port_centered(vertex_t u, vec2 dir) { + return nodes[u].pos + nodes[u].size * normalized(dir); + } + + // calculate the port as the lowest/highest points on the node + vec2 calculate_port_single(vertex_t u, vec2 dir) { + return nodes[u].pos + vec2{0, sgn(dir.y) * nodes[u].size}; + } + +}; + +} // namespace detail diff --git a/src/gui/graph/subgraph.hpp b/src/gui/graph/subgraph.hpp new file mode 100644 index 0000000..9e2f51a --- /dev/null +++ b/src/gui/graph/subgraph.hpp @@ -0,0 +1,256 @@ +#pragma once + +#include +#include +#include + +#include "utils.hpp" +#include "graph.hpp" + + +namespace detail { + +struct edge { + vertex_t from, to; +}; + +inline bool operator==(edge lhs, edge rhs) { return lhs.to == rhs.to && lhs.from == rhs.from; } +inline bool operator!=(edge lhs, edge rhs) { return !(lhs == rhs); } + +inline edge reversed(edge e) { return {e.to, e.from}; } + +inline std::ostream& operator<<(std::ostream& out, edge e) { + out << "(" << e.from << ", " << e.to << ")"; + return out; +} + +// ---------------------------------------------------------------------------------------------- +// -------------------------------------- SUBGRAPH -------------------------------------------- +// ---------------------------------------------------------------------------------------------- + + +/** + * Subgraph of a given graph. + * + * Vertices which are added after the construction are called dummy vertices. + * They can be used to distinguis between the vertices of the original graph and vertices which were added for "algorithmic" purpouses. + */ +class subgraph { + graph& m_source; + std::vector< vertex_t > m_vertices; + vertex_t m_dummy_border; + +public: + subgraph(graph& g, std::vector< vertex_t > vertices) + : m_source(g) + , m_vertices(std::move(vertices)) { + if (!m_vertices.empty()) { + m_dummy_border = 1 + *std::max_element(m_vertices.begin(), m_vertices.end()); + } else { + m_dummy_border = 0; + } + } + + unsigned size() const { return m_vertices.size(); } + + void add_edge(edge e) { m_source.add_edge(e.from, e.to); } + void add_edge(vertex_t u, vertex_t v) { add_edge( { u, v } ); } + + vertex_t add_dummy() { + auto u = m_source.add_node(); + m_vertices.push_back(u); + return u; + } + + bool is_dummy(vertex_t u) const { return u >= m_dummy_border; } + + void remove_edge(edge e) { m_source.remove_edge(e.from, e.to); } + void remove_edge(vertex_t u, vertex_t v) { remove_edge( { u, v } ); } + + bool has_edge(edge e) const { + auto out = out_neighbours(e.from); + return std::find(out.begin(), out.end(), e.to) != out.end(); + } + bool has_edge(vertex_t u, vertex_t v) const { return has_edge( { u, v } ); } + + const std::vector& out_neighbours(vertex_t u) const { return m_source.out_neighbours(u); } + const std::vector& in_neighbours(vertex_t u) const { return m_source.in_neighbours(u); } + chain_range< std::vector > neighbours(vertex_t u) const { return { out_neighbours(u), in_neighbours(u) }; } + + vertex_t out_neighbour(vertex_t u, int i) const { return m_source.out_neighbours(u)[i]; } + vertex_t in_neighbour(vertex_t u, int i) const { return m_source.in_neighbours(u)[i]; } + + unsigned out_degree(vertex_t u) const { return m_source.out_neighbours(u).size(); } + unsigned in_deree(vertex_t u) const { return m_source.in_neighbours(u).size(); } + + const std::vector& vertices() const { return m_vertices; } + vertex_t vertex(int i) const { return m_vertices[i]; } +}; + + +inline std::ostream& operator<<(std::ostream& out, const subgraph& g) { + for (auto u : g.vertices()) { + out << u << ": {"; + const char* sep = ""; + for (auto v : g.out_neighbours(u)) { + out << sep << v; + sep = ", "; + } + out << "}\n"; + } + return out; +} + + +// ---------------------------------------------------------------------------------------------- +// -------------------------------------- SPLITING -------------------------------------------- +// ---------------------------------------------------------------------------------------------- + +/** + * Recursively assign u and all vertices reachable from u in the underlying undirected graph to the same component. + */ +inline void split(const graph& g, std::vector& done, std::vector& component, vertex_t u) { + done[u] = true; + component.push_back(u); + for (auto v : g.out_neighbours(u)) { + if (!done[v]) { + split(g, done, component, v); + } + } + for (auto v : g.in_neighbours(u)) { + if (!done[v]) { + split(g, done, component, v); + } + } +} + + +/** + * Split the given graph into connected components represented by subgrapgs. + */ +inline std::vector split(graph& g) { + std::vector< std::vector > components; + std::vector< bool > done(g.size(), false); + + for (auto u : g.vertices()) { + if (!done[u]) { + components.emplace_back(); + split(g, done, components.back(), u); + } + } + + std::vector subgraphs; + for (auto component : components) { + subgraphs.emplace_back(g, component); + } + + return subgraphs; +} + + +// ---------------------------------------------------------------------------------------------- +// ----------------------------------- VERTEX MAP --------------------------------------------- +// ---------------------------------------------------------------------------------------------- + +/** + * Maps vertices to objects of type T + */ +template< typename T > +struct vertex_map { + std::vector< T > data; + + vertex_map() = default; + + vertex_map(const graph& g) : data(g.size(), T{}) {} + vertex_map(const graph& g, T val) : data(g.size(), val) {} + + vertex_map(const subgraph& g) : vertex_map(g, T{}) {} + vertex_map(const subgraph& g, T val) { + for (auto u : g.vertices()) { + if (u >= data.size()) { + data.resize(u + 1); + } + data[u] = val; + } + } + + void resize(const graph& g) { data.resize(g.size()); } + void resize(const graph& g, T val) { data.resize(g.size(), val); } + void resize(const subgraph& g) { resize(g, T{}); } + void resize(const subgraph& g, T val) { + for (auto u : g.vertices()) { + if (u >= data.size()) { + data.resize(u + 1); + data[u] = val; + } + } + } + + void init(const subgraph& g, T val) { + for (auto u : g.vertices()) { + if (u >= data.size()) { + data.resize(u + 1); + } + data[u] = val; + } + } + + // only to be used with T = bool, because the operator[] doesnt work :( + T at(vertex_t u) const { return data[u]; } + void set(vertex_t u, T val) { data[u] = val; } + + T& operator[](vertex_t u) { return data[u]; } + const T& operator[](vertex_t u) const { return data[u]; } + + void insert(vertex_t u, const T& val) { + add_vertex(u); + data[u] = val; + } + void insert(vertex_t u) { insert(u, T{}); } + + void add_vertex(vertex_t u) { + if (u >= data.size()) { + data.resize(u + 1); + } + } + + bool contains(vertex_t u) const { + return u < data.size(); + } + + void clear() { data.clear(); } +}; + + +struct edge_set { + vertex_map< std::vector > data; + + bool contains(edge e) const { return contains(e.from, e.to); } + bool contains(vertex_t u, vertex_t v) const { + return data.contains(u) && std::find(data[u].begin(), data[u].end(), v) != data[u].end(); + } + + void insert(edge e) { insert(e.from, e.to); } + void insert(vertex_t u, vertex_t v) { + data.add_vertex(u); + data[u].push_back(v); + } + + bool remove(edge e) { return remove(e.from, e.to); } + bool remove(vertex_t u, vertex_t v) { + if (!data.contains(u)) + return false; + auto it = std::find(data[u].begin(), data[u].end(), v); + if (it == data[u].end()) + return false; + data[u].erase(it); + return true; + } +}; + +#ifdef DEBUG_LABELS +std::map debug_labels; +#endif + + +} //namespace detail diff --git a/src/gui/graph/types.hpp b/src/gui/graph/types.hpp new file mode 100644 index 0000000..1cc5112 --- /dev/null +++ b/src/gui/graph/types.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#include "vec2.hpp" + +using vertex_t = unsigned; + +/** + * Object representing a vertex in the final layout. + */ +struct node { + vertex_t u; /**< the corresponding vertex identifier */ + vec2 pos; /**< the position in space */ + float size; /**< the radius */ +}; + +inline float biggestSize(const std::vector &nodes, const std::vector &layer) { + auto n = *std::max_element(layer.begin(), layer.end(), + [&nodes](auto a, auto b) { + return nodes[a].size < nodes[b].size; + }); + return nodes[n].size; +} + +/** + * Object representing an edge in the final layout. + */ +struct path { + vertex_t from, to; /**< the vertex identifiers of endpoints of the corresponding edge */ + std::vector points; /**< control points of the poly-line representing the edge */ + bool bidirectional = false; /**< is the edge bidirectional? */ +}; + +/** + * Contains the parameters of the desired graph layout. + */ +struct attributes { + float default_node_size = 15; /**< radius of all nodes */ + float node_dist = 10; /**< minimum distance between borders of 2 nodes */ + float layer_dist = 30; /**< minimum distance between borders of nodes in 2 different layers */ + float loop_angle = 55; /**< angle determining the point on the node where a loop connects to it */ + float loop_size = 15; /**< distance which the loop extends from the node*/ +}; diff --git a/src/gui/graph/utils.hpp b/src/gui/graph/utils.hpp new file mode 100644 index 0000000..acc649d --- /dev/null +++ b/src/gui/graph/utils.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include "vec2.hpp" +#include "types.hpp" + + +template +T sgn(T val) { return ( T(0) < val ) - ( val < T(0) ); } + + +template +struct range { + T r_start; + T r_end; + int step; + + range(T start, T end, int step = 1) : r_start(start), r_end(end), step(step) {} + + struct iterator { + T val; + int step; + iterator(T val, int step) : val(val), step(step) {} + + T operator*() { return val; } + friend bool operator==(const iterator& lhs, const iterator& rhs) { return lhs.val == rhs.val; } + friend bool operator!=(const iterator& lhs, const iterator& rhs) { return !(lhs == rhs); } + iterator& operator++() { val += step; return *this; } + }; + + iterator begin() const { return iterator(r_start, step); } + iterator end() const { return iterator(r_end, step); } +}; + + +template +struct chain_range { + const T& first; + const T& second; + + chain_range(const T& first, const T& second) : first(first), second(second) {} + + struct iterator { + using It = typename T::const_iterator; + It curr; + It st_end; + It nd_beg; + + iterator(It curr, It st_end, It nd_beg) + : curr(curr) + , st_end(st_end) + , nd_beg(nd_beg) + { + if (curr == st_end) { + this->curr = nd_beg; + } + } + + typename T::value_type operator*() { return *curr; } + friend bool operator==(const iterator& lhs, const iterator& rhs) { return lhs.curr == rhs.curr; } + friend bool operator!=(const iterator& lhs, const iterator& rhs) { return !(lhs == rhs); } + iterator& operator++() { + if (++curr == st_end) { + curr = nd_beg; + } + return *this; + } + }; + + iterator begin() { + return iterator( std::begin(first), std::end(first), std::begin(second) ); + } + iterator begin() const { + return iterator( std::begin(first), std::end(first), std::begin(second) ); + } + iterator end() { + return iterator( std::end(second), std::end(first), std::begin(second) ); + } + iterator end() const { + return iterator( std::end(second), std::end(first), std::begin(second) ); + } +}; + + +template +std::ostream& operator<<(std::ostream& out, const std::vector& vec) { + const char* sep = ""; + for (const auto& x : vec) { + out << sep << x; + sep = ", "; + } + return out; +} diff --git a/src/gui/graph/vec2.hpp b/src/gui/graph/vec2.hpp new file mode 100644 index 0000000..456c78f --- /dev/null +++ b/src/gui/graph/vec2.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include + +struct vec2 { + float x, y; +}; + +inline std::ostream& operator<<(std::ostream& out, vec2 v) { + out << "[" << v.x << ", " << v.y << "]"; + return out; +} + +template +inline vec2 operator*(T a, vec2 vec) { return { a*vec.x, a*vec.y}; } +template +inline vec2 operator*(vec2 vec, T a) { return { a*vec.x, a*vec.y}; } + +inline bool operator==(vec2 lhs, vec2 rhs) { return rhs.x == lhs.x && rhs.y == lhs.y; } +inline bool operator!=(vec2 lhs, vec2 rhs) { return !(lhs == rhs); } + +inline vec2 operator+(vec2 lhs, vec2 rhs) { return { lhs.x + rhs.x, lhs.y + rhs.y }; } +inline vec2& operator+=(vec2& lhs, vec2 rhs) { + lhs = lhs + rhs; + return lhs; +} + +inline vec2 operator-(vec2 v) { return -1*v; } +inline vec2 operator-(vec2 lhs, vec2 rhs) { return { lhs.x - rhs.x, lhs.y - rhs.y }; } +inline vec2& operator-=(vec2& lhs, vec2 rhs) { + lhs = lhs - rhs; + return lhs; +} + +inline float dot(vec2 u, vec2 v) { return u.x*v.x + u.y*v.y; } +inline float cross(vec2 u, vec2 v) { + return u.x*v.y - u.y*v.x; +} + +inline float magnitude(vec2 v) { return std::sqrt( dot(v, v) ); } +inline float distance(vec2 u, vec2 v) { return magnitude( v - u ); } + +inline vec2 normalized(vec2 v) { return { v.x/magnitude(v), v.y/magnitude(v) }; } + +inline float to_radians(float deg) { return (M_PI*deg)/180; } +inline float to_degrees(float rad) { return (180*rad)/M_PI; } + +inline vec2 rotate(vec2 vec, float deg) { + float rad = to_radians(deg); + float sin = std::sin(rad); + float cos = std::cos(rad); + return { vec.x*cos + vec.y*sin, -vec.x*sin + vec.y*cos }; +} + +float line_point_dist(vec2 from, vec2 to, vec2 p) { + auto v = to - from; + auto w = p - from; + + float c1 = dot(w,v); + float c2 = dot(v,v); + float t = c1 / c2; + + auto x = from + t*v; + return distance(p, x); +} + +// get the first interesection +std::optional line_circle_intersection(vec2 from, vec2 to, vec2 center, float r) { + auto d = to - from; + auto f = from - center; + + auto a = dot(d, d); + auto b = 2*dot(f, d); + auto c = dot(f, f) - r*r; + + float discriminant = b*b - 4*a*c; + if (discriminant < 0) { + return std::nullopt; + } + + discriminant = sqrt(discriminant); + float t1 = (-b - discriminant)/(2*a); + float t2 = (-b + discriminant)/(2*a); + + if (t1 >= 0 && t1 <= 1) { + return { from + t1*d }; + } + + if (t2 >= 0 && t2 <= 1) { + return { from + t2*d }; + } + + return std::nullopt; +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 0000000..379cf0b --- /dev/null +++ b/src/hash.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +namespace ts::hash { + + //https://github.com/ekpyron/xxhashct + struct xxh64 { + static constexpr uint64_t hash(const char *p, uint64_t len, uint64_t seed) { + return finalize((len >= 32 ? h32bytes(p, len, seed) : seed + PRIME5) + len, p + (len & ~0x1F), len & 0x1F); + } + private: + static constexpr uint64_t PRIME1 = 11400714785074694791ULL; + static constexpr uint64_t PRIME2 = 14029467366897019727ULL; + static constexpr uint64_t PRIME3 = 1609587929392839161ULL; + static constexpr uint64_t PRIME4 = 9650029242287828579ULL; + static constexpr uint64_t PRIME5 = 2870177450012600261ULL; + + static constexpr uint64_t rotl(uint64_t x, int r) { + return ((x << r) | (x >> (64 - r))); + } + static constexpr uint64_t mix1(const uint64_t h, const uint64_t prime, int rshift) { + return (h ^ (h >> rshift)) * prime; + } + static constexpr uint64_t mix2(const uint64_t p, const uint64_t v = 0) { + return rotl(v + p * PRIME2, 31) * PRIME1; + } + static constexpr uint64_t mix3(const uint64_t h, const uint64_t v) { + return (h ^ mix2(v)) * PRIME1 + PRIME4; + } +#ifdef XXH64_BIG_ENDIAN + static constexpr uint32_t endian32 (const char *v) { + return uint32_t(uint8_t(v[3]))|(uint32_t(uint8_t(v[2]))<<8) + |(uint32_t(uint8_t(v[1]))<<16)|(uint32_t(uint8_t(v[0]))<<24); + } + + static constexpr uint64_t endian64 (const char *v) + { + return uint64_t(uint8_t(v[7]))|(uint64_t(uint8_t(v[6]))<<8) + |(uint64_t(uint8_t(v[5]))<<16)|(uint64_t(uint8_t(v[4]))<<24) + |(uint64_t(uint8_t(v[3]))<<32)|(uint64_t(uint8_t(v[2]))<<40) + |(uint64_t(uint8_t(v[1]))<<48)|(uint64_t(uint8_t(v[0]))<<56); + } +#else + static constexpr uint32_t endian32(const char *v) { + return uint32_t(uint8_t(v[0])) | (uint32_t(uint8_t(v[1])) << 8) + | (uint32_t(uint8_t(v[2])) << 16) | (uint32_t(uint8_t(v[3])) << 24); + } + static constexpr uint64_t endian64(const char *v) { + return uint64_t(uint8_t(v[0])) | (uint64_t(uint8_t(v[1])) << 8) + | (uint64_t(uint8_t(v[2])) << 16) | (uint64_t(uint8_t(v[3])) << 24) + | (uint64_t(uint8_t(v[4])) << 32) | (uint64_t(uint8_t(v[5])) << 40) + | (uint64_t(uint8_t(v[6])) << 48) | (uint64_t(uint8_t(v[7])) << 56); + } +#endif + static constexpr uint64_t fetch64(const char *p, const uint64_t v = 0) { + return mix2(endian64(p), v); + } + static constexpr uint64_t fetch32(const char *p) { + return uint64_t(endian32(p)) * PRIME1; + } + static constexpr uint64_t fetch8(const char *p) { + return uint8_t(*p) * PRIME5; + } + static constexpr uint64_t finalize(const uint64_t h, const char *p, uint64_t len) { + return (len >= 8) ? (finalize(rotl(h ^ fetch64(p), 27) * PRIME1 + PRIME4, p + 8, len - 8)) : + ((len >= 4) ? (finalize(rotl(h ^ fetch32(p), 23) * PRIME2 + PRIME3, p + 4, len - 4)) : + ((len > 0) ? (finalize(rotl(h ^ fetch8(p), 11) * PRIME1, p + 1, len - 1)) : + (mix1(mix1(mix1(h, PRIME2, 33), PRIME3, 29), 1, 32)))); + } + static constexpr uint64_t h32bytes(const char *p, uint64_t len, const uint64_t v1, const uint64_t v2, const uint64_t v3, const uint64_t v4) { + return (len >= 32) ? h32bytes(p + 32, len - 32, fetch64(p, v1), fetch64(p + 8, v2), fetch64(p + 16, v3), fetch64(p + 24, v4)) : + mix3(mix3(mix3(mix3(rotl(v1, 1) + rotl(v2, 7) + rotl(v3, 12) + rotl(v4, 18), v1), v2), v3), v4); + } + static constexpr uint64_t h32bytes(const char *p, uint64_t len, const uint64_t seed) { + return h32bytes(p, len, seed + PRIME1 + PRIME2, seed + PRIME2, seed, seed - PRIME1); + } + }; + + inline consteval size_t constLength(const char *str) { + return (*str == 0) ? 0 : constLength(str + 1) + 1; + } + + inline size_t length(const char *str) { + return (*str == 0) ? 0 : length(str + 1) + 1; + } + + inline consteval uint64_t const_hash(const char *input) { + return xxh64::hash(input, constLength(input), 0); + } + + inline consteval uint64_t const_hash(const std::string &input) { + return const_hash(input.c_str()); + } + + inline uint64_t runtime_hash(const std::string &input) { + return xxh64::hash(input.c_str(), input.size(), 0); + } + + inline uint64_t runtime_hash(const std::string_view &input) { + return xxh64::hash(input.begin(), input.size(), 0); + } + + inline uint64_t runtime_hash(const char * input) { + return xxh64::hash(input, length(input), 0); + } + + inline consteval uint64_t operator ""_hash(const char *s, size_t size) { + return xxh64::hash(s, size, 0); + } +}; \ No newline at end of file diff --git a/src/parser2.h b/src/parser2.h index 4797822..f202299 100644 --- a/src/parser2.h +++ b/src/parser2.h @@ -13,11 +13,13 @@ #include "scanner.h" #include "node_test.h" #include "factory.h" +#include "hash.h" #include "utilities.h" #include "diagnostic_messages.h" #include using namespace ts::types; +using namespace ts::hash; namespace ts { using std::string; @@ -1480,7 +1482,7 @@ namespace ts { auto pos = skipTrivia(sourceText, node->pos); // Some known keywords are likely signs of syntax being used improperly. - switch (const_hash(expressionText)) { + switch (runtime_hash(expressionText)) { case "const"_hash: case "let"_hash: case "var"_hash: diff --git a/src/path.cpp b/src/path.cpp index c0ad92d..b8f221c 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -6,6 +6,8 @@ namespace ts { using std::string; using std::replace; using std::regex; + using ts::utf::charCodeAt; + using ts::utf::CharacterCodes; bool fileExtensionIs(const string &path, const string &extension) { return path.size() > extension.size() && endsWith(path, extension); diff --git a/src/path.h b/src/path.h index 4b7c834..3832b89 100644 --- a/src/path.h +++ b/src/path.h @@ -9,6 +9,7 @@ namespace ts { using std::string; using std::replace; using std::regex; + using ts::utf::CharCode; constexpr static auto directorySeparator = "/"; constexpr static auto altDirectorySeparator = "\\"; diff --git a/src/scanner.cpp b/src/scanner.cpp index fe2dd83..0ba92e0 100644 --- a/src/scanner.cpp +++ b/src/scanner.cpp @@ -11,6 +11,11 @@ using namespace ts; using namespace std; namespace ts { + using ts::utf::charCodeAt; + using ts::utf::CharacterCodes; + using ts::utf::fromCharCode; + using namespace ts::hash; + bool isShebangTrivia(const string &text, int pos) { // Shebangs check must only be done at the start of the file // Debug.assert(pos == 0); @@ -988,7 +993,7 @@ namespace ts { if (len >= 2 && len <= 12) { auto ch = charCodeAt(tokenValue, 0); if (ch.code >= CharacterCodes::a && ch.code <= CharacterCodes::z) { - switch (const_hash(tokenValue)) { + switch (runtime_hash(tokenValue)) { case "abstract"_hash: return SyntaxKind::AbstractKeyword; case "any"_hash: diff --git a/src/scanner.h b/src/scanner.h index 4412b69..ed18f9c 100644 --- a/src/scanner.h +++ b/src/scanner.h @@ -6,11 +6,13 @@ #include #include "types.h" #include "utf.h" - -using namespace ts::types; -using namespace std; +#include "hash.h" namespace ts { + using namespace ts::types; + using namespace std; + using ts::utf::CharCode; + struct ScanNumber { SyntaxKind type; string value; @@ -415,8 +417,6 @@ namespace ts { /* @internal */ bool isIdentifierText(string name, ScriptTarget languageVersion = ScriptTarget::Latest, LanguageVariant identifierVariant = LanguageVariant::Standard); - bool isLineBreak(const CharCode &ch); - class Scanner { public: string text; diff --git a/src/tests/test_bench.cpp b/src/tests/test_bench.cpp new file mode 100644 index 0000000..e9da1e5 --- /dev/null +++ b/src/tests/test_bench.cpp @@ -0,0 +1,226 @@ +#include +#include + +#include "../core.h" +#include "../hash.h" +#include "../checker/types.h" +#include "../checker/vm.h" +#include "../checker/vm2.h" +#include "../checker/compiler.h" +#include "../checker/debug.h" + +using namespace ts; +using std::string; +using std::make_shared; +using std::string_view; + +struct SimpleType { + string_view typeName; + vm::TypeKind kind = vm::TypeKind::Never; + + //this is the OP position (instruction pointer) of the bytecode. sourcemap is necessary to map it to source positions. + unsigned int ip{}; +}; + +struct SimpleType2 { + vm::TypeKind kind = vm::TypeKind::Never; +}; + +TEST(bench, jit) { + vm2::jitTest(); + debug("called {}", vm2::called); + + bench("jit", 1000, [&] { + vm2::called = 0; + vm2::jitTest(); +// EXPECT_EQ(vm2::called, 1800); + }); + + auto fn = vm2::jitBuild(); + vector vec; + bench("jit prebuild", 1000, [&] { + for (auto i = 0; i < 300*6; i++) { + volatile auto a = new SimpleType(); + } + vm2::called = 0; +// fn(); +// EXPECT_EQ(vm2::called, 1800); + }); + + bench("300*6 calls", 1000, [&] { + vm2::called = 0; + for (auto i = 0; i < 300; i++) { + vm2::calledFunc(); + vm2::calledFunc(); + vm2::calledFunc(); + vm2::calledFunc(); + vm2::calledFunc(); + vm2::calledFunc(); + } + EXPECT_EQ(vm2::called, 1800); + }); + + bench("300*6 jump", 1000, [&] { + vm2::called = 0; + volatile auto i = 0; //stop optimisation + volatile vm::TypeUnknown* t; + + inc: + vm2::called++; +// t = new vm::TypeUnknown; + goto next; + + next: + if (++i < 1800) goto inc; + goto end; + + end: + EXPECT_EQ(vm2::called, 1800); + }); +} + +TEST(bench, vm2) { + checker::Program program; +// program.pushOp(instructions::OP::Frame); + for (auto i = 0; i < 300; i++) { + program.pushOp(instructions::OP::Noop); //frame + program.pushOp(instructions::OP::Noop); //string + program.pushOp(instructions::OP::Noop); //property name + + program.pushOp(instructions::OP::Noop); //propertySignature + program.pushOp(instructions::OP::Noop); //objectLiteral + program.pushOp(instructions::OP::Noop); //TupleMember + } + program.pushOp(instructions::OP::Halt); + + auto bin = program.build(); + checker::printBin(bin); + + auto module = make_shared(bin, "app", ""); + bench("vm1 old, less cases", 1000, [&] { + module->clear(); + vm::VM vm; + vm.run(module); + }); + bench("vm2 direct threaded", 1000, [&] { + vm2::called = 0; + module->clear(); + vm2::run(module); + EXPECT_EQ(vm2::called, 1800); + }); + + bench("vm2 naive switch-case", 1000, [&] { + vm2::called = 0; + module->clear(); + vm2::naive(module); +// EXPECT_EQ(vm2::called, 1800); + }); +} + +TEST(bench, vm3) { + auto jump = [] { + + //https://christopherschwaab.wordpress.com/2018/04/13/generating-a-threaded-arm-interpreter-with-templates/ + + // We're using the GNU C, labels-as-values extension here. + void *prog[] = {&&PUSH,(void*)6, + &&PUSH,(void*)7, + &&MUL, &&HALT}; +// vector program; +// void **prog = prog; + + void **vPC = prog; + void *stack[4], **sp = stack; + + goto **vPC++; + PUSH: *sp++ = *vPC++; goto **vPC++; + MUL: *(sp-1) = (void*)(*(long*)(sp-1) * *(long*)(sp-2)); + --sp; + goto **vPC++;; + PRINT: //printf("%li\n", *(long*)sp--); goto **vPC++;; + HALT: return; + }; + + bench("jump", 1000, [&] { + jump(); + }); +} + +TEST(bench, types) { + bench("simple type", 1000, [&] { + for (auto i = 0; i < 1000; i++) { + volatile auto a = make_shared(); + } + }); + + bench("simple type array", 1000, [&] { + auto simpleTypes = vector>(); + simpleTypes.reserve(1000); + for (auto i = 0; i < 1000; i++) { + simpleTypes.emplace_back(); + simpleTypes.pop_back(); + } + }); + + bench("simple type 2", 1000, [&] { + volatile auto a = make_shared(); + }); + + bench("simple type 3", 1000, [&] { + volatile auto a = new SimpleType2; + delete a; + }); + + bench("simple type 4", 1000, [&] { + volatile vm::TypeKind *a = new vm::TypeKind; + *a = vm::TypeKind::Unknown; + delete a; + }); + + bench("base type", 1000, [&] { + volatile auto a = make_shared(); + }); + + bench("string type", 1000, [&] { + for (auto i = 0; i < 1000; i++) { + volatile auto a = make_shared(); + } + }); + + bench("string type array", 1000, [&] { + auto simpleTypes = vector>(); + simpleTypes.reserve(1000); + for (auto i = 0; i < 1000; i++) { + simpleTypes.emplace_back(); + simpleTypes.pop_back(); + } + }); + + bench("string literal type ", 1000, [&] { + for (auto i = 0; i < 1000; i++) { + volatile auto a = make_shared("", vm::TypeLiteralType::String); + } + }); + + bench("string literal type array", 1000, [&] { + auto simpleTypes = vector>(); + simpleTypes.reserve(1000); + for (auto i = 0; i < 1000; i++) { + simpleTypes.emplace_back(); + simpleTypes.pop_back(); + } + }); +} + +TEST(bench, hashing) { + string_view sv = "foo188"; + string s = "foo188"; + + bench("hash from view", 100, [&] { + ts::hash::runtime_hash(s); + }); + + bench("hash from string", 100, [&] { + ts::hash::runtime_hash(s); + }); +} diff --git a/src/tests/test_checker.cpp b/src/tests/test_checker.cpp index 9836062..60652d2 100644 --- a/src/tests/test_checker.cpp +++ b/src/tests/test_checker.cpp @@ -4,19 +4,10 @@ #include "../checker/compiler.h" #include "../checker/vm.h" #include "../checker/debug.h" +#include "../utils.h" using namespace ts; -string compile(string code, bool print = true) { - Parser parser; - auto result = parser.parseSourceFile("app.ts", code, ScriptTarget::Latest, false, ScriptKind::TS, {}); - checker::Compiler compiler; - auto program = compiler.compileSourceFile(result); - auto bin = program.build(); - if (print) checker::printBin(bin); - return bin; -} - void test(string code, unsigned int expectedErrors = 0) { auto bin = compile(code); auto module = make_shared(bin, "app.ts", code); @@ -82,6 +73,21 @@ TEST(checker, type) { testBench(code, 0); } +TEST(checker, typeObject) { + Parser parser; + + string code = R"( +type CommandConfig = { id: string }; +const commands: CommandConfig[] = [ + {id: 'foo0'}, + {id: false}, + {id: false}, +]; + )"; + + test(code, 2); +} + TEST(checker, typeError) { Parser parser; diff --git a/src/tests/test_hash.cpp b/src/tests/test_hash.cpp new file mode 100644 index 0000000..c7b1406 --- /dev/null +++ b/src/tests/test_hash.cpp @@ -0,0 +1,22 @@ +#include + +#include "../hash.h" + +using namespace std; +using namespace ts::hash; + +TEST(hash, hash) { + string_view sv = "foo188"; + string s = "foo188"; + +// EXPECT_EQ("fo"_hash, 1842121476277988984UL); +// EXPECT_EQ(const_hash("fo"), 1842121476277988984UL); +// EXPECT_EQ(runtime_hash("fo"), 1842121476277988984UL); + + EXPECT_EQ("foo188"_hash, 3578094862220341077UL); + EXPECT_EQ(runtime_hash(s), 3578094862220341077UL); + EXPECT_EQ(runtime_hash(sv), 3578094862220341077UL); + + EXPECT_EQ(const_hash("foo188"), 3578094862220341077UL); + EXPECT_EQ(runtime_hash("foo188"), 3578094862220341077UL); +} diff --git a/src/tests/test_vm2.cpp b/src/tests/test_vm2.cpp new file mode 100644 index 0000000..a667ffb --- /dev/null +++ b/src/tests/test_vm2.cpp @@ -0,0 +1,179 @@ +#include +#include +#include +#include + +#include "../core.h" +#include "../hash.h" +#include "../checker/compiler.h" +#include "../checker/vm2.h" +#include "./utils.h" + +using namespace ts; +using namespace ts::vm2; + +using std::string; +using std::string_view; + +//struct StringLiteral { +// string_view text; +//}; + +//struct Type; +// +//struct PropertySignature { +// string_view name; +//// std::reference_wrapper type; +//}; +// +//struct ObjectLiteral { +// string_view name; +// std::array, 4> types; +//// std::vector types; +//}; +// +//struct Type { +// unsigned int ip; +// TypeKind type = TypeKind::Unknown; +// +// union T { +// struct StringLiteral stringLiteral; +// struct PropertySignature propertySignature; +//// struct ObjectLiteral objectLiteral; +// }; +// +// T v; +//}; + +template +struct Pool { + std::array pool; + unsigned int i{}; + + void clear() { + i = 0; + } + + T *allocate() { + return &pool[i++]; + } + +// std::vector pool; +// Pool () { +// pool.reserve(300); +// } +// T *make() { +// return &pool.emplace_back(); +// } + +// shared make() { +// return std::make_shared(); +// } +}; +// +//struct TypeMemoryPool { +// Pool never; +// Pool any; +// Pool literal; +// Pool objectLiteral; +// Pool propertySignature; +// +// void clear() { +// never.clear(); +// any.clear(); +// literal.clear(); +// objectLiteral.clear(); +// propertySignature.clear(); +// } +//}; + + +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, reinterpret) { +// auto base = std::make_shared(); +//// printKind(base); +// printKind(std::reinterpret_pointer_cast(base)); +// auto never = std::make_shared(); +// printKind(never); +//// printKind(std::reinterpret_pointer_cast(never)); +//} + +TEST(vm2, 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); + + ts::bench("first", 1000, [&] { + module->clear(); + run(module); + }); +} + +TEST(vm2, vm2Base2) { + string code = R"( +type a = T extends string ? 'yes': 'no'; +const v1: a = 'no'; +const v2: a = 'yes'; +//const v3: a = 'yes'; +const v4: a = 'nope'; +)"; + auto module = std::make_shared(ts::compile(code), "app.ts", code); + run(module); + + module->printErrors(); + EXPECT_EQ(module->errors.size(), 1); + + ts::bench("first", 1000, [&] { + module->clear(); + run(module); + }); +} + +TEST(vm2, vm2) { + ts::checker::Program program; + program.pushOp(OP::Frame); + for (auto i = 0; i < 300; i++) { + program.pushOp(OP::StringLiteral); + program.pushStorage("a"); + } + program.pushOp(OP::Union); + + program.pushOp(OP::Frame); + for (auto i = 0; i < 300; i++) { + program.pushOp(OP::Frame); + program.pushOp(OP::StringLiteral); + program.pushStorage("a"); + program.pushOp(OP::StringLiteral); + program.pushStorage("foo1"); + program.pushOp(OP::PropertySignature); + program.pushOp(OP::ObjectLiteral); + program.pushOp(OP::TupleMember); + } + program.pushOp(OP::Tuple); + program.pushOp(OP::Halt); + + auto module = std::make_shared(program.build(), "app.ts", ""); + + ts::bench("first", 1000, [&] { + module->clear(); + run(module); +// EXPECT_EQ(process(ops), 300 * 6); + }); +} diff --git a/src/tests/utils.h b/src/tests/utils.h new file mode 100644 index 0000000..fa8074c --- /dev/null +++ b/src/tests/utils.h @@ -0,0 +1,15 @@ +#include "../parser2.h" +#include "../checker/compiler.h" +#include "../checker/debug.h" + +namespace ts { + std::string compile(std::string code, bool print = true) { + Parser parser; + auto result = parser.parseSourceFile("app.ts", code, ScriptTarget::Latest, false, ScriptKind::TS, {}); + checker::Compiler compiler; + auto program = compiler.compileSourceFile(result); + auto bin = program.build(); + if (print) checker::printBin(bin); + return bin; + } +} \ No newline at end of file diff --git a/src/types.h b/src/types.h index 64f8086..b851fc4 100644 --- a/src/types.h +++ b/src/types.h @@ -10,11 +10,9 @@ #include #include #include "core.h" +#include "enum.h" #include #include -#define MAGIC_ENUM_RANGE_MIN 0 -#define MAGIC_ENUM_RANGE_MAX 512 -#include "magic_enum.hpp" namespace ts { struct SourceFile; @@ -1521,15 +1519,12 @@ namespace ts { OptionalProperty(expression, Expression); }; - //this seems to be related to instantiated types - struct Type {}; - struct ParameterDeclaration; struct NamedTupleMember; struct SyntheticExpression: BrandKind { bool isSpread; - Property(type, Type); + Property(type, Node); OptionalUnionProperty(tupleNameSource, ParameterDeclaration, NamedTupleMember); }; diff --git a/src/utf.cpp b/src/utf.cpp index ff3aea8..5cca110 100644 --- a/src/utf.cpp +++ b/src/utf.cpp @@ -4,7 +4,7 @@ * Note that an arbitrary `charCodeAt(text, position+1)` does not work since the current code point might be longer than one byte. * We probably should introduction `int position, int offset` so that `charCodeAt(text, position, 1)` returns the correct unicode code point. */ -ts::CharCode ts::charCodeAt(const std::string &text, int position, int *size) { +ts::utf::CharCode ts::utf::charCodeAt(const std::string &text, int position, int *size) { //from - https://stackoverflow.com/a/40054802/979328 int length = 1; int first = text[position]; @@ -34,7 +34,7 @@ ts::CharCode ts::charCodeAt(const std::string &text, int position, int *size) { return {-1, position}; } -std::string ts::fromCharCode(int cp) { +std::string ts::utf::fromCharCode(int cp) { char c[5] = {0x00, 0x00, 0x00, 0x00, 0x00}; if (cp <= 0x7F) { c[0] = cp; } else if (cp <= 0x7FF) { diff --git a/src/utf.h b/src/utf.h index 080f727..8b29232 100644 --- a/src/utf.h +++ b/src/utf.h @@ -5,8 +5,7 @@ #include #include -namespace ts { - +namespace ts::utf { enum CharacterCodes { nullCharacter = 0, maxAsciiCharacter = 0x7F, diff --git a/src/utilities.cpp b/src/utilities.cpp index 948d57a..6ce6233 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -3,10 +3,12 @@ #include "Tracy.hpp" #include "types.h" #include "utilities.h" -#include "core.h" +#include "hash.h" #include namespace ts { + using namespace ts::hash; + LanguageVariant getLanguageVariant(ScriptKind scriptKind) { // .tsx and .jsx files are treated as jsx language variant. return scriptKind == ScriptKind::TSX || scriptKind == ScriptKind::JSX || scriptKind == ScriptKind::JS || scriptKind == ScriptKind::JSON ? LanguageVariant::JSX : LanguageVariant::Standard; @@ -347,7 +349,7 @@ namespace ts { ScriptKind getScriptKindFromFileName(const string &fileName) { auto ext = fileExt(fileName); - switch (const_hash(ext)) { + switch (runtime_hash(ext)) { case const_hash(Extension::Js): case const_hash(Extension::Cjs): case const_hash(Extension::Mjs):return ScriptKind::JS; diff --git a/src/utilities.h b/src/utilities.h index c840b62..68f09b0 100644 --- a/src/utilities.h +++ b/src/utilities.h @@ -9,6 +9,7 @@ #include "diagnostic_messages.h" #include "node_test.h" #include "scanner.h" +#include "hash.h" namespace ts { using namespace ts::types; @@ -19,6 +20,9 @@ namespace ts { using ts::types::SyntaxKind; using ts::types::Extension; using ts::types::LanguageVariant; + using ts::utf::charCodeAt; + using ts::utf::CharCode; + using ts::utf::CharacterCodes; using types::DiagnosticMessage; using types::DiagnosticCategory; using types::DiagnosticWithDetachedLocation; diff --git a/tests/big1.ts b/tests/big1.ts new file mode 100644 index 0000000..d1d19d7 --- /dev/null +++ b/tests/big1.ts @@ -0,0 +1,356 @@ +type Command = + 'foo0' + | 'foo1' + | 'foo2' + | 'foo3' + | 'foo4' + | 'foo5' + | 'foo6' + | 'foo7' + | 'foo8' + | 'foo9' + | 'foo10' + | 'foo11' + | 'foo12' + | 'foo13' + | 'foo14' + | 'foo15' + | 'foo16' + | 'foo17' + | 'foo18' + | 'foo19' + | 'foo20' + | 'foo21' + | 'foo22' + | 'foo23' + | 'foo24' + | 'foo25' + | 'foo26' + | 'foo27' + | 'foo28' + | 'foo29' + | 'foo30' + | 'foo31' + | 'foo32' + | 'foo33' + | 'foo34' + | 'foo35' + | 'foo36' + | 'foo37' + | 'foo38' + | 'foo39' + | 'foo40' + | 'foo41' + | 'foo42' + | 'foo43' + | 'foo44' + | 'foo45' + | 'foo46' + | 'foo47' + | 'foo48' + | 'foo49' + | 'foo50' + | 'foo51' + | 'foo52' + | 'foo53' + | 'foo54' + | 'foo55' + | 'foo56' + | 'foo57' + | 'foo58' + | 'foo59' + | 'foo60' + | 'foo61' + | 'foo62' + | 'foo63' + | 'foo64' + | 'foo65' + | 'foo66' + | 'foo67' + | 'foo68' + | 'foo69' + | 'foo70' + | 'foo71' + | 'foo72' + | 'foo73' + | 'foo74' + | 'foo75' + | 'foo76' + | 'foo77' + | 'foo78' + | 'foo79' + | 'foo80' + | 'foo81' + | 'foo82' + | 'foo83' + | 'foo84' + | 'foo85' + | 'foo86' + | 'foo87' + | 'foo88' + | 'foo89' + | 'foo90' + | 'foo91' + | 'foo92' + | 'foo93' + | 'foo94' + | 'foo95' + | 'foo96' + | 'foo97' + | 'foo98' + | 'foo99' + | 'foo100' + | 'foo101' + | 'foo102' + | 'foo103' + | 'foo104' + | 'foo105' + | 'foo106' + | 'foo107' + | 'foo108' + | 'foo109' + | 'foo110' + | 'foo111' + | 'foo112' + | 'foo113' + | 'foo114' + | 'foo115' + | 'foo116' + | 'foo117' + | 'foo118' + | 'foo119' + | 'foo120' + | 'foo121' + | 'foo122' + | 'foo123' + | 'foo124' + | 'foo125' + | 'foo126' + | 'foo127' + | 'foo128' + | 'foo129' + | 'foo130' + | 'foo131' + | 'foo132' + | 'foo133' + | 'foo134' + | 'foo135' + | 'foo136' + | 'foo137' + | 'foo138' + | 'foo139' + | 'foo140' + | 'foo141' + | 'foo142' + | 'foo143' + | 'foo144' + | 'foo145' + | 'foo146' + | 'foo147' + | 'foo148' + | 'foo149' + | 'foo150' + | 'foo151' + | 'foo152' + | 'foo153' + | 'foo154' + | 'foo155' + | 'foo156' + | 'foo157' + | 'foo158' + | 'foo159' + | 'foo160' + | 'foo161' + | 'foo162' + | 'foo163' + | 'foo164' + | 'foo165' + | 'foo166' + | 'foo167' + | 'foo168' + | 'foo169' + | 'foo170' + | 'foo171' + | 'foo172' + | 'foo173' + | 'foo174' + | 'foo175' + | 'foo176' + | 'foo177' + | 'foo178' + | 'foo179' + | 'foo180' + | 'foo181' + | 'foo182' + | 'foo183' + | 'foo184' + | 'foo185' + | 'foo186' + | 'foo187' + | 'foo188' + | 'foo189' + | 'foo190' + | 'foo191' + | 'foo192' + | 'foo193' + | 'foo194' + | 'foo195' + | 'foo196' + | 'foo197' + | 'foo198' + | 'foo199' + | 'foo200' + | 'foo201' + | 'foo202' + | 'foo203' + | 'foo204' + | 'foo205' + | 'foo206' + | 'foo207' + | 'foo208' + | 'foo209' + | 'foo210' + | 'foo211' + | 'foo212' + | 'foo213' + | 'foo214' + | 'foo215' + | 'foo216' + | 'foo217' + | 'foo218' + | 'foo219' + | 'foo220' + | 'foo221' + | 'foo222' + | 'foo223' + | 'foo224' + | 'foo225' + | 'foo226' + | 'foo227' + | 'foo228' + | 'foo229' + | 'foo230' + | 'foo231' + | 'foo232' + | 'foo233' + | 'foo234' + | 'foo235' + | 'foo236' + | 'foo237' + | 'foo238' + | 'foo239' + | 'foo240' + | 'foo241' + | 'foo242' + | 'foo243' + | 'foo244' + | 'foo245' + | 'foo246' + | 'foo247' + | 'foo248' + | 'foo249' + | 'foo250' + | 'foo251' + | 'foo252' + | 'foo253' + | 'foo254' + | 'foo255' + | 'foo256' + | 'foo257' + | 'foo258' + | 'foo259' + | 'foo260' + | 'foo261' + | 'foo262' + | 'foo263' + | 'foo264' + | 'foo265' + | 'foo266' + | 'foo267' + | 'foo268' + | 'foo269' + | 'foo270' + | 'foo271' + | 'foo272' + | 'foo273' + | 'foo274' + | 'foo275' + | 'foo276' + | 'foo277' + | 'foo278' + | 'foo279' + | 'foo280' + | 'foo281' + | 'foo282' + | 'foo283' + | 'foo284' + | 'foo285' + | 'foo286' + | 'foo287' + | 'foo288' + | 'foo289' + | 'foo290' + | 'foo291' + | 'foo292' + | 'foo293' + | 'foo294' + | 'foo295' + | 'foo296' + | 'foo297' + | 'foo298' + | 'foo299'; + +type CommandConfig = { id: Command }; +const commands: CommandConfig[] = [ + {id: 'foo0'}, {id: 'foo1'}, {id: 'foo2'}, {id: 'foo3'}, {id: 'foo4'}, {id: 'foo5'}, + {id: 'foo6'}, {id: 'foo7'}, {id: 'foo8'}, {id: 'foo9'}, {id: 'foo10'}, {id: 'foo11'}, + {id: 'foo12'}, {id: 'foo13'}, {id: 'foo14'}, {id: 'foo15'}, {id: 'foo16'}, {id: 'foo17'}, + {id: 'foo18'}, {id: 'foo19'}, {id: 'foo20'}, {id: 'foo21'}, {id: 'foo22'}, {id: 'foo23'}, + {id: 'foo24'}, {id: 'foo25'}, {id: 'foo26'}, {id: 'foo27'}, {id: 'foo28'}, {id: 'foo29'}, + {id: 'foo30'}, {id: 'foo31'}, {id: 'foo32'}, {id: 'foo33'}, {id: 'foo34'}, {id: 'foo35'}, + {id: 'foo36'}, {id: 'foo37'}, {id: 'foo38'}, {id: 'foo39'}, {id: 'foo40'}, {id: 'foo41'}, + {id: 'foo42'}, {id: 'foo43'}, {id: 'foo44'}, {id: 'foo45'}, {id: 'foo46'}, {id: 'foo47'}, + {id: 'foo48'}, {id: 'foo49'}, {id: 'foo50'}, {id: 'foo51'}, {id: 'foo52'}, {id: 'foo53'}, + {id: 'foo54'}, {id: 'foo55'}, {id: 'foo56'}, {id: 'foo57'}, {id: 'foo58'}, {id: 'foo59'}, + {id: 'foo60'}, {id: 'foo61'}, {id: 'foo62'}, {id: 'foo63'}, {id: 'foo64'}, {id: 'foo65'}, + {id: 'foo66'}, {id: 'foo67'}, {id: 'foo68'}, {id: 'foo69'}, {id: 'foo70'}, {id: 'foo71'}, + {id: 'foo72'}, {id: 'foo73'}, {id: 'foo74'}, {id: 'foo75'}, {id: 'foo76'}, {id: 'foo77'}, + {id: 'foo78'}, {id: 'foo79'}, {id: 'foo80'}, {id: 'foo81'}, {id: 'foo82'}, {id: 'foo83'}, + {id: 'foo84'}, {id: 'foo85'}, {id: 'foo86'}, {id: 'foo87'}, {id: 'foo88'}, {id: 'foo89'}, + {id: 'foo90'}, {id: 'foo91'}, {id: 'foo92'}, {id: 'foo93'}, {id: 'foo94'}, {id: 'foo95'}, + {id: 'foo96'}, {id: 'foo97'}, {id: 'foo98'}, {id: 'foo99'}, {id: 'foo100'}, {id: 'foo101'}, + {id: 'foo102'}, {id: 'foo103'}, {id: 'foo104'}, {id: 'foo105'}, {id: 'foo106'}, {id: 'foo107'}, + {id: 'foo108'}, {id: 'foo109'}, {id: 'foo110'}, {id: 'foo111'}, {id: 'foo112'}, {id: 'foo113'}, + {id: 'foo114'}, {id: 'foo115'}, {id: 'foo116'}, {id: 'foo117'}, {id: 'foo118'}, {id: 'foo119'}, + {id: 'foo120'}, {id: 'foo121'}, {id: 'foo122'}, {id: 'foo123'}, {id: 'foo124'}, {id: 'foo125'}, + {id: 'foo126'}, {id: 'foo127'}, {id: 'foo128'}, {id: 'foo129'}, {id: 'foo130'}, {id: 'foo131'}, + {id: 'foo132'}, {id: 'foo133'}, {id: 'foo134'}, {id: 'foo135'}, {id: 'foo136'}, {id: 'foo137'}, + {id: 'foo138'}, {id: 'foo139'}, {id: 'foo140'}, {id: 'foo141'}, {id: 'foo142'}, {id: 'foo143'}, + {id: 'foo144'}, {id: 'foo145'}, {id: 'foo146'}, {id: 'foo147'}, {id: 'foo148'}, {id: 'foo149'}, + {id: 'foo150'}, {id: 'foo151'}, {id: 'foo152'}, {id: 'foo153'}, {id: 'foo154'}, {id: 'foo155'}, + {id: 'foo156'}, {id: 'foo157'}, {id: 'foo158'}, {id: 'foo159'}, {id: 'foo160'}, {id: 'foo161'}, + {id: 'foo162'}, {id: 'foo163'}, {id: 'foo164'}, {id: 'foo165'}, {id: 'foo166'}, {id: 'foo167'}, + {id: 'foo168'}, {id: 'foo169'}, {id: 'foo170'}, {id: 'foo171'}, {id: 'foo172'}, {id: 'foo173'}, + {id: 'foo174'}, {id: 'foo175'}, {id: 'foo176'}, {id: 'foo177'}, {id: 'foo178'}, {id: 'foo179'}, + {id: 'foo180'}, {id: 'foo181'}, {id: 'foo182'}, {id: 'foo183'}, {id: 'foo184'}, {id: 'foo185'}, + {id: 'foo186'}, {id: 'foo187'}, {id: 'foo188'}, {id: 'foo189'}, {id: 'foo190'}, {id: 'foo191'}, + {id: 'foo192'}, {id: 'foo193'}, {id: 'foo194'}, {id: 'foo195'}, {id: 'foo196'}, {id: 'foo197'}, + {id: 'foo198'}, {id: 'foo199'}, {id: 'foo200'}, {id: 'foo201'}, {id: 'foo202'}, {id: 'foo203'}, + {id: 'foo204'}, {id: 'foo205'}, {id: 'foo206'}, {id: 'foo207'}, {id: 'foo208'}, {id: 'foo209'}, + {id: 'foo210'}, {id: 'foo211'}, {id: 'foo212'}, {id: 'foo213'}, {id: 'foo214'}, {id: 'foo215'}, + {id: 'foo216'}, {id: 'foo217'}, {id: 'foo218'}, {id: 'foo219'}, {id: 'foo220'}, {id: 'foo221'}, + {id: 'foo222'}, {id: 'foo223'}, {id: 'foo224'}, {id: 'foo225'}, {id: 'foo226'}, {id: 'foo227'}, + {id: 'foo228'}, {id: 'foo229'}, {id: 'foo230'}, {id: 'foo231'}, {id: 'foo232'}, {id: 'foo233'}, + {id: 'foo234'}, {id: 'foo235'}, {id: 'foo236'}, {id: 'foo237'}, {id: 'foo238'}, {id: 'foo239'}, + {id: 'foo240'}, {id: 'foo241'}, {id: 'foo242'}, {id: 'foo243'}, {id: 'foo244'}, {id: 'foo245'}, + {id: 'foo246'}, {id: 'foo247'}, {id: 'foo248'}, {id: 'foo249'}, {id: 'foo250'}, {id: 'foo251'}, + {id: 'foo252'}, {id: 'foo253'}, {id: 'foo254'}, {id: 'foo255'}, {id: 'foo256'}, {id: 'foo257'}, + {id: 'foo258'}, {id: 'foo259'}, {id: 'foo260'}, {id: 'foo261'}, {id: 'foo262'}, {id: 'foo263'}, + {id: 'foo264'}, {id: 'foo265'}, {id: 'foo266'}, {id: 'foo267'}, {id: 'foo268'}, {id: 'foo269'}, + {id: 'foo270'}, {id: 'foo271'}, {id: 'foo272'}, {id: 'foo273'}, {id: 'foo274'}, {id: 'foo275'}, + {id: 'foo276'}, {id: 'foo277'}, {id: 'foo278'}, {id: 'foo279'}, {id: 'foo280'}, {id: 'foo281'}, + {id: 'foo282'}, {id: 'foo283'}, {id: 'foo284'}, {id: 'foo285'}, {id: 'foo286'}, {id: 'foo287'}, + {id: 'foo288'}, {id: 'foo289'}, {id: 'foo290'}, {id: 'foo291'}, {id: 'foo292'}, {id: 'foo293'}, + {id: 'foo294'}, {id: 'foo295'}, {id: 'foo296'}, {id: 'foo297'}, {id: 'foo298'}, {id: 'foo299'}, + {id: 3} +]; \ No newline at end of file diff --git a/tests/big2.ts b/tests/big2.ts new file mode 100644 index 0000000..4d0c34f --- /dev/null +++ b/tests/big2.ts @@ -0,0 +1,355 @@ +type CommandConfig = + { id: 'foo0' } + | { id: 'foo1' } + | { id: 'foo2' } + | { id: 'foo3' } + | { id: 'foo4' } + | { id: 'foo5' } + | { id: 'foo6' } + | { id: 'foo7' } + | { id: 'foo8' } + | { id: 'foo9' } + | { id: 'foo10' } + | { id: 'foo11' } + | { id: 'foo12' } + | { id: 'foo13' } + | { id: 'foo14' } + | { id: 'foo15' } + | { id: 'foo16' } + | { id: 'foo17' } + | { id: 'foo18' } + | { id: 'foo19' } + | { id: 'foo20' } + | { id: 'foo21' } + | { id: 'foo22' } + | { id: 'foo23' } + | { id: 'foo24' } + | { id: 'foo25' } + | { id: 'foo26' } + | { id: 'foo27' } + | { id: 'foo28' } + | { id: 'foo29' } + | { id: 'foo30' } + | { id: 'foo31' } + | { id: 'foo32' } + | { id: 'foo33' } + | { id: 'foo34' } + | { id: 'foo35' } + | { id: 'foo36' } + | { id: 'foo37' } + | { id: 'foo38' } + | { id: 'foo39' } + | { id: 'foo40' } + | { id: 'foo41' } + | { id: 'foo42' } + | { id: 'foo43' } + | { id: 'foo44' } + | { id: 'foo45' } + | { id: 'foo46' } + | { id: 'foo47' } + | { id: 'foo48' } + | { id: 'foo49' } + | { id: 'foo50' } + | { id: 'foo51' } + | { id: 'foo52' } + | { id: 'foo53' } + | { id: 'foo54' } + | { id: 'foo55' } + | { id: 'foo56' } + | { id: 'foo57' } + | { id: 'foo58' } + | { id: 'foo59' } + | { id: 'foo60' } + | { id: 'foo61' } + | { id: 'foo62' } + | { id: 'foo63' } + | { id: 'foo64' } + | { id: 'foo65' } + | { id: 'foo66' } + | { id: 'foo67' } + | { id: 'foo68' } + | { id: 'foo69' } + | { id: 'foo70' } + | { id: 'foo71' } + | { id: 'foo72' } + | { id: 'foo73' } + | { id: 'foo74' } + | { id: 'foo75' } + | { id: 'foo76' } + | { id: 'foo77' } + | { id: 'foo78' } + | { id: 'foo79' } + | { id: 'foo80' } + | { id: 'foo81' } + | { id: 'foo82' } + | { id: 'foo83' } + | { id: 'foo84' } + | { id: 'foo85' } + | { id: 'foo86' } + | { id: 'foo87' } + | { id: 'foo88' } + | { id: 'foo89' } + | { id: 'foo90' } + | { id: 'foo91' } + | { id: 'foo92' } + | { id: 'foo93' } + | { id: 'foo94' } + | { id: 'foo95' } + | { id: 'foo96' } + | { id: 'foo97' } + | { id: 'foo98' } + | { id: 'foo99' } + | { id: 'foo100' } + | { id: 'foo101' } + | { id: 'foo102' } + | { id: 'foo103' } + | { id: 'foo104' } + | { id: 'foo105' } + | { id: 'foo106' } + | { id: 'foo107' } + | { id: 'foo108' } + | { id: 'foo109' } + | { id: 'foo110' } + | { id: 'foo111' } + | { id: 'foo112' } + | { id: 'foo113' } + | { id: 'foo114' } + | { id: 'foo115' } + | { id: 'foo116' } + | { id: 'foo117' } + | { id: 'foo118' } + | { id: 'foo119' } + | { id: 'foo120' } + | { id: 'foo121' } + | { id: 'foo122' } + | { id: 'foo123' } + | { id: 'foo124' } + | { id: 'foo125' } + | { id: 'foo126' } + | { id: 'foo127' } + | { id: 'foo128' } + | { id: 'foo129' } + | { id: 'foo130' } + | { id: 'foo131' } + | { id: 'foo132' } + | { id: 'foo133' } + | { id: 'foo134' } + | { id: 'foo135' } + | { id: 'foo136' } + | { id: 'foo137' } + | { id: 'foo138' } + | { id: 'foo139' } + | { id: 'foo140' } + | { id: 'foo141' } + | { id: 'foo142' } + | { id: 'foo143' } + | { id: 'foo144' } + | { id: 'foo145' } + | { id: 'foo146' } + | { id: 'foo147' } + | { id: 'foo148' } + | { id: 'foo149' } + | { id: 'foo150' } + | { id: 'foo151' } + | { id: 'foo152' } + | { id: 'foo153' } + | { id: 'foo154' } + | { id: 'foo155' } + | { id: 'foo156' } + | { id: 'foo157' } + | { id: 'foo158' } + | { id: 'foo159' } + | { id: 'foo160' } + | { id: 'foo161' } + | { id: 'foo162' } + | { id: 'foo163' } + | { id: 'foo164' } + | { id: 'foo165' } + | { id: 'foo166' } + | { id: 'foo167' } + | { id: 'foo168' } + | { id: 'foo169' } + | { id: 'foo170' } + | { id: 'foo171' } + | { id: 'foo172' } + | { id: 'foo173' } + | { id: 'foo174' } + | { id: 'foo175' } + | { id: 'foo176' } + | { id: 'foo177' } + | { id: 'foo178' } + | { id: 'foo179' } + | { id: 'foo180' } + | { id: 'foo181' } + | { id: 'foo182' } + | { id: 'foo183' } + | { id: 'foo184' } + | { id: 'foo185' } + | { id: 'foo186' } + | { id: 'foo187' } + | { id: 'foo188' } + | { id: 'foo189' } + | { id: 'foo190' } + | { id: 'foo191' } + | { id: 'foo192' } + | { id: 'foo193' } + | { id: 'foo194' } + | { id: 'foo195' } + | { id: 'foo196' } + | { id: 'foo197' } + | { id: 'foo198' } + | { id: 'foo199' } + | { id: 'foo200' } + | { id: 'foo201' } + | { id: 'foo202' } + | { id: 'foo203' } + | { id: 'foo204' } + | { id: 'foo205' } + | { id: 'foo206' } + | { id: 'foo207' } + | { id: 'foo208' } + | { id: 'foo209' } + | { id: 'foo210' } + | { id: 'foo211' } + | { id: 'foo212' } + | { id: 'foo213' } + | { id: 'foo214' } + | { id: 'foo215' } + | { id: 'foo216' } + | { id: 'foo217' } + | { id: 'foo218' } + | { id: 'foo219' } + | { id: 'foo220' } + | { id: 'foo221' } + | { id: 'foo222' } + | { id: 'foo223' } + | { id: 'foo224' } + | { id: 'foo225' } + | { id: 'foo226' } + | { id: 'foo227' } + | { id: 'foo228' } + | { id: 'foo229' } + | { id: 'foo230' } + | { id: 'foo231' } + | { id: 'foo232' } + | { id: 'foo233' } + | { id: 'foo234' } + | { id: 'foo235' } + | { id: 'foo236' } + | { id: 'foo237' } + | { id: 'foo238' } + | { id: 'foo239' } + | { id: 'foo240' } + | { id: 'foo241' } + | { id: 'foo242' } + | { id: 'foo243' } + | { id: 'foo244' } + | { id: 'foo245' } + | { id: 'foo246' } + | { id: 'foo247' } + | { id: 'foo248' } + | { id: 'foo249' } + | { id: 'foo250' } + | { id: 'foo251' } + | { id: 'foo252' } + | { id: 'foo253' } + | { id: 'foo254' } + | { id: 'foo255' } + | { id: 'foo256' } + | { id: 'foo257' } + | { id: 'foo258' } + | { id: 'foo259' } + | { id: 'foo260' } + | { id: 'foo261' } + | { id: 'foo262' } + | { id: 'foo263' } + | { id: 'foo264' } + | { id: 'foo265' } + | { id: 'foo266' } + | { id: 'foo267' } + | { id: 'foo268' } + | { id: 'foo269' } + | { id: 'foo270' } + | { id: 'foo271' } + | { id: 'foo272' } + | { id: 'foo273' } + | { id: 'foo274' } + | { id: 'foo275' } + | { id: 'foo276' } + | { id: 'foo277' } + | { id: 'foo278' } + | { id: 'foo279' } + | { id: 'foo280' } + | { id: 'foo281' } + | { id: 'foo282' } + | { id: 'foo283' } + | { id: 'foo284' } + | { id: 'foo285' } + | { id: 'foo286' } + | { id: 'foo287' } + | { id: 'foo288' } + | { id: 'foo289' } + | { id: 'foo290' } + | { id: 'foo291' } + | { id: 'foo292' } + | { id: 'foo293' } + | { id: 'foo294' } + | { id: 'foo295' } + | { id: 'foo296' } + | { id: 'foo297' } + | { id: 'foo298' } + | { id: 'foo299' }; + +const commands: CommandConfig[] = [ + {id: 'foo0'}, {id: 'foo1'}, {id: 'foo2'}, {id: 'foo3'}, {id: 'foo4'}, {id: 'foo5'}, + {id: 'foo6'}, {id: 'foo7'}, {id: 'foo8'}, {id: 'foo9'}, {id: 'foo10'}, {id: 'foo11'}, + {id: 'foo12'}, {id: 'foo13'}, {id: 'foo14'}, {id: 'foo15'}, {id: 'foo16'}, {id: 'foo17'}, + {id: 'foo18'}, {id: 'foo19'}, {id: 'foo20'}, {id: 'foo21'}, {id: 'foo22'}, {id: 'foo23'}, + {id: 'foo24'}, {id: 'foo25'}, {id: 'foo26'}, {id: 'foo27'}, {id: 'foo28'}, {id: 'foo29'}, + {id: 'foo30'}, {id: 'foo31'}, {id: 'foo32'}, {id: 'foo33'}, {id: 'foo34'}, {id: 'foo35'}, + {id: 'foo36'}, {id: 'foo37'}, {id: 'foo38'}, {id: 'foo39'}, {id: 'foo40'}, {id: 'foo41'}, + {id: 'foo42'}, {id: 'foo43'}, {id: 'foo44'}, {id: 'foo45'}, {id: 'foo46'}, {id: 'foo47'}, + {id: 'foo48'}, {id: 'foo49'}, {id: 'foo50'}, {id: 'foo51'}, {id: 'foo52'}, {id: 'foo53'}, + {id: 'foo54'}, {id: 'foo55'}, {id: 'foo56'}, {id: 'foo57'}, {id: 'foo58'}, {id: 'foo59'}, + {id: 'foo60'}, {id: 'foo61'}, {id: 'foo62'}, {id: 'foo63'}, {id: 'foo64'}, {id: 'foo65'}, + {id: 'foo66'}, {id: 'foo67'}, {id: 'foo68'}, {id: 'foo69'}, {id: 'foo70'}, {id: 'foo71'}, + {id: 'foo72'}, {id: 'foo73'}, {id: 'foo74'}, {id: 'foo75'}, {id: 'foo76'}, {id: 'foo77'}, + {id: 'foo78'}, {id: 'foo79'}, {id: 'foo80'}, {id: 'foo81'}, {id: 'foo82'}, {id: 'foo83'}, + {id: 'foo84'}, {id: 'foo85'}, {id: 'foo86'}, {id: 'foo87'}, {id: 'foo88'}, {id: 'foo89'}, + {id: 'foo90'}, {id: 'foo91'}, {id: 'foo92'}, {id: 'foo93'}, {id: 'foo94'}, {id: 'foo95'}, + {id: 'foo96'}, {id: 'foo97'}, {id: 'foo98'}, {id: 'foo99'}, {id: 'foo100'}, {id: 'foo101'}, + {id: 'foo102'}, {id: 'foo103'}, {id: 'foo104'}, {id: 'foo105'}, {id: 'foo106'}, {id: 'foo107'}, + {id: 'foo108'}, {id: 'foo109'}, {id: 'foo110'}, {id: 'foo111'}, {id: 'foo112'}, {id: 'foo113'}, + {id: 'foo114'}, {id: 'foo115'}, {id: 'foo116'}, {id: 'foo117'}, {id: 'foo118'}, {id: 'foo119'}, + {id: 'foo120'}, {id: 'foo121'}, {id: 'foo122'}, {id: 'foo123'}, {id: 'foo124'}, {id: 'foo125'}, + {id: 'foo126'}, {id: 'foo127'}, {id: 'foo128'}, {id: 'foo129'}, {id: 'foo130'}, {id: 'foo131'}, + {id: 'foo132'}, {id: 'foo133'}, {id: 'foo134'}, {id: 'foo135'}, {id: 'foo136'}, {id: 'foo137'}, + {id: 'foo138'}, {id: 'foo139'}, {id: 'foo140'}, {id: 'foo141'}, {id: 'foo142'}, {id: 'foo143'}, + {id: 'foo144'}, {id: 'foo145'}, {id: 'foo146'}, {id: 'foo147'}, {id: 'foo148'}, {id: 'foo149'}, + {id: 'foo150'}, {id: 'foo151'}, {id: 'foo152'}, {id: 'foo153'}, {id: 'foo154'}, {id: 'foo155'}, + {id: 'foo156'}, {id: 'foo157'}, {id: 'foo158'}, {id: 'foo159'}, {id: 'foo160'}, {id: 'foo161'}, + {id: 'foo162'}, {id: 'foo163'}, {id: 'foo164'}, {id: 'foo165'}, {id: 'foo166'}, {id: 'foo167'}, + {id: 'foo168'}, {id: 'foo169'}, {id: 'foo170'}, {id: 'foo171'}, {id: 'foo172'}, {id: 'foo173'}, + {id: 'foo174'}, {id: 'foo175'}, {id: 'foo176'}, {id: 'foo177'}, {id: 'foo178'}, {id: 'foo179'}, + {id: 'foo180'}, {id: 'foo181'}, {id: 'foo182'}, {id: 'foo183'}, {id: 'foo184'}, {id: 'foo185'}, + {id: 'foo186'}, {id: 'foo187'}, {id: 'foo188'}, {id: 'foo189'}, {id: 'foo190'}, {id: 'foo191'}, + {id: 'foo192'}, {id: 'foo193'}, {id: 'foo194'}, {id: 'foo195'}, {id: 'foo196'}, {id: 'foo197'}, + {id: 'foo198'}, {id: 'foo199'}, {id: 'foo200'}, {id: 'foo201'}, {id: 'foo202'}, {id: 'foo203'}, + {id: 'foo204'}, {id: 'foo205'}, {id: 'foo206'}, {id: 'foo207'}, {id: 'foo208'}, {id: 'foo209'}, + {id: 'foo210'}, {id: 'foo211'}, {id: 'foo212'}, {id: 'foo213'}, {id: 'foo214'}, {id: 'foo215'}, + {id: 'foo216'}, {id: 'foo217'}, {id: 'foo218'}, {id: 'foo219'}, {id: 'foo220'}, {id: 'foo221'}, + {id: 'foo222'}, {id: 'foo223'}, {id: 'foo224'}, {id: 'foo225'}, {id: 'foo226'}, {id: 'foo227'}, + {id: 'foo228'}, {id: 'foo229'}, {id: 'foo230'}, {id: 'foo231'}, {id: 'foo232'}, {id: 'foo233'}, + {id: 'foo234'}, {id: 'foo235'}, {id: 'foo236'}, {id: 'foo237'}, {id: 'foo238'}, {id: 'foo239'}, + {id: 'foo240'}, {id: 'foo241'}, {id: 'foo242'}, {id: 'foo243'}, {id: 'foo244'}, {id: 'foo245'}, + {id: 'foo246'}, {id: 'foo247'}, {id: 'foo248'}, {id: 'foo249'}, {id: 'foo250'}, {id: 'foo251'}, + {id: 'foo252'}, {id: 'foo253'}, {id: 'foo254'}, {id: 'foo255'}, {id: 'foo256'}, {id: 'foo257'}, + {id: 'foo258'}, {id: 'foo259'}, {id: 'foo260'}, {id: 'foo261'}, {id: 'foo262'}, {id: 'foo263'}, + {id: 'foo264'}, {id: 'foo265'}, {id: 'foo266'}, {id: 'foo267'}, {id: 'foo268'}, {id: 'foo269'}, + {id: 'foo270'}, {id: 'foo271'}, {id: 'foo272'}, {id: 'foo273'}, {id: 'foo274'}, {id: 'foo275'}, + {id: 'foo276'}, {id: 'foo277'}, {id: 'foo278'}, {id: 'foo279'}, {id: 'foo280'}, {id: 'foo281'}, + {id: 'foo282'}, {id: 'foo283'}, {id: 'foo284'}, {id: 'foo285'}, {id: 'foo286'}, {id: 'foo287'}, + {id: 'foo288'}, {id: 'foo289'}, {id: 'foo290'}, {id: 'foo291'}, {id: 'foo292'}, {id: 'foo293'}, + {id: 'foo294'}, {id: 'foo295'}, {id: 'foo296'}, {id: 'foo297'}, {id: 'foo298'}, {id: 'foo299'}, + {id: 'foo2'} +]; \ No newline at end of file