From 3cee56fca7ad5ec4f6a8ad2e5e3f5a36ec896abb Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Wed, 15 Jun 2022 03:10:08 +0200 Subject: [PATCH] stack optimisation, bytecode header, debug binary --- src/CMakeLists.txt | 2 +- src/checker/checks.h | 8 +- src/checker/compiler.h | 270 ++++++++++++++++++------------------- src/checker/debug.h | 125 +++++++++++++++++ src/checker/instructions.h | 4 +- src/checker/type_objects.h | 7 +- src/checker/vm.h | 171 ++++++++++++++++------- src/tests/test_checker.cpp | 94 +++++++------ 8 files changed, 439 insertions(+), 242 deletions(-) create mode 100644 src/checker/debug.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 30603ba..38d474f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,7 +11,7 @@ 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/type_objects.h checker/utils.h checker/checks.h) + checker/vm.h checker/vm.cpp checker/instructions.h checker/compiler.h checker/type_objects.h checker/utils.h checker/checks.h checker/debug.h) # ${CMAKE_CURRENT_SOURCE_DIR}/../libs/tracy/TracyClient.cpp target_link_libraries(typescript fmt) \ No newline at end of file diff --git a/src/checker/checks.h b/src/checker/checks.h index 2f16c5d..8924fbc 100644 --- a/src/checker/checks.h +++ b/src/checker/checks.h @@ -20,9 +20,9 @@ namespace ts::vm { if (left->kind == TypeKind::Literal) return to(left)->type == TypeLiteralType::Number; } - if (left->kind == TypeKind::Literal && left->kind == TypeKind::Literal) { - return to(left)->type == to(left)->type && - to(right)->text() == to(left)->text(); + if (left->kind == TypeKind::Literal && right->kind == TypeKind::Literal) { + return to(left)->type == to(right)->type && + to(left)->text() == to(right)->text(); } if (right->kind == TypeKind::Tuple && left->kind == TypeKind::Tuple) { @@ -43,7 +43,7 @@ namespace ts::vm { if (right->kind == TypeKind::Union) { if (left->kind != TypeKind::Union) { for (auto &&l: to(right)->types) { - if (isExtendable(l, left)) return true; + if (isExtendable(left, l)) return true; } return false; } else { diff --git a/src/checker/compiler.h b/src/checker/compiler.h index d3d24b7..c2abcf3 100644 --- a/src/checker/compiler.h +++ b/src/checker/compiler.h @@ -29,21 +29,24 @@ namespace ts::checker { vector ops; //OPs, and its parameters unordered_map opSourceMap; string_view identifier{}; - unsigned int address{}; //during compilation the index address, after the final address in the binary + unsigned int index{}; + unsigned int nameAddress{}; unsigned int pos{}; SymbolType type = SymbolType::Type; - explicit Subroutine() {} + explicit Subroutine() { + identifier = ""; + } explicit Subroutine(string_view &identifier): identifier(identifier) {} }; struct Frame; struct Symbol { - const string_view name; - const SymbolType type; - const unsigned int index{}; //symbol index of the current frame - const unsigned int pos; + string name; + SymbolType type = SymbolType::Type; + unsigned int index{}; //symbol index of the current frame + unsigned int pos{}; unsigned int declarations = 1; sharedOpt routine = nullptr; shared frame = nullptr; @@ -103,12 +106,12 @@ namespace ts::checker { auto routine = make_shared(); routine->pos = pos; routine->type = SymbolType::TypeFunction; - routine->address = subroutines.size(); + routine->index = subroutines.size(); pushFrame(true); //subroutines have implicit stack frames due to call convention subroutines.push_back(routine); activeSubroutines.push_back(subroutines.back()); - return routine->address; + return routine->index; } /** @@ -120,7 +123,7 @@ namespace ts::checker { if (s.name == name) { pushFrame(true); //subroutines have implicit stack frames due to call convention activeSubroutines.push_back(s.routine); - return s.routine->address; + return s.routine->index; } } throw runtime_error(fmt::format("no symbol found for {}", name)); @@ -222,7 +225,7 @@ namespace ts::checker { * Symbols will be created first before a body is extracted. This makes sure all * symbols are known before their reference is used. */ - Symbol &pushSymbol(string_view name, SymbolType type, unsigned int pos, shared frameToUse = nullptr) { + Symbol &pushSymbol(string_view name, SymbolType type, unsigned int pos, sharedOpt frameToUse = nullptr) { if (!frameToUse) frameToUse = frame; for (auto &&v: frameToUse->symbols) { @@ -232,13 +235,21 @@ namespace ts::checker { } } - frameToUse->symbols.push_back(Symbol{ - .frame = frameToUse, - .name = name, - .type = type, - .pos = pos, - .index = (unsigned int) frameToUse->symbols.size() - }); +// Symbol symbol{ +// .name = name, +// .type = type, +// .index = (unsigned int) frameToUse->symbols.size(), +// .pos = pos, +// .routine = nullptr, +// .frame = frameToUse, +// }; + Symbol symbol; + symbol.name = string(name); + symbol.type = type; + symbol.index = (unsigned int) frameToUse->symbols.size(); + symbol.pos = pos; + symbol.frame = frameToUse; + frameToUse->symbols.push_back(std::move(symbol)); return frameToUse->symbols.back(); } @@ -249,7 +260,8 @@ namespace ts::checker { auto routine = make_shared(name); routine->pos = pos; routine->type = type; - routine->address = subroutines.size(); + routine->nameAddress = registerStorage(routine->identifier); + routine->index = subroutines.size(); subroutines.push_back(routine); symbol.routine = routine; @@ -266,19 +278,14 @@ namespace ts::checker { return address; } + /** + * Pushes a Uint32 and stores the text into the storage. + * @param s + */ void pushStorage(string_view s) { pushAddress(registerStorage(s)); } - string_view findStorage(unsigned int index) { - unsigned int i = 5; - for (auto &&s: storage) { - if (i == index) return s; - i += 2 + s.size(); - } - return "!unknown"; - } - string build() { vector bin; unsigned int address = 0; @@ -286,136 +293,100 @@ namespace ts::checker { 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 routine handling + writeUint32(bin, bin.size(), 0); //set after storage handling } for (auto &&item: storage) { - writeUint16(bin, address, item.size()); - bin.insert(bin.end(), item.begin(), item.end()); address += 2 + item.size(); } - //detect final binary address of all subroutines - unsigned int routineAddress = address; - for (auto &&routine: subroutines) { - routine->address = routineAddress; - routineAddress += routine->ops.size(); - } + //set initial jump position to right after the storage data + writeUint32(bin, 1, address); - //go through all OPs and adjust CALL parameter to the final binary address - setFinalBinaryAddress(ops); - for (auto &&routine: subroutines) { - setFinalBinaryAddress(routine->ops); + address += subroutines.size() * (1 + 4 + 4); //OP::Subroutine + uint32 name address + uin32 routine address + address += 1 + 4; //OP::Main + uint32 address + + //push all storage data to the binary + for (auto &&item: storage) { + writeUint16(bin, bin.size(), item.size()); + bin.insert(bin.end(), item.begin(), item.end()); } + //after the storage data follows the subroutine meta-data. +// vector subroutineMetadata; for (auto &&routine: subroutines) { - bin.insert(bin.end(), routine->ops.begin(), routine->ops.end()); + bin.push_back(OP::Subroutine); + writeUint32(bin, bin.size(), routine->nameAddress); + writeUint32(bin, bin.size(), address); address += routine->ops.size(); } - writeUint32(bin, 1, address); + //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); - bin.insert(bin.end(), ops.begin(), ops.end()); - - return string(bin.begin(), bin.end()); - } + //we don't need that anymore, as the address is part of the subroutine-metadata +// //go through all OPs and adjust CALL parameter to the final binary address +// setFinalBinaryAddress(ops); +// for (auto &&routine: subroutines) { +// setFinalBinaryAddress(routine->ops); +// } - void setFinalBinaryAddress(vector &ops) { - const auto end = ops.size(); - for (unsigned int i = 0; i < end; i++) { - auto op = (OP) ops[i]; - switch (op) { - case OP::TypeArgumentDefault: - case OP::Distribute: { - auto index = readUint32(ops, i + 1); - auto &routine = subroutines[index]; - writeUint32(ops, i + 1, routine->address); - i += 4; - break; - } - case OP::Call: { - //adjust binary address - auto index = readUint32(ops, i + 1); - auto &routine = subroutines[index]; - writeUint32(ops, i + 1, routine->address); - i += 6; - break; - } - case OP::JumpCondition: { - //adjust binary address - auto first = readUint16(ops, i + 1); - auto second = readUint16(ops, i + 3); - writeUint16(ops, i + 1, subroutines[first]->address); - writeUint16(ops, i + 3, subroutines[second]->address); - i += 4; - break; - } - case OP::Loads: //2 bytes each: frame id + symbol index - case OP::NumberLiteral: - case OP::BigIntLiteral: - case OP::StringLiteral: { - i += 4; - break; - } - } + for (auto &&routine: subroutines) { + bin.insert(bin.end(), routine->ops.begin(), routine->ops.end()); } - } - void printOps(vector ops) { - const auto end = ops.size(); - for (unsigned int i = 0; i < end; i++) { - auto op = (OP) ops[i]; - std::string params = ""; - switch (op) { - case OP::Call: { - params += fmt::format(" &{}[{}]", readUint32(ops, i + 1), readUint16(ops, i + 5)); - i += 6; - break; - } - case OP::JumpCondition: { - params += fmt::format(" &{}:&{}", readUint16(ops, i + 1), readUint16(ops, i + 3)); - i += 4; - break; - } - case OP::TypeArgumentDefault: - case OP::Distribute: { - params += fmt::format(" &{}", readUint32(ops, i + 1)); - i += 4; - break; - } - case OP::Loads: { - params += fmt::format(" &{}:{}", readUint16(ops, i + 1), readUint16(ops, i + 3)); - i += 4; - break; - } - case OP::NumberLiteral: - case OP::BigIntLiteral: - case OP::StringLiteral: { - params += fmt::format(" \"{}\"", findStorage(readUint32(ops, i + 1))); - i += 4; - break; - } - } + //now the main code is added + bin.insert(bin.end(), ops.begin(), ops.end()); - if (params.empty()) { - fmt::print("{} ", op); - } else { - fmt::print("({}{}) ", op, params); - } - } - fmt::print("\n"); + debug("bin {}", bin); + + return string(bin.begin(), bin.end()); } - void print() { - int i = 0; - for (auto &&subroutine: subroutines) { - fmt::print("Subroutine {} &{}, {} bytes: ", subroutine->identifier, i++, subroutine->ops.size()); - printOps(subroutine->ops); - } +// void setFinalBinaryAddress(vector &ops) { +// const auto end = ops.size(); +// for (unsigned int i = 0; i < end; i++) { +// auto op = (OP) ops[i]; +// switch (op) { +// case OP::TypeArgumentDefault: +// case OP::Distribute: { +// auto index = readUint32(ops, i + 1); +// auto &routine = subroutines[index]; +// writeUint32(ops, i + 1, routine->address); +// i += 4; +// break; +// } +// case OP::Call: { +// //adjust binary address +// auto index = readUint32(ops, i + 1); +// auto &routine = subroutines[index]; +// writeUint32(ops, i + 1, routine->address); +// i += 6; +// break; +// } +// case OP::JumpCondition: { +// //adjust binary address +// auto first = readUint16(ops, i + 1); +// auto second = readUint16(ops, i + 3); +// writeUint16(ops, i + 1, subroutines[first]->address); +// writeUint16(ops, i + 3, subroutines[second]->address); +// i += 4; +// break; +// } +// case OP::Loads: //2 bytes each: frame id + symbol index +// case OP::Parameter: //uint32 for storage +// case OP::NumberLiteral: +// case OP::BigIntLiteral: +// case OP::StringLiteral: { +// i += 4; +// break; +// } +// } +// } +// } - debug("Main {} bytes: {}", ops.size(), ops); - printOps(ops); - } }; class Compiler { @@ -531,7 +502,7 @@ namespace ts::checker { if (!symbol.routine) { throw runtime_error("Reference is not a reference to a existing routine."); } - program.pushAddress(symbol.routine->address); + program.pushAddress(symbol.routine->index); if (n->typeArguments) { program.pushUint16(n->typeArguments->length()); } else { @@ -561,7 +532,7 @@ namespace ts::checker { handle(tp->defaultType, program); auto routine = program.popSubroutine(); program.pushOp(instructions::TypeArgumentDefault); - program.pushAddress(routine->address); + program.pushAddress(routine->index); } //todo constraints + default } @@ -572,6 +543,21 @@ namespace ts::checker { } break; } + case types::SyntaxKind::Parameter: { + const auto n = to(node); + if (n->type) { + handle(n->type, program); + } else { + program.pushOp(OP::Unknown); + } + program.pushOp(OP::Parameter); + if (auto id = to(n->name)) { + program.pushStorage(id->escapedText); + } else { + program.pushStorage(""); + } + break; + } case types::SyntaxKind::FunctionDeclaration: { const auto n = to(node); if (const auto id = to(n->name)) { @@ -582,7 +568,7 @@ namespace ts::checker { program.pushSubroutine(id->escapedText); for (auto &¶m: n->parameters->list) { - handle(n, program); + handle(param, program); } if (n->type) { handle(n->type, program); @@ -632,7 +618,7 @@ namespace ts::checker { //in the subroutine of the conditional type we place a new type variable, which acts as input. //the `Distribute` OP makes then sure that the current stack entry is used as input program.pushSymbol(distributiveOverIdentifier->escapedText, SymbolType::TypeVariable, distributiveOverIdentifier->pos); - program.pushOp(instructions::Var); + program.pushOp(instructions::TypeArgument); } handle(n->checkType, program); @@ -656,10 +642,10 @@ namespace ts::checker { auto routine = program.popSubroutine(); handle(n->checkType, program); //LOADS the input type onto the stack. Distribute pops it then. program.pushOp(OP::Distribute); - program.pushAddress(routine->address); + program.pushAddress(routine->index); } - debug("ConditionalType {}", !!distributiveOverIdentifier); +// debug("ConditionalType {}", !!distributiveOverIdentifier); break; } case types::SyntaxKind::ParenthesizedType: { diff --git a/src/checker/debug.h b/src/checker/debug.h new file mode 100644 index 0000000..806ebb2 --- /dev/null +++ b/src/checker/debug.h @@ -0,0 +1,125 @@ +#pragma once + +#include +#include "./instructions.h" +#include "../core.h" +#include "./utils.h" + +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 PrintSubroutine { + string_view name; + unsigned int address; + }; + + inline void printBin(string_view bin) { + const auto end = bin.size(); + unsigned int storageEnd = 0; + bool newSubRoutine = false; + vector subroutines; + + for (unsigned int i = 0; i < end; i++) { + if (storageEnd) { + while (i < storageEnd) { + auto size = readUint16(bin, i); + auto data = bin.substr(i + 2, size); + fmt::print("(Storage ({})\"{}\") ", size, data); + i += 2 + size; + } + debug(""); + storageEnd = 0; + } + + if (newSubRoutine) { + auto found = false; + unsigned int j = 0; + for (auto &&r: subroutines) { + if (r.address == i) { + fmt::print("\n&{} {}(): ", j, r.name); + found = true; + break; + } + j++; + } + if (!found) { + fmt::print("\nunknown!(): "); + } + newSubRoutine = false; + } + std::string params = ""; + auto op = (OP) bin[i]; + + switch (op) { + case OP::Call: { + params += fmt::format(" &{}[{}]", readUint32(bin, i + 1), readUint16(bin, i + 5)); + i += 6; + break; + } + case OP::Subroutine: { + auto nameAddress = readUint32(bin, i + 1); + auto address = readUint32(bin, i + 5); + string_view name = nameAddress ? readStorage(bin, nameAddress) : ""; + params += fmt::format(" {}[{}]", name, address); + i += 8; + subroutines.push_back({.name = name, .address = address}); + break; + } + case OP::Main: + case OP::Jump: { + auto address = readUint32(bin, i + 1); + params += fmt::format(" &{}", address); + i += 4; + if (op == OP::Jump) { + storageEnd = address; + } else { + subroutines.push_back({.name = "main", .address = address}); + newSubRoutine = true; + } + break; + } + case OP::Return: { + newSubRoutine = true; + break; + } + case OP::JumpCondition: { + params += fmt::format(" &{}:&{}", readUint16(bin, i + 1), readUint16(bin, i + 3)); + i += 4; + break; + } + case OP::TypeArgumentDefault: + case OP::Distribute: { + params += fmt::format(" &{}", readUint32(bin, i + 1)); + i += 4; + break; + } + case OP::Loads: { + params += fmt::format(" &{}:{}", readUint16(bin, i + 1), readUint16(bin, i + 3)); + i += 4; + break; + } + case OP::Parameter: + case OP::NumberLiteral: + case OP::BigIntLiteral: + case OP::StringLiteral: { + params += fmt::format(" \"{}\"", readStorage(bin, readUint32(bin, i + 1))); + i += 4; + break; + } + } + + if (params.empty()) { + fmt::print("{} ", op); + } else { + fmt::print("({}{}) ", op, params); + } + } + fmt::print("\n"); + } +} \ No newline at end of file diff --git a/src/checker/instructions.h b/src/checker/instructions.h index 698f42e..72de4ee 100644 --- a/src/checker/instructions.h +++ b/src/checker/instructions.h @@ -94,7 +94,9 @@ namespace ts::instructions { Frame, //creates a new stack frame Return, - Jump, + 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 diff --git a/src/checker/type_objects.h b/src/checker/type_objects.h index aad9127..fcde90f 100644 --- a/src/checker/type_objects.h +++ b/src/checker/type_objects.h @@ -137,10 +137,9 @@ namespace ts::vm { }; struct TypeLiteral: BrandKind { - private: - string_view literal; - string *literalText = nullptr; public: + string *literalText = nullptr; + string_view literal; TypeLiteralType type; void append(const string_view &text) { if (!literalText) literalText = new string(literal); @@ -152,7 +151,7 @@ namespace ts::vm { } virtual ~TypeLiteral() { - if (literalText) delete literalText; + delete literalText; } string_view text() { diff --git a/src/checker/vm.h b/src/checker/vm.h index 6616c02..63d9ba7 100644 --- a/src/checker/vm.h +++ b/src/checker/vm.h @@ -51,7 +51,7 @@ namespace ts::vm { return (++s.i == s.iterator.size()) ? (s.i = 0, false) : true; } - vector> toGroup(shared type) { + vector> toGroup(shared &type) { if (type->kind == TypeKind::Boolean) { return {make_shared("false", TypeLiteralType::String), make_shared("true", TypeLiteralType::String)}; } else if (type->kind == TypeKind::Null) { @@ -82,8 +82,8 @@ namespace ts::vm { } } - void add(shared item) { - stack.push_back({iterator: toGroup(item), i: 0, round: 0}); + void add(shared &item) { + stack.push_back({.iterator=toGroup(item), .i= 0, .round= 0}); } vector>> calculate() { @@ -130,7 +130,7 @@ namespace ts::vm { * e.g. [string, number][0] => string * e.g. [string, number][number] => string | number */ - shared indexAccess(shared container, shared index) { + shared indexAccess(shared &container, shared &index) { if (container->kind == TypeKind::Array) { // if ((index.kind == TypeKind::literal && 'number' == typeof index.literal) || index.kind == TypeKind::number) return container.type; // if (index.kind == TypeKind::literal && index.literal == 'length') return { kind: TypeKind::number }; @@ -257,7 +257,7 @@ namespace ts::vm { return sp - initialSp; } - Frame(Frame *previous = nullptr): previous(previous) { + explicit Frame(Frame *previous = nullptr): previous(previous) { if (previous) { sp = previous->sp; initialSp = previous->sp; @@ -265,17 +265,35 @@ namespace ts::vm { } }; - struct Subroutine { + struct ModuleSubroutine { + string_view name; + bool exported = false; + unsigned int address; + }; + + struct Module { const string_view &bin; + vector subroutines; + unsigned int mainAddress; + + unsigned int getAddressOfSubroutine(unsigned int index) { + return subroutines[index].address; + } + }; + + struct Subroutine { + Module &module; unsigned int ip = 0; //instruction pointer unsigned int end = 0; //last instruction pointer + unsigned int index = 0; + string_view name; unsigned int depth = 0; bool active = true; unsigned int typeArguments = 0; sharedOpt resultType = nullptr; sharedOpt previous = nullptr; - Subroutine(const string_view &bin): bin(bin) {} + explicit Subroutine(Module &module): module(module) {} }; static unsigned char emptyTuple[] = { @@ -297,6 +315,37 @@ namespace ts::vm { shared stringToNum(VM &vm); + inline Module parseHeader(const string_view &bin) { + Module module{.bin = 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::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 = name, + .address = address, + }); + break; + } + case OP::Main: { + module.mainAddress = readUint32(bin, i + 1); + return module; + } + } + } + + throw runtime_error("No OP::Main found"); + } + class VM { public: vector errors; @@ -311,7 +360,7 @@ namespace ts::vm { unordered_map(VM &)>> optimised; VM() { - if (frame) delete frame; + delete frame; // stack.reserve(1000); } @@ -322,7 +371,22 @@ namespace ts::vm { } } - void call(const string_view &bin, unsigned int address = 0, unsigned int arguments = 0) { + void run(const string_view &bin) { + auto module = parseHeader(bin); + + if (subroutine) throw runtime_error("Subroutine already running"); + + auto next = make_shared(module); + next->ip = module.mainAddress; + next->end = module.bin.size(); + next->previous = subroutine; + subroutine = next; + frame = new Frame(frame); + + process(); + } + + void call(Module &module, unsigned int index = 0, unsigned int arguments = 0) { //todo: check if address was already calculated using unordered_map? const auto loopRunning = !!subroutine; if (!loopRunning) { @@ -334,13 +398,11 @@ namespace ts::vm { // push(make_shared()); // return; // } - // } - auto next = make_shared(bin); - next->ip = address; - next->end = bin.size(); -// debug("call {}({})", address, arguments); + auto next = make_shared(module); + next->ip = module.getAddressOfSubroutine(index); + next->end = module.bin.size(); next->previous = subroutine; next->depth = subroutine ? subroutine->depth + 1 : 0; @@ -356,7 +418,7 @@ namespace ts::vm { subroutine = next; if (loopRunning) subroutine->ip--; //`subroutine` is set to something new, so for() increments its ip, which we don't want - if (!loopRunning) this->process(); + if (!loopRunning) process(); // print(); // return next->resultType; @@ -459,19 +521,14 @@ namespace ts::vm { } void process() { - while (subroutine) { for (; subroutine->active && subroutine->ip < subroutine->end; subroutine->ip++) { - const auto op = (OP) subroutine->bin[subroutine->ip]; -// debug("[{}] OP {} ({} -> {})", subroutine->depth, op, subroutine->ip, (unsigned int)bin[subroutine->ip]); - - - if (op == OP::TypeArgument && (subroutine->bin[subroutine->ip + 1]) == OP::TypeArgument) { - } + const auto op = (OP) subroutine->module.bin[subroutine->ip]; +// debug("[{}] OP {} ({} -> {})", subroutine->depth, op, subroutine->ip, (unsigned int)op); switch (op) { case OP::Jump: { - subroutine->ip = readUint32(subroutine->bin, 1) - 1; //minus 1 because for's i++ + subroutine->ip = readUint32(subroutine->module.bin, 1) - 1; //minus 1 because for's i++ break; } case OP::Extends: { @@ -493,7 +550,7 @@ namespace ts::vm { if (!next) { //done auto types = popFrame(); - if (types.size() == 0) { + if (types.empty()) { push(make_shared()); } else if (types.size() == 1) { push(types[0]); @@ -508,35 +565,38 @@ namespace ts::vm { subroutine->ip += 4; } else { //next - const auto loopProgram = readUint32(subroutine->bin, subroutine->ip + 1); + 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->bin, loopProgram, 1); + call(subroutine->module, loopProgram, 1); } break; } case OP::JumpCondition: { auto condition = pop(); - const auto leftProgram = readUint16(subroutine->bin, subroutine->ip + 1); - const auto rightProgram = readUint16(subroutine->bin, subroutine->ip + 3); + const auto leftProgram = readUint16(subroutine->module.bin, subroutine->ip + 1); + const auto rightProgram = readUint16(subroutine->module.bin, subroutine->ip + 3); subroutine->ip += 4; // debug("{} ? {} : {}", stringify(condition), leftProgram, rightProgram); - call(subroutine->bin, isConditionTruthy(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 + //which we don't want. + // | [T] [T] [R] | + if (frame->variables) { + stack[frame->sp - 1 - frame->variables] = 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; - frame->sp++; //consume the new stack entry from the function call, making it part of the previous 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 (!t) { -// throw runtime_error("Stack empty"); -// } -// debug("routine result: {}", vm::stringify(last())); +// debug("routine {} result: {}", subroutine->name, vm::stringify(last())); break; } case OP::TypeArgument: { @@ -546,6 +606,7 @@ namespace ts::vm { push(unknown); } subroutine->typeArguments++; + frame->variables++; break; } case OP::TypeArgumentDefault: { @@ -553,9 +614,9 @@ namespace ts::vm { //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 = readUint32(subroutine->bin, subroutine->ip + 1); + const auto address = readUint32(subroutine->module.bin, subroutine->ip + 1); subroutine->ip += 4; //jump over address - call(subroutine->bin, address, 0); //the result is pushed on the stack + call(subroutine->module, address, 0); //the result is pushed on the stack } else { subroutine->ip += 4; //jump over address } @@ -571,11 +632,10 @@ namespace ts::vm { break; } case OP::Loads: { - const auto frameOffset = readUint16(subroutine->bin, subroutine->ip + 1); - const auto varIndex = readUint16(subroutine->bin, subroutine->ip + 3); + const auto frameOffset = readUint16(subroutine->module.bin, subroutine->ip + 1); + const auto varIndex = readUint16(subroutine->module.bin, subroutine->ip + 3); subroutine->ip += 4; if (frameOffset == 0) { - //todo: this does not work with std::stack. what could be an alternative? push(stack[frame->initialSp + varIndex]); } else if (frameOffset == 1) { push(stack[frame->previous->initialSp + varIndex]); @@ -592,17 +652,17 @@ namespace ts::vm { break; } case OP::Call: { - const auto address = readUint32(subroutine->bin, subroutine->ip + 1); - const auto arguments = readUint16(subroutine->bin, subroutine->ip + 5); + const auto address = readUint32(subroutine->module.bin, subroutine->ip + 1); + const auto arguments = readUint16(subroutine->module.bin, subroutine->ip + 5); subroutine->ip += 6; - call(subroutine->bin, address, arguments); + call(subroutine->module, address, arguments); break; } case OP::Assign: { - auto lvalue = pop(); auto rvalue = pop(); + auto lvalue = pop(); if (!isExtendable(lvalue, rvalue)) { - errors.push_back(fmt::format("{}={} not assignable!", stringify(lvalue), stringify(rvalue))); + errors.push_back(fmt::format("{}={} not assignable!", stringify(rvalue), stringify(lvalue))); } break; } @@ -714,26 +774,26 @@ namespace ts::vm { break; } case OP::NumberLiteral: { - const auto address = readUint32(subroutine->bin, subroutine->ip + 1); + const auto address = readUint32(subroutine->module.bin, subroutine->ip + 1); subroutine->ip += 4; - push(make_shared(readStorage(subroutine->bin, address), TypeLiteralType::Number)); + push(make_shared(readStorage(subroutine->module.bin, address), TypeLiteralType::Number)); break; } case OP::BigIntLiteral: { - const auto address = readUint32(subroutine->bin, subroutine->ip + 1); + const auto address = readUint32(subroutine->module.bin, subroutine->ip + 1); subroutine->ip += 4; - push(make_shared(readStorage(subroutine->bin, address), TypeLiteralType::Bigint)); + push(make_shared(readStorage(subroutine->module.bin, address), TypeLiteralType::Bigint)); break; } case OP::StringLiteral: { - const auto address = readUint32(subroutine->bin, subroutine->ip + 1); + const auto address = readUint32(subroutine->module.bin, subroutine->ip + 1); subroutine->ip += 4; - auto text = readStorage(subroutine->bin, address); + auto text = readStorage(subroutine->module.bin, address); push(make_shared(text, TypeLiteralType::String)); break; } default: { - throw runtime_error(fmt::format("unhandeled op {}", op)); + throw runtime_error(fmt::format("unhandled op {}", op)); } } } @@ -749,7 +809,14 @@ namespace ts::vm { //short path for `{'asd'}` if (types.size() == 1 && types[0]->kind == TypeKind::Literal) { - push(types[0]); + //we can not just change the TypeLiteral->type, we have to create a new one +// 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); + } + push(res); return; } diff --git a/src/tests/test_checker.cpp b/src/tests/test_checker.cpp index 71f80f2..fb9d5a1 100644 --- a/src/tests/test_checker.cpp +++ b/src/tests/test_checker.cpp @@ -3,9 +3,27 @@ #include "../parser2.h" #include "../checker/compiler.h" #include "../checker/vm.h" +#include "../checker/debug.h" using namespace ts; +string compile(string code) { + 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(); + checker::printBin(bin); + return bin; +} + +vector run(string code) { + vm::VM vm; + vm.run(compile(code)); + vm.printErrors(); + return vm.errors; +} + TEST(checker, program) { checker::Program program; @@ -20,7 +38,6 @@ TEST(checker, program) { program.pushAddress(2); fmt::print("bytes {}", program.ops); - program.print(); } TEST(checker, type) { @@ -31,18 +48,16 @@ TEST(checker, type) { const v2: number = 123; )"; - auto result = parser.parseSourceFile("app.ts", code, ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); - - checker::Compiler compiler; + auto bin = compile(code); - auto program = compiler.compileSourceFile(result); - program.print(); - auto bin = program.build(); - debug("Code {} chars, to {} bytes: {}", code.size(), bin.size(), bin); + vm::VM vm; + vm.run(bin); + vm.printErrors(); + EXPECT_EQ(vm.errors.size(), 0); bench(10, [&] { vm::VM vm; - vm.call(bin); + vm.run(bin); }); } @@ -53,18 +68,35 @@ TEST(checker, type2) { type a = number; type b = string | a; const v1: b = 'abc'; + const v2: b = 23; + const v3: b = true; )"; - auto result = parser.parseSourceFile("app.ts", code, ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); + auto bin = compile(code); - checker::Compiler compiler; + vm::VM vm; + vm.run(bin); + vm.printErrors(); + EXPECT_EQ(vm.errors.size(), 1); +} - auto program = compiler.compileSourceFile(result); - program.print(); - auto bin = program.build(); +TEST(checker, type31) { + string code = R"( + type a = T; + const v1: a = 34; + const v2: a = "as"; + )"; + + auto bin = compile(code); vm::VM vm; - vm.call(bin); + vm.run(bin); + vm.printErrors(); + EXPECT_EQ(vm.errors.size(), 1); + bench(10, [&] { + vm::VM vm; + vm.run(bin); + }); } TEST(checker, type3) { @@ -79,21 +111,16 @@ TEST(checker, type3) { const v5: a = 'nope'; )"; - auto result = parser.parseSourceFile("app.ts", code, ScriptTarget::Latest, false, ScriptKind::TS, {}); - - checker::Compiler compiler; - auto program = compiler.compileSourceFile(result); - program.print(); - auto bin = program.build(); + auto bin = compile(code); vm::VM vm; - vm.call(bin); + vm.run(bin); vm.printErrors(); EXPECT_EQ(vm.errors.size(), 1); bench(10, [&] { vm::VM vm; - vm.call(bin); + vm.run(bin); }); } @@ -118,25 +145,16 @@ TEST(checker, tuple) { const var1: StringToNum<'999'> = 1002; )"; - auto result = parser.parseSourceFile("app.ts", code, ScriptTarget::Latest, false, ScriptKind::TS, {}); - - vector bin2{0, 0, 0, 0}; - checker::writeUint16(bin2, 0, 1); - debug("bin2 {}", bin2); - - checker::Compiler compiler; - auto program = compiler.compileSourceFile(result); - program.print(); - auto bin = program.build(); + auto bin = compile(code); vm::VM vm; - vm.call(bin); + vm.run(bin); vm.printErrors(); // EXPECT_EQ(vm.errors.size(), 1); bench(10, [&] { vm::VM vm; - vm.call(bin); + vm.run(bin); }); } @@ -155,7 +173,8 @@ TEST(checker, assign) { checker::Compiler compiler; auto program = compiler.compileSourceFile(result); - program.print(); + auto bin = program.build(); + checker::printBin(bin); debug("done"); } @@ -200,8 +219,7 @@ TEST(checker, basic) { print(i); )"; - auto result = parser.parseSourceFile("app.ts", code, ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); - debug("done"); + compile(code); } TEST(checker, basic2) {