diff --git a/src/checker/MemoryPool.h b/src/checker/MemoryPool.h index 8a50d62..ab1ed77 100644 --- a/src/checker/MemoryPool.h +++ b/src/checker/MemoryPool.h @@ -34,6 +34,8 @@ class MemoryPool { ~MemoryPool() noexcept; + unsigned int active = 0; + MemoryPool &operator=(const MemoryPool &memoryPool) = delete; MemoryPool &operator=(MemoryPool &&memoryPool) noexcept; @@ -44,6 +46,9 @@ class MemoryPool { pointer allocate(size_type n = 1, const_pointer hint = 0); void deallocate(pointer p, size_type n = 1); + // Schedules for garbage collection + void gc(pointer p); + size_type max_size() const noexcept; template @@ -172,6 +177,7 @@ MemoryPool::allocateBlock() { template inline typename MemoryPool::pointer MemoryPool::allocate(size_type n, const_pointer hint) { + active++; if (freeSlots_ != nullptr) { pointer result = reinterpret_cast(freeSlots_); freeSlots_ = freeSlots_->next; @@ -179,15 +185,25 @@ MemoryPool::allocate(size_type n, const_pointer hint) { } else { if (currentSlot_ >= lastSlot_) { allocateBlock(); -// printf("allocate new block\n"); } return reinterpret_cast(currentSlot_++); } } +template +inline void +MemoryPool::gc(pointer p) { + deallocate(p); +// if (p != nullptr) { +// reinterpret_cast(p)->next = freeSlots_; +// freeSlots_ = reinterpret_cast(p); +// } +} + template inline void MemoryPool::deallocate(pointer p, size_type n) { + active--; if (p != nullptr) { reinterpret_cast(p)->next = freeSlots_; freeSlots_ = reinterpret_cast(p); diff --git a/src/checker/check2.h b/src/checker/check2.h index cbf6333..84e447e 100644 --- a/src/checker/check2.h +++ b/src/checker/check2.h @@ -3,34 +3,70 @@ #include "./types2.h" namespace ts::vm2 { + + namespace check { + struct Check { + Type *left; + Type *right; + }; + + inline std::array checks; + inline unsigned int depth; + } + /** * `left extends right ? true : false` */ bool extends(Type *left, Type *right) { switch (right->kind) { + case TypeKind::Union: { + auto current = (TypeRef *) right->type; + while (current) { + if (extends(left, current->type)) return true; + current = current->next; + } + return false; + } case TypeKind::Literal: { switch (left->kind) { case TypeKind::Literal: //todo: literal type - return left->hash == right->hash; + if ((left->flag & TypeFlag::StringLiteral && right->flag & TypeFlag::StringLiteral) || (left->flag & TypeFlag::NumberLiteral && right->flag & TypeFlag::NumberLiteral)) + return left->hash == right->hash; + return (left->flag & TypeFlag::True && right->flag & TypeFlag::True) || left->flag & TypeFlag::False && right->flag & TypeFlag::False; } - break; + return false; } case TypeKind::String: { switch (left->kind) { - case TypeKind::String: return true; - case TypeKind::Literal: return left->flag | TypeFlag::StringLiteral; + case TypeKind::String: + return true; + case TypeKind::Literal: + return left->flag & TypeFlag::StringLiteral; } - break; + return false; } case TypeKind::Number: { switch (left->kind) { - case TypeKind::Number: return true; - case TypeKind::Literal: return left->flag | TypeFlag::NumberLiteral; + case TypeKind::Number: + return true; + case TypeKind::Literal: + return left->flag & TypeFlag::NumberLiteral; + } + break; + } + case TypeKind::Boolean: { + switch (left->kind) { + case TypeKind::Boolean: + return true; + case TypeKind::Literal: + return left->flag & TypeFlag::True || left->flag & TypeFlag::False; } break; } } return false; } + + } \ No newline at end of file diff --git a/src/checker/compiler.h b/src/checker/compiler.h index 6cb7e0a..2b6feee 100644 --- a/src/checker/compiler.h +++ b/src/checker/compiler.h @@ -101,7 +101,8 @@ namespace ts::checker { SourceMap sourceMap; //SourceMap of "main" vector storage; //all kind of literals, as strings - unordered_map> storageMap; //used to deduplicated storage entries + unordered_map> storageMap; //used to deduplicated storage entries + unsigned int storageIndex{}; shared frame = make_shared(); @@ -209,7 +210,7 @@ namespace ts::checker { //errors need to be part of main sourceMap.push(0, node->pos, node->end); ops.push_back(OP::Error); - vm::writeUint16(ops, ops.size(), (unsigned int)code); + vm::writeUint16(ops, ops.size(), (unsigned int) code); } void pushSymbolAddress(Symbol &symbol) { @@ -314,11 +315,11 @@ namespace ts::checker { //note: make sure the same name is not added twice. needs hashmap unsigned int registerStorage(const string_view &s) { - if (!storageIndex) storageIndex = 5; //jump+address + if (!storageIndex) storageIndex = 1 + 4; //jump+address const auto address = storageIndex; storage.push_back(s); - storageIndex += 2 + s.size(); + storageIndex += 8 + 2 + s.size(); //hash + size + data return address; } @@ -344,13 +345,14 @@ namespace ts::checker { vm::writeUint32(bin, bin.size(), 0); //set after storage handling for (auto &&item: storage) { - address += 2 + item.size(); + address += 8 + 2 + item.size(); //hash+size+data } //set initial jump position to right after the storage data vm::writeUint32(bin, 1, address); //push all storage data to the binary for (auto &&item: storage) { + vm::writeUint64(bin, bin.size(), hash::runtime_hash(item)); vm::writeUint16(bin, bin.size(), item.size()); bin.insert(bin.end(), item.begin(), item.end()); } @@ -432,26 +434,35 @@ namespace ts::checker { } break; } - case types::SyntaxKind::NeverKeyword: program.pushOp(OP::Never, node); + case types::SyntaxKind::NeverKeyword: + program.pushOp(OP::Never, node); break; - case types::SyntaxKind::BooleanKeyword: program.pushOp(OP::Boolean, node); + case types::SyntaxKind::BooleanKeyword: + program.pushOp(OP::Boolean, node); break; - case types::SyntaxKind::StringKeyword: program.pushOp(OP::String, node); + case types::SyntaxKind::StringKeyword: + program.pushOp(OP::String, node); break; - case types::SyntaxKind::NumberKeyword: program.pushOp(OP::Number, node); + case types::SyntaxKind::NumberKeyword: + program.pushOp(OP::Number, node); break; - case types::SyntaxKind::BigIntLiteral: program.pushOp(OP::BigIntLiteral, node); + case types::SyntaxKind::BigIntLiteral: + program.pushOp(OP::BigIntLiteral, node); program.pushStorage(to(node)->text); break; - case types::SyntaxKind::NumericLiteral: program.pushOp(OP::NumberLiteral, node); + case types::SyntaxKind::NumericLiteral: + program.pushOp(OP::NumberLiteral, node); program.pushStorage(to(node)->text); break; - case types::SyntaxKind::StringLiteral: program.pushOp(OP::StringLiteral, node); + case types::SyntaxKind::StringLiteral: + program.pushOp(OP::StringLiteral, node); program.pushStorage(to(node)->text); break; - case types::SyntaxKind::TrueKeyword: program.pushOp(OP::True, node); + case types::SyntaxKind::TrueKeyword: + program.pushOp(OP::True, node); break; - case types::SyntaxKind::FalseKeyword: program.pushOp(OP::False, node); + case types::SyntaxKind::FalseKeyword: + program.pushOp(OP::False, node); break; case types::SyntaxKind::IndexedAccessType: { const auto n = to(node); @@ -547,7 +558,7 @@ namespace ts::checker { const auto n = to(node); auto &symbol = program.pushSymbolForRoutine(n->name->escapedText, SymbolType::Type, n); //move this to earlier symbol-scan round - if (symbol.declarations > 1) { + if (symbol.declarations>1) { //todo: for functions/variable embed an error that symbol was declared twice in the same scope } else { //populate routine @@ -602,7 +613,7 @@ namespace ts::checker { const auto n = to(node); if (const auto id = to(n->name)) { auto &symbol = program.pushSymbolForRoutine(id->escapedText, SymbolType::Function, id); //move this to earlier symbol-scan round - if (symbol.declarations > 1) { + if (symbol.declarations>1) { //todo: embed error since function is declared twice } else { if (n->typeParameters) { @@ -974,7 +985,8 @@ namespace ts::checker { break; } - default: throw runtime_error(fmt::format("BinaryExpression Operator token {} not handled", n->operatorToken->kind)); + default: + throw runtime_error(fmt::format("BinaryExpression Operator token {} not handled", n->operatorToken->kind)); } break; } @@ -988,7 +1000,7 @@ namespace ts::checker { const auto n = to(node); if (const auto id = to(n->name)) { auto &symbol = program.pushSymbolForRoutine(id->escapedText, SymbolType::Variable, id); //move this to earlier symbol-scan round - if (symbol.declarations > 1) { + if (symbol.declarations>1) { //todo: embed error since variable is declared twice } else { if (n->type) { diff --git a/src/checker/debug.h b/src/checker/debug.h index 44ee067..ec4382e 100644 --- a/src/checker/debug.h +++ b/src/checker/debug.h @@ -45,11 +45,11 @@ namespace ts::checker { for (unsigned int i = 0; i < end; i++) { if (storageEnd) { while (i < storageEnd) { - auto size = vm::readUint16(bin, i); - auto data = bin.substr(i + 2, size); + auto size = vm::readUint16(bin, i + 8); + auto data = bin.substr(i + 8 + 2, size); if (print) fmt::print("(Storage ({})\"{}\") ", size, data); result.storages.push_back(string(data)); - i += 2 + size; + i += 8 + 2 + size; } debug(""); storageEnd = 0; @@ -103,7 +103,7 @@ namespace ts::checker { case OP::Subroutine: { auto nameAddress = vm::readUint32(bin, i + 1); auto address = vm::readUint32(bin, i + 5); - string name = nameAddress ? string(vm::readStorage(bin, nameAddress)) : ""; + string name = nameAddress ? string(vm::readStorage(bin, nameAddress + 8)) : ""; params += fmt::format(" {}[{}]", name, address); i += 8; result.subroutines.push_back({.name = name, .address = address}); @@ -168,7 +168,7 @@ namespace ts::checker { case OP::BigIntLiteral: case OP::StringLiteral: { auto address = vm::readUint32(bin, i + 1); - params += fmt::format(" \"{}\"", vm::readStorage(bin, address)); + params += fmt::format(" \"{}\"", vm::readStorage(bin, address + 8)); i += 4; break; } diff --git a/src/checker/types2.h b/src/checker/types2.h index 3334573..e40692a 100644 --- a/src/checker/types2.h +++ b/src/checker/types2.h @@ -1,7 +1,11 @@ #pragma once #include +#include +#include +#include #include "../enum.h" +#include "../hash.h" namespace ts::vm2 { using std::string; @@ -18,19 +22,91 @@ namespace ts::vm2 { PropertySignature, ObjectLiteral, Union, + Array, Tuple, TupleMember, + TemplateLiteral, }; 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, + 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; + + inline constexpr auto defaultTypesSize = 1; + struct Types { + std::array list; + unsigned int p; +// std::vector *extended = nullptr; +// +// ~Types() { +// delete extended; +// } +// +// void reserve(unsigned int size) { +// if (p > 0) throw std::runtime_error("nope"); +// +// if (size>defaultTypesSize) { +// if (extended) { +// extended->reserve(size); +// } else { +// createExtended(size); +// } +// } +// } +// +// void createExtended(unsigned int size) { +// if (!extended) { +// extended = new std::vector(size); +// extended->insert(extended->begin(), list.begin(), list.end()); +// } +// } + + void push(Type *type) { +// if (extended) { +// extended->push_back(type); +// } else { + if (p>defaultTypesSize) { +// createExtended(300); +// extended = new std::vector(32); +// extended->insert(extended->begin(), list.begin(), list.end()); +// extended->push_back(type); + } else { + list[p++] = type; + } +// } + } + + Type **begin() { +// if (extended) return &*extended->begin(); + return list.begin(); + } + + Type **end() { +// if (extended) return &*extended->end(); + return list.begin() + p; + } + + Type *back() { + return list[p - 1]; + } + + unsigned int size() { + return p; + } + }; + + struct TypeRef { + Type *type; + TypeRef *next; }; struct Type { @@ -39,13 +115,121 @@ namespace ts::vm2 { string_view text; /** see TypeFlag */ unsigned int flag; + unsigned int users; uint64_t hash; -// string_view text2; - Type *type; -// Type * type2; - vector types; + void *type; //either Type* or TypeRef* depending on kind + + void setLiteral(TypeFlag flag, std::string_view value) { + text = value; + this->flag |= flag; + hash = hash::runtime_hash(value); + } + + void readStorage(const string_view &bin, uint32_t offset) { + //offset points to the start of the storage entry: its structure is: hash+size+data; + hash = vm::readUint64(bin, offset); + offset += 8; + text = vm::readStorage(bin, offset); + } }; + inline void stringifyType(Type *type, std::string &r) { + switch (type->kind) { + case TypeKind::Boolean: { + r += "boolean"; + break; + } + case TypeKind::Number: { + r += "number"; + break; + } + case TypeKind::String: { + r += "string"; + break; + } + case TypeKind::Never: { + r += "never"; + break; + } + case TypeKind::Any: { + r += "any"; + break; + } + case TypeKind::Unknown: { + r += "unknown"; + break; + } + case TypeKind::PropertySignature: { + r += string(type->text) + ": "; + stringifyType((Type *)type->type, r); + break; + } + case TypeKind::ObjectLiteral: { + r += "{"; + auto current = (TypeRef *)type->type; + while (current) { + stringifyType(current->type, r); + current = current->next; + r + "; "; + } + r += "}"; + break; + } + case TypeKind::TupleMember: { + if (!type->text.empty()) { + r += string(type->text); + if (type->flag & TypeFlag::Optional) r += "?"; + r += ": "; + } + stringifyType((Type *)type->type, r); + break; + } + case TypeKind::Tuple: { + r += "["; + auto current = (TypeRef *)type->type; + while (current) { + stringifyType(current->type, r); + current = current->next; + if (current) r += ", "; + } + r += "]"; + break; + } + case TypeKind::Union: { + auto current = (TypeRef *)type->type; + while (current) { + stringifyType(current->type, r); + current = current->next; + if (current) r += " | "; + } + break; + } + case TypeKind::Literal: { + if (type->flag & TypeFlag::StringLiteral) { + r += string("\"").append(type->text).append("\""); + } else if (type->flag & TypeFlag::NumberLiteral) { + r += type->text; + } else if (type->flag & TypeFlag::True) { + r += "true"; + } else if (type->flag & TypeFlag::False) { + r += "false"; + } else { + r += "UnknownLiteral"; + } + break; + } + default: { + r += "*notStringified*"; + } + } + } + + inline string stringify(Type *type) { + std::string r; + stringifyType(type, r); + return r; + } + // struct TypeMeta { // string_view typeName; // }; diff --git a/src/checker/utils.h b/src/checker/utils.h index 6b047a1..d9d569c 100644 --- a/src/checker/utils.h +++ b/src/checker/utils.h @@ -8,6 +8,14 @@ namespace ts::vm { using std::vector; using std::string_view; + inline uint32_t readUint64(const vector &bin, unsigned int offset) { + return *(uint64_t *) (bin.data() + offset); + } + + inline uint32_t readUint64(const string_view &bin, unsigned int offset) { + return *(uint64_t *) (bin.data() + offset); + } + inline uint32_t readUint32(const vector &bin, unsigned int offset) { return *(uint32_t *) (bin.data() + offset); } @@ -21,6 +29,11 @@ namespace ts::vm { *(uint32_t *) (bin.data() + offset) = value; } + inline void writeUint64(vector &bin, unsigned int offset, uint64_t value) { + if (offset + 8 > bin.size()) bin.resize(bin.size() + 8); + *(uint64_t *) (bin.data() + offset) = value; + } + inline uint16_t readUint16(const vector &bin, unsigned int offset) { return *(uint16_t *) (bin.data() + offset); } diff --git a/src/checker/vm2.cpp b/src/checker/vm2.cpp index b0a77ee..64221af 100644 --- a/src/checker/vm2.cpp +++ b/src/checker/vm2.cpp @@ -1,9 +1,9 @@ #include "./vm2.h" #include "../hash.h" #include "./check2.h" +#include namespace ts::vm2 { - void prepare(shared &module) { parseHeader(module); // if (activeSubroutine) throw std::runtime_error("Subroutine already running"); @@ -18,23 +18,133 @@ namespace ts::vm2 { activeSubroutine->depth = 0; } - Type *pop() { - return stack[--sp]; + // TypeRef is an owning reference + inline TypeRef *useAsRef(Type *type) { + type->users++; + auto t = poolRef.newElement(); + t->type = type; + return t; + } + + inline Type *allocate(TypeKind kind) { + auto type = pool.newElement(); + type->kind = kind; + type->users = 0; +// debug("allocate {}", stringify(type)); + return type; + } + + void gc(TypeRef *typeRef) { + if (gcQueueRefIdx>=maxGcSize) { + //garbage collect now + gcRefFlush(); + } + gcQueueRef[gcQueueRefIdx++] = typeRef; + } + + void gc(Type *type) { + if (gcQueueIdx>=maxGcSize) { + //garbage collect now + gcFlush(); + } +// debug("gc ({}) {}", type->users, stringify(type)); + switch (type->kind) { + case TypeKind::Union: + case TypeKind::Tuple: + case TypeKind::TemplateLiteral: + case TypeKind::ObjectLiteral: { + auto current = (TypeRef *) type->type; + while (current) { + current->type->users--; + gc(current->type); + current = current->next; + } + break; + } + case TypeKind::Array: + case TypeKind::PropertySignature: + case TypeKind::TupleMember: { + ((Type *) type->type)->users--; + gc((Type *) type->type); + break; + } + } + gcQueue[gcQueueIdx++] = type; + } + + inline Type *use(Type *type) { +// debug("use({})", stringify(type)); + type->users++; + return type; + } + + void gcRefFlush() { + for (unsigned int i = 0; iusers) continue; + pool.deleteElement(type); + } + gcQueueIdx = 0; + } + + void drop(TypeRef *typeRef) { + if (typeRef == nullptr) return; + gc(typeRef); + drop(typeRef->type); + drop(typeRef->next); + } + + void drop(Type *type) { + if (type == nullptr) return; + + type->users--; +// debug("drop ({}) {}", type->users, stringify(type)); + if (!type->users) { + gc(type); + } + } + + void gcStack() { + for (unsigned int i = 0; i &module) { + for (auto &&subroutine: module->subroutines) { + if (subroutine.result) drop(subroutine.result); + if (subroutine.narrowed) drop(subroutine.narrowed); + } + module->clear(); + } + + inline void push(Type *type) { stack[sp++] = type; //reinterpret_cast(type); } - std::span popFrame() { + inline Type *pop() { + auto type = stack[--sp]; + type->users++; + return type; + } + + inline 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; + frame = frames.pop(); //&frames[--frameIdx]; return sub; } - void moveFrame(std::vector to) { + inline 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); @@ -43,51 +153,6 @@ namespace ts::vm2 { 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); @@ -103,7 +168,10 @@ namespace ts::vm2 { inline void pushFrame() { auto next = frames.push(); ///&frames[frameIdx++]; + //important to reset necessary stuff, since we reuse next->initialSp = sp; +// debug("pushFrame {}", sp); + next->variables = 0; frame = next; } @@ -122,13 +190,18 @@ namespace ts::vm2 { activeSubroutine->ip++; auto nextActiveSubroutine = activeSubroutines.push(); //&activeSubroutines[++activeSubroutineIdx]; + //important to reset necessary stuff, since we reuse nextActiveSubroutine->ip = routine->address; nextActiveSubroutine->module = activeSubroutine->module; + nextActiveSubroutine->subroutine = routine; nextActiveSubroutine->depth = activeSubroutine->depth + 1; + nextActiveSubroutine->typeArguments = 0; activeSubroutine = nextActiveSubroutine; auto nextFrame = frames.push(); //&frames[++frameIdx]; + //important to reset necessary stuff, since we reuse nextFrame->initialSp = sp; + nextFrame->variables = 0; if (arguments) { //we move x arguments from the old stack frame to the new one nextFrame->initialSp -= arguments; @@ -141,15 +214,220 @@ namespace ts::vm2 { return type->flag & TypeFlag::True; } + /** + * Query a container type and return the result. + * + * container[index] + * + * e.g. {a: string}['a'] => string + * e.g. {a: string, b: number}[keyof T] => string | number + * e.g. [string, number][0] => string + * e.g. [string, number][number] => string | number + */ + inline Type *indexAccess(Type *container, Type *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 }; + } else if (container->kind == TypeKind::Tuple) { + if (index->hash == hash::const_hash("length")) { + auto t = allocate(TypeKind::Literal); +// t->setLiteral(TypeFlag::NumberLiteral, std::to_string(container->types.size())); +// auto t = make_shared("", TypeLiteralType::Number); +// t->append(to(container)->types.size()); + return t; + } + +// if (index.kind == TypeKind::literal && 'number' == typeof index.literal && index.literal < 0) { +// index = { kind: TypeKind::number }; +// } +// +// if (index.kind == TypeKind::literal && 'number' == typeof index.literal) { +// type b0 = [string, boolean?][0]; //string +// type b1 = [string, boolean?][1]; //boolean|undefined +// type a0 = [string, ...number[], boolean][0]; //string +// type a1 = [string, ...number[], boolean][1]; //number|boolean +// type a2 = [string, ...number[], boolean][2]; //number|boolean +// type a22 = [string, ...number[], boolean][3]; //number|boolean +// // type a23 = [string, number, boolean][4]; //number|boolean +// type a3 = [string, number, ...number[], boolean][1]; //number +// type a4 = [string, number, ...number[], boolean][-2]; //string|number|boolean, minus means all +// type a5 = [string, number, ...number[], boolean][number]; //string|number|boolean +// +// let restPosition = -1; +// for (let i = 0; i < container.types.length; i++) { +// if (container.types[i].type.kind == TypeKind::rest) { +// restPosition = i; +// break; +// } +// } +// +// if (restPosition == -1 || index.literal < restPosition) { +// const sub = container.types[index.literal]; +// if (!sub) return { kind: TypeKind::undefined }; +// if (sub.optional) return { kind: TypeKind::union, types: [sub.type, { kind: TypeKind::undefined }] }; +// return sub.type; +// } +// +// //index beyond a rest, return all beginning from there as big enum +// +// const result: TypeUnion = { kind: TypeKind::union, types: [] }; +// for (let i = restPosition; i < container.types.length; i++) { +// const member = container.types[i]; +// const type = member.type.kind == TypeKind::rest ? member.type.type : member.type; +// if (!isTypeIncluded(result.types, type)) result.types.push(type); +// if (member.optional && !isTypeIncluded(result.types, { kind: TypeKind::undefined })) result.types.push({ kind: TypeKind::undefined }); +// } +// +// return unboxUnion(result); +// } else if (index.kind == TypeKind::number) { +// const union: TypeUnion = { kind: TypeKind::union, types: [] }; +// for (const sub of container.types) { +// if (sub.type.kind == TypeKind::rest) { +// if (isTypeIncluded(union.types, sub.type.type)) continue; +// union.types.push(sub.type.type); +// } else { +// if (isTypeIncluded(union.types, sub.type)) continue; +// union.types.push(sub.type); +// } +// } +// return unboxUnion(union); +// } else { +// return { kind: TypeKind::never }; +// } +// } else if (container.kind == TypeKind::objectLiteral || container.kind == TypeKind::class) { +// if (index.kind == TypeKind::literal) { +// return resolveObjectIndexType(container, index); +// } else if (index.kind == TypeKind::union) { +// const union: TypeUnion = { kind: TypeKind::union, types: [] }; +// for (const t of index.types) { +// const result = resolveObjectIndexType(container, t); +// if (result.kind == TypeKind::never) continue; +// +// if (result.kind == TypeKind::union) { +// for (const resultT of result.types) { +// if (isTypeIncluded(union.types, resultT)) continue; +// union.types.push(resultT); +// } +// } else { +// if (isTypeIncluded(union.types, result)) continue; +// union.types.push(result); +// } +// } +// return unboxUnion(union); +// } else { +// return { kind: TypeKind::never }; +// } +// } else if (container.kind == TypeKind::any) { +// return container; + } + return allocate(TypeKind::Never); //make_shared(); + } + + void handleTemplateLiteral() { +// auto types = popFrame(); +// +// //short path for `{'asd'}` +// if (types.size() == 1 && types[0]->kind == TypeKind::Literal) { +// //we can not just change the TypeLiteral->type, we have to create a new one +//// to(types[0])->type = TypeLiteralType::String; +// auto t = types[0]; +// auto res = make_shared(t->literal, TypeLiteralType::String); +// if (t->dynamicString) { +// res->dynamicString = new string(*t->dynamicString); +// } +// push(res); +// return; +// } +// +// auto result = make_shared(); +// CartesianProduct cartesian; +// for (auto &&type: types) { +// cartesian.add(type); +// } +// auto product = cartesian.calculate(); +// +// outer: +// for (auto &&combination: product) { +// auto templateType = make_shared(); +// bool hasPlaceholder = false; +//// let lastLiteral: { kind: TypeKind::literal, literal: string, parent?: Type } | undefined = undefined; +// sharedOpt lastLiteral; +// +// //merge a combination of types, e.g. [string, 'abc', '3'] as template literal => `${string}abc3`. +// for (auto &&item: combination) { +// if (item->kind == TypeKind::Never) { +// //template literals that contain a never like `prefix.${never}` are completely ignored +// goto outer; +// } +// +// if (item->kind == TypeKind::Literal) { +// if (lastLiteral) { +// lastLiteral->append(to(item)->text()); +// } else { +// lastLiteral = make_shared("", TypeLiteralType::String); +// lastLiteral->append(to(item)->text()); +// templateType->types.push_back(lastLiteral); +// } +// } else { +// hasPlaceholder = true; +// lastLiteral = nullptr; +//// item.parent = template; +// templateType->types.push_back(item); +// } +// } +// +// if (hasPlaceholder) { +// if (templateType->types.size() == 1 && templateType->types[0]->kind == TypeKind::String) { +//// templateType->types[0].parent = result; +// result->types.push_back(templateType->types[0]); +// } else { +//// templateType->parent = result; +// result->types.push_back(templateType); +// } +// } else if (lastLiteral) { +//// lastLiteral.parent = result; +// result->types.push_back(lastLiteral); +// } +// } +// +// auto t = vm::unboxUnion(result); +//// if (t.kind == TypeKind::union) for (const member of t.types) member.parent = t; +//// debug("handleTemplateLiteral: {}", stringify(t)); +// push(t); + } + + inline void mapFrameToChildren(Type *container) { + auto i = frame->initialSp + frame->variables; + auto current = (TypeRef *) (container->type = useAsRef(stack[i++])); + for (; inext = useAsRef(stack[i]); + current = current->next; + } + current->next = nullptr; + +// unsigned int start = 0; +// std::span sub{stack.data() + start, sp - start}; +// sp = frame->initialSp; +// frame = frames.pop(); //&frames[--frameIdx]; +// +// TypeRef * current = allocateRef(); +// for_each(++types.begin(), types.end(), [¤t](auto v) { +// current->next = allocateRef(v); +// current = current->next; +// }); +// current->next = nullptr; + } + void process() { start: auto &bin = activeSubroutine->module->bin; while (true) { -// debug("[{}] OP {} {}", activeSubroutine->depth, activeSubroutine->ip, (OP)bin[activeSubroutine->ip]); +// debug("[{}] OP {} {}", activeSubroutine->depth, activeSubroutine->ip, (OP) bin[activeSubroutine->ip]); switch ((OP) bin[activeSubroutine->ip]) { case OP::Halt: { // activeSubroutine = activeSubroutines.reset(); // frame = frames.reset(); +// gcFlush(); return; } case OP::Never: { @@ -168,10 +446,11 @@ namespace ts::vm2 { case OP::Assign: { auto rvalue = pop(); auto lvalue = pop(); +// debug("assign {} = {}", stringify(rvalue), stringify(lvalue)); if (!extends(lvalue, rvalue)) { // auto error = stack.errorMessage(); // error.ip = ip; - report("not assignable"); + report(fmt::format("{} = {} not assignable", stringify(rvalue), stringify(lvalue))); } // ExtendableStack stack; // if (!isExtendable(lvalue, rvalue, stack)) { @@ -179,8 +458,8 @@ namespace ts::vm2 { // error.ip = ip; // report(error); // } - gc(lvalue); - gc(rvalue); + drop(lvalue); + drop(rvalue); break; } case OP::Return: { @@ -192,6 +471,10 @@ namespace ts::vm2 { } sp = frame->initialSp + 1; frame = frames.pop(); //&frames[--frameIdx]; + if (activeSubroutine->typeArguments == 0) { +// debug("keep type result {}", activeSubroutine->subroutine->name); + activeSubroutine->subroutine->result = use(stack[sp - 1]); + } activeSubroutine = activeSubroutines.pop(); //&activeSubroutines[--activeSubroutineIdx]; goto start; break; @@ -209,7 +492,9 @@ namespace ts::vm2 { const auto leftProgram = activeSubroutine->parseUint16(); const auto rightProgram = activeSubroutine->parseUint16(); // debug("{} ? {} : {}", stringify(condition), leftProgram, rightProgram); - if (call(isConditionTruthy(condition) ? leftProgram : rightProgram, 0)) { + auto valid = isConditionTruthy(condition); + drop(condition); + if (call(valid ? leftProgram : rightProgram, 0)) { goto start; } break; @@ -223,7 +508,12 @@ namespace ts::vm2 { item->flag |= TypeFlag::BooleanLiteral; item->flag |= valid ? TypeFlag::True : TypeFlag::False; push(item); -// push(make_shared(valid ? "true" : "false", TypeLiteralType::Boolean)); + drop(right); + drop(left); + break; + } + case OP::TemplateLiteral: { + handleTemplateLiteral(); break; } case OP::Distribute: { @@ -232,7 +522,7 @@ namespace ts::vm2 { if (type->kind == TypeKind::Union) { pushFrame(); frame->loop = loops.push(); // new LoopHelper(type); - frame->loop->set(type->types); + frame->loop->set((TypeRef *) type->type); } else { push(type); const auto loopProgram = vm::readUint32(bin, activeSubroutine->ip + 1); @@ -255,9 +545,12 @@ namespace ts::vm2 { push(types[0]); } else { auto result = allocate(TypeKind::Union); - for (auto &&v: types) { - if (v->kind != TypeKind::Never) result->types.push_back(v); - } + TypeRef *current = useAsRef(types[0]); + for_each(++types.begin(), types.end(), [¤t](auto v) { + current->next = useAsRef(v); + current = current->next; + }); + current->next = nullptr; push(result); } loops.pop(); @@ -274,7 +567,6 @@ namespace ts::vm2 { goto start; } break; -// call(subroutine->module, loopProgram, 1); } break; } @@ -292,7 +584,6 @@ namespace ts::vm2 { } // debug("load var {}/{}", frameOffset, varIndex); break; - break; } case OP::TypeArgument: { if (frame->size()<=activeSubroutine->typeArguments) { @@ -304,6 +595,41 @@ namespace ts::vm2 { frame->variables++; break; } + case OP::TypeArgumentDefault: { + auto t = stack[frame->initialSp + activeSubroutine->typeArguments - 1]; + //t is always set because TypeArgument ensures that + if (t->flag & TypeFlag::UnprovidedArgument) { + gc(stack[sp]); + sp--; //remove unknown type from stack + const auto address = activeSubroutine->parseUint32(); + if (call(address, 0)) { //the result is pushed on the stack + goto start; + } + } else { + activeSubroutine->ip += 4; //jump over address + } + 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::String: { stack[sp++] = allocate(TypeKind::String); break; @@ -319,8 +645,7 @@ namespace ts::vm2 { 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->readStorage(bin, address); item->flag |= TypeFlag::NumberLiteral; stack[sp++] = item; break; @@ -328,50 +653,107 @@ namespace ts::vm2 { 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->readStorage(bin, address); item->flag |= TypeFlag::StringLiteral; stack[sp++] = item; break; } + case OP::False: { + auto item = allocate(TypeKind::Literal); + item->flag |= TypeFlag::False; + stack[sp++] = item; + break; + } + case OP::True: { + auto item = allocate(TypeKind::Literal); + item->flag |= TypeFlag::True; + 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? + auto item = allocate(TypeKind::PropertySignature); + item->type = type; + item->text = nameLiteral->text; + drop(nameLiteral); + push(item); break; } default: { - valid = false; + //popped types need either be consumed (owned) or deallocated. + drop(type); + drop(nameLiteral); 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()); + if (types.empty()) { + item->type = nullptr; + push(item); + break; + } + + item->type = useAsRef(types[0]); + auto current = (TypeRef *) item->type; + for_each(++types.begin(), types.end(), [¤t](auto v) { + current->next = useAsRef(v); + current = current->next; + }); + current->next = nullptr; 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()); + if (types.empty()) { + item->type = nullptr; + break; + } + + auto type = types[0]; + if (type->kind == TypeKind::Union) { + //if type has no owner, we can steal its children + if (type->users == 0) { + item->type = type->type; + item->type = nullptr; + //set current to the end of the list + } else { + throw std::runtime_error("Can not merge used union"); + } + } else { + item->type = useAsRef(type); + } + auto current = (TypeRef *) item->type; + + //set current to the end of the list + while (current->next) current = current->next; + + for_each(++types.begin(), types.end(), [¤t](auto v) { + if (v->kind == TypeKind::Union) { + //if type has no owner, we can steal its children + if (v->users == 0) { + current->next = (TypeRef *) v->type; + v->type = nullptr; + //set current to the end of the list + while (current->next) current = current->next; + } else { + throw std::runtime_error("Can not merge used union"); + } + } else { + current->next = useAsRef(v); + current = current->next; + } + }); + current->next = nullptr; + stack[sp++] = item; break; } @@ -384,8 +766,20 @@ namespace ts::vm2 { case OP::Tuple: { auto item = allocate(TypeKind::Tuple); auto types = popFrame(); - item->types.insert(item->types.begin(), types.begin(), types.end()); - stack[sp++] = item; + if (types.empty()) { + item->type = nullptr; + push(item); + break; + } + + item->type = useAsRef(types[0]); + auto current = (TypeRef *) item->type; + for_each(++types.begin(), types.end(), [¤t](auto v) { + current->next = useAsRef(v); + current = current->next; + }); + current->next = nullptr; + push(item); break; } default: { diff --git a/src/checker/vm2.h b/src/checker/vm2.h index 1b6d886..8f64bce 100644 --- a/src/checker/vm2.h +++ b/src/checker/vm2.h @@ -17,7 +17,7 @@ namespace ts::vm2 { using instructions::OP; using std::string_view; - constexpr auto memoryDefault = 4096 * 10; +// constexpr auto memoryDefault = 4096 * 10; // struct TypeMemoryPool2 { // MemoryPool unknown; @@ -31,20 +31,18 @@ namespace ts::vm2 { // 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; - } + constexpr auto poolSize = sizeof(Type) * 2048; + inline MemoryPool pool; + inline MemoryPool poolRef; + void gcFlush(); + void gcRefFlush(); /** * For each active subroutine this object is created. */ struct ActiveSubroutine { Module *module; + ModuleSubroutine *subroutine; // ActiveSubroutine *previous = nullptr; unsigned int ip = 0; //current instruction pointer @@ -73,22 +71,31 @@ namespace ts::vm2 { inline std::array gcQueue; inline unsigned int gcQueueIdx; + inline std::array gcQueueRef; + inline unsigned int gcQueueRefIdx; + // The stack does not own Type inline std::array stack; inline unsigned int sp = 0; struct LoopHelper { - std::span types; - unsigned int i = 0; + TypeRef *current; - void set(std::span types) { - this->types = types; - i = 0; + explicit LoopHelper() { + } + + explicit LoopHelper(TypeRef *typeRef) { + set(typeRef); + } + + void set(TypeRef *typeRef) { + current = typeRef; } Type *next() { - if (i == types.size()) return nullptr; - return types[i++]; + auto t = current->type; + current = current->next; + return t; } }; @@ -142,14 +149,21 @@ namespace ts::vm2 { void process(); + void clear(shared &module); void prepare(shared &module); + void drop(Type *type); + void drop(TypeRef *type); + void gc(TypeRef *typeRef); + void gc(Type *type); + // Garbage collect whatever is left on the stack + void gcStack(); std::span popFrame(); static void run(shared module) { // profiler.clear(); -// pool = TypeMemoryPool2(); - poolU = MemoryPool(); + pool = MemoryPool(); + poolRef = MemoryPool(); gcQueueIdx = 0; sp = 0; prepare(module); @@ -157,347 +171,4 @@ namespace ts::vm2 { } 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/checker/vm3.h b/src/checker/vm3.h new file mode 100644 index 0000000..c220aa5 --- /dev/null +++ b/src/checker/vm3.h @@ -0,0 +1,345 @@ +namespace ts::vm3 { + +// 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/tests/test_vm2.cpp b/src/tests/test_vm2.cpp index a667ffb..3a2e896 100644 --- a/src/tests/test_vm2.cpp +++ b/src/tests/test_vm2.cpp @@ -87,7 +87,6 @@ struct Pool { // } //}; - TEST(bench, size) { std::array a1; std::array a2; @@ -126,37 +125,199 @@ const v2: number = 123; }); } +TEST(vm2, allocator) { + +} + +TEST(vm2, vm2Union) { + string code = R"( +type a = T | (string | number); +const v1: a = 'yes'; +const v2: a = true; +const v3: a = false; +)"; + auto module = std::make_shared(ts::compile(code), "app.ts", code); + run(module); + module->printErrors(); + + EXPECT_EQ(module->errors.size(), 1); + ts::vm2::gcFlush(); + //only v1, v2, v3 cached value should live + EXPECT_EQ(ts::vm2::pool.active, 3); + + ts::bench("first", 1000, [&] { + ts::vm2::clear(module); +// module->clear(); + run(module); + }); +} + TEST(vm2, vm2Base2) { string code = R"( -type a = T extends string ? 'yes': 'no'; +type a = T extends string ? 'yes' : 'no'; const v1: a = 'no'; const v2: a = 'yes'; -//const v3: a = 'yes'; -const v4: a = 'nope'; +const v3: a = 'nope'; )"; auto module = std::make_shared(ts::compile(code), "app.ts", code); run(module); + module->printErrors(); + + EXPECT_EQ(module->errors.size(), 1); + ts::vm2::gcFlush(); + //only v1, v2, v3, plus 'yes' : 'no' subroutine cached value should live + EXPECT_EQ(ts::vm2::pool.active, 5); + + ts::bench("first", 1000, [&] { + module->clear(); + run(module); + }); +} +TEST(vm2, vm2Base22) { + string code = R"( +type a = K | (T extends string ? 'yes': 'no'); +const v1: a = 'no'; +const v2: a = 'yes'; +const v3: a = true; +const v4: a = 'yes'; +const v5: 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, [&] { + ts::vm2::clear(module); +// module->clear(); + run(module); + }); +} + +TEST(vm2, gc) { + // The idea of garbage collection is this: Each type has `users` counter. + // As soon as another type wants to hold on it, it increases `users`. + // This happens automatically once a user pop() from the stack. The stack itself + // is not an owner. If the user who pop()'d does not want to keep it, a drop(type) is necessary. + // This schedules it for garbage collection. + // If a type was received from somewhere else than the stack, the counter needs to be increased + // manually by using use(type). + // Subroutines like here `var1` keep the type for caching, hence this example has one active type + // and after clearing the module, all its subroutines reset their cache, so that is not no active type anymore. + string code = R"( +type b = T; +type a = 'a' extends b ? T : never; +const var1: a = false; +)"; + + auto module = std::make_shared(ts::compile(code), "app.ts", code); + run(module); + module->printErrors(); + EXPECT_EQ(module->errors.size(), 1); + + ts::vm2::gcFlush(); + //only var1 cached value should live + EXPECT_EQ(ts::vm2::pool.active, 1); + + ts::vm2::clear(module); + ts::vm2::gcFlush(); + EXPECT_EQ(ts::vm2::pool.active, 0); + + ts::bench("first", 1000, [&] { +// ts::vm2::clear(module); module->clear(); + //no vm2::clear necessary sine run() resets the type pool anyways run(module); }); } -TEST(vm2, vm2) { +TEST(vm2, gcUnion) { ts::checker::Program program; program.pushOp(OP::Frame); - for (auto i = 0; i < 300; i++) { + for (auto i = 0; i<10; i++) { + program.pushOp(OP::StringLiteral); + program.pushStorage("a" + to_string(i)); + } + program.pushOp(OP::Union); + program.pushOp(OP::Halt); + + auto module = std::make_shared(program.build(), "app.ts", ""); + run(module); + EXPECT_EQ(module->errors.size(), 0); + ts::vm2::gcStack(); + ts::vm2::gcFlush(); + EXPECT_EQ(ts::vm2::pool.active, 0); +} + +TEST(vm2, gcTuple) { + ts::checker::Program program; + program.pushOp(OP::Frame); + for (auto i = 0; i<10; i++) { + program.pushOp(OP::String); + program.pushOp(OP::TupleMember); + } + program.pushOp(OP::Tuple); + program.pushOp(OP::Halt); + + auto module = std::make_shared(program.build(), "app.ts", ""); + run(module); + EXPECT_EQ(module->errors.size(), 0); + ts::vm2::gcStack(); + ts::vm2::gcFlush(); + EXPECT_EQ(ts::vm2::pool.active, 0); +} + +TEST(vm2, gcObject) { + ts::checker::Program program; + program.pushOp(OP::Frame); + for (auto i = 0; i<10; i++) { 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::Halt); + + auto module = std::make_shared(program.build(), "app.ts", ""); + run(module); + EXPECT_EQ(module->errors.size(), 0); + ts::vm2::gcStack(); + ts::vm2::gcFlush(); + EXPECT_EQ(ts::vm2::pool.active, 0); +} + +TEST(vm2, vm2Base3) { + string code = R"( +type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; +const var1: StringToNum<'999'> = 1002; +)"; + 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, bigUnion) { + ts::checker::Program program; + program.pushOp(OP::Frame); + for (auto i = 0; i<300; i++) { + program.pushOp(OP::StringLiteral); + program.pushStorage("a" + to_string(i)); } program.pushOp(OP::Union); program.pushOp(OP::Frame); - for (auto i = 0; i < 300; i++) { + for (auto i = 0; i<300; i++) { program.pushOp(OP::Frame); program.pushOp(OP::StringLiteral); program.pushStorage("a"); @@ -170,9 +331,17 @@ TEST(vm2, vm2) { program.pushOp(OP::Halt); auto module = std::make_shared(program.build(), "app.ts", ""); + run(module); + EXPECT_EQ(module->errors.size(), 0); + + ts::vm2::clear(module); + ts::vm2::gcStack(); + ts::vm2::gcFlush(); + EXPECT_EQ(ts::vm2::pool.active, 0); ts::bench("first", 1000, [&] { module->clear(); +// ts::vm2::clear(module); run(module); // EXPECT_EQ(process(ops), 300 * 6); });