From 82001c575ecd99a47a026ecf59f0d3f8866a7452 Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Thu, 4 Aug 2022 03:46:41 +0200 Subject: [PATCH] hash table for union/object literal/class, new array memory pool, type inference --- CMakeLists.txt | 7 +- main.cpp | 35 +- package-lock.json | 305 ++++++++++ package.json | 26 + src/CMakeLists.txt | 2 +- src/checker/MemoryPool.h | 191 ------ src/checker/check2.h | 83 ++- src/checker/compiler.h | 37 +- src/checker/instructions.h | 2 + src/checker/pool_array.h | 189 ++++++ src/checker/pool_single.h | 139 +++++ src/checker/types2.h | 125 +++- src/checker/vm2.cpp | 555 ++++++++++-------- src/checker/vm2.h | 35 +- src/core.h | 4 +- src/tests/test_pool_array.cpp | 213 +++++++ ...est_allocator.cpp => test_pool_single.cpp} | 12 +- src/tests/test_vm2.cpp | 149 ++--- src/tests/test_vm2_union.cpp | 69 +++ src/tests/utils.h | 13 + tests/bench.ts | 37 ++ tests/big1.ts | 1 - 22 files changed, 1620 insertions(+), 609 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json delete mode 100644 src/checker/MemoryPool.h create mode 100644 src/checker/pool_array.h create mode 100644 src/checker/pool_single.h create mode 100644 src/tests/test_pool_array.cpp rename src/tests/{test_allocator.cpp => test_pool_single.cpp} (94%) create mode 100644 src/tests/test_vm2_union.cpp create mode 100644 tests/bench.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bc3e14..3028cb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,10 @@ project(typescript) set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_FLAGS "-Wno-unused-variable -Wno-switch") + if(CMAKE_BUILD_TYPE STREQUAL "Release") - set(CMAKE_CXX_FLAGS "-Wall -Wextra -O3 -ffast-math") + set(CMAKE_CXX_FLAGS "-Wno-unused-variable -O3 -ffast-math") endif() include_directories(libs/tracy/) @@ -12,9 +14,6 @@ include_directories(libs/fmt/include/) list(APPEND CMAKE_MODULE_PATH libs) -#add_definitions(-DTRACY_ENABLE) -#include_directories(libs/tracy/) - add_subdirectory(libs/doctest) add_subdirectory(libs/tracy) add_subdirectory(libs/fmt) diff --git a/main.cpp b/main.cpp index 1a045af..0d481b4 100644 --- a/main.cpp +++ b/main.cpp @@ -1,9 +1,12 @@ #include +#include +#include #include "./src/core.h" #include "./src/fs.h" #include "./src/parser2.h" -#include "./src/checker/vm.h" +#include "./src/checker/vm2.h" +#include "./src/checker/module2.h" #include "./src/checker/debug.h" #include "./src/checker/compiler.h" @@ -11,47 +14,49 @@ using namespace ts; void run(const string &bytecode, const string &code, const string &fileName) { - vm::VM vm; - auto module = make_shared(bytecode, fileName, code); + ZoneScoped; + auto module = std::make_shared(bytecode, fileName, code); bench(1, [&]{ - vm.run(module); - vm.printErrors(); + vm2::run(module); + module->printErrors(); }); } void compileAndRun(const string &code, const string &file, const string &fileName) { + ZoneScoped; auto bytecodePath = file + ".tsb"; - auto buffer = readFile(file); + auto buffer = fileRead(file); checker::Compiler compiler; Parser parser; auto result = parser.parseSourceFile(file, buffer, ts::types::ScriptTarget::Latest, false, ScriptKind::TS, {}); auto program = compiler.compileSourceFile(result); auto bin = program.build(); - writeFile(bytecodePath, bin); + fileWrite(bytecodePath, bin); std::filesystem::last_write_time(bytecodePath, std::filesystem::last_write_time(file)); - vm::VM vm; checker::printBin(bin); - auto module = make_shared(bin, fileName, code); - vm.run(module); - vm.printErrors(); + auto module = make_shared(bin, fileName, code); + vm2::run(module); + module->printErrors(); } int main(int argc, char *argv[]) { - std::string file = "/Users/marc/bude/typescript-cpp/tests/big1.ts"; + ZoneScoped; + std::string file = "/Users/marc/bude/typescript-cpp/tests/basic1.ts"; auto cwd = std::filesystem::current_path(); if (argc > 1) { file = cwd.string() + "/" + argv[1]; } - auto code = readFile(file); + auto code = fileRead(file); auto bytecode = file + ".tsb"; auto relative = std::filesystem::relative(file, cwd); - if (exists(bytecode) && std::filesystem::last_write_time(bytecode) == std::filesystem::last_write_time(file)) { - run(readFile(bytecode), code, relative.string()); + if (fileExists(bytecode) && std::filesystem::last_write_time(bytecode) == std::filesystem::last_write_time(file)) { + run(fileRead(bytecode), code, relative.string()); } else { compileAndRun(code, file, relative.string()); } + usleep(100000); return 0; } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..395b4ef --- /dev/null +++ b/package-lock.json @@ -0,0 +1,305 @@ +{ + "name": "typescript-cpp", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "typescript-cpp", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "ts-node": "^10.9.1", + "typescript": "^4.7.4" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" + }, + "node_modules/@types/node": { + "version": "18.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.3.tgz", + "integrity": "sha512-6qKpDtoaYLM+5+AFChLhHermMQxc3TOEFIDzrZLPRGHPrLEwqFkkT5Kx3ju05g6X7uDPazz3jHbKPX0KzCjntg==", + "peer": true + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" + }, + "@types/node": { + "version": "18.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.3.tgz", + "integrity": "sha512-6qKpDtoaYLM+5+AFChLhHermMQxc3TOEFIDzrZLPRGHPrLEwqFkkT5Kx3ju05g6X7uDPazz3jHbKPX0KzCjntg==", + "peer": true + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==" + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..35c4fe0 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "typescript-cpp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/marcj/typescript-cpp.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/marcj/typescript-cpp/issues" + }, + "homepage": "https://github.com/marcj/typescript-cpp#readme", + "dependencies": { + "ts-node": "^10.9.1", + "typescript": "^4.7.4" + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 82656c9..8105feb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,6 @@ add_library(typescript utf.h utf.cpp core.h core.cpp utilities.h utilities.cpp n # ${CMAKE_CURRENT_SOURCE_DIR}/../libs/tracy/TracyClient.cpp target_link_libraries(typescript fmt) -target_link_libraries(typescript asmjit::asmjit) +#target_link_libraries(typescript asmjit::asmjit) add_subdirectory(gui) \ No newline at end of file diff --git a/src/checker/MemoryPool.h b/src/checker/MemoryPool.h deleted file mode 100644 index 8ec2339..0000000 --- a/src/checker/MemoryPool.h +++ /dev/null @@ -1,191 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -template -class MemoryPool { -public: - typedef T value_type; - typedef T *pointer; - typedef T &reference; - typedef const T *const_pointer; - typedef const T &const_reference; - typedef size_t size_type; - - union Slot { - value_type element; - struct Pointer { - Slot *prev; - Slot *next; - } pointer; - }; - - typedef char *data_pointer; - typedef Slot slot_type; - typedef Slot *slot_pointer; - - /* Member functions */ - MemoryPool() noexcept { - currentBlock = nullptr; - firstBlock = nullptr; - currentSlot = nullptr; - lastSlot = nullptr; - freeSlots = nullptr; - } - - MemoryPool(const MemoryPool &memoryPool) noexcept: MemoryPool() {} - MemoryPool(MemoryPool &&memoryPool) noexcept { - currentBlock = memoryPool.currentBlock; - memoryPool.currentBlock = nullptr; - currentSlot = memoryPool.currentSlot; - firstBlock = memoryPool.firstBlock; - lastSlot = memoryPool.lastSlot; - freeSlots = memoryPool.freeSlots; - } - - template - MemoryPool(const MemoryPool &memoryPool) noexcept: MemoryPool() {} - - ~MemoryPool() noexcept { - slot_pointer curr = currentBlock; - while (curr != nullptr) { - slot_pointer prev = curr->pointer.prev; - operator delete(reinterpret_cast(curr)); - curr = prev; - } - } - - unsigned int active = 0; - unsigned int blocks = 0; - - MemoryPool &operator=(const MemoryPool &memoryPool) = delete; - MemoryPool &operator=(MemoryPool &&memoryPool) noexcept { - if (this != &memoryPool) { - std::swap(currentBlock, memoryPool.currentBlock); - currentSlot = memoryPool.currentSlot; - lastSlot = memoryPool.lastSlot; - firstBlock = memoryPool.firstBlock; - freeSlots = memoryPool.freeSlots; - } - return *this; - } - - pointer address(reference x) const noexcept { - return &x; - } - - const_pointer address(const_reference x) const noexcept { - return &x; - } - - // Can only allocate one object at a time. n and hint are ignored - pointer allocate() { - active++; - if (freeSlots != nullptr) { - pointer result = reinterpret_cast(freeSlots); - freeSlots = freeSlots->pointer.next; - return result; - } else { - if (currentSlot>=lastSlot) { - allocateBlock(); - } - return reinterpret_cast(currentSlot++); - } - } - - void deallocate(pointer p) { - if (p != nullptr) { - active--; - reinterpret_cast(p)->pointer = {.prev = nullptr, .next = freeSlots}; - freeSlots = reinterpret_cast(p); - } - } - - size_type max_size() const noexcept { - size_type maxBlocks = -1 / BlockSize; - return (BlockSize - sizeof(data_pointer)) / sizeof(slot_type) * maxBlocks; - } - - template - void construct(U *p, Args &&... args) { - new(p) U(std::forward(args)...); - } - - template - void destroy(U *p) { - p->~U(); - } - - template - pointer newElement(Args &&... args) { - pointer result = allocate(); - construct(result, std::forward(args)...); - return result; - } - - void deleteElement(pointer p) { - if (p != nullptr) { - p->~value_type(); - deallocate(p); - } - } - - void clear() { - active = 0; - freeSlots = nullptr; - if (firstBlock) initializeBlock(firstBlock); - } -private: - slot_pointer currentBlock; - slot_pointer firstBlock; - - slot_pointer currentSlot; - slot_pointer lastSlot; - slot_pointer freeSlots; - - size_type padPointer(data_pointer p, size_type align) const noexcept { - uintptr_t result = reinterpret_cast(p); - return ((align - result) % align); - } - - void allocateBlock() { - if (currentBlock && reinterpret_cast(currentBlock)->pointer.next) { - initializeBlock(reinterpret_cast(currentBlock)->pointer.next); - } else { - blocks++; - // Allocate space for the new block and store a pointer to the previous one - data_pointer newBlock = reinterpret_cast(operator new(BlockSize)); - reinterpret_cast(newBlock)->pointer = {.prev = currentBlock, .next = nullptr}; - setNextBlock(reinterpret_cast(newBlock)); - } - } - - void setNextBlock(slot_pointer nextBlock) { - if (currentBlock) currentBlock->pointer.next = nextBlock; - if (!firstBlock) firstBlock = nextBlock; - initializeBlock(nextBlock); - } - - slot_pointer blockStartSlot(slot_pointer block) { - auto blockPoint = reinterpret_cast(block); - // Pad block body to satisfy the alignment requirements for elements - //data_pointer body = blockPoint + sizeof(slot_pointer); - //size_type bodyPadding = padPointer(body, alignof(slot_type)); - return reinterpret_cast(blockPoint + sizeof(slot_type)); - } - - void initializeBlock(slot_pointer nextBlock) { - currentBlock = nextBlock; - //// Pad block body to satisfy the alignment requirements for elements - //data_pointer_ body = newBlock + sizeof(slot_pointer_); - //size_type bodyPadding = padPointer(body, alignof(slot_type_)); - currentSlot = blockStartSlot(nextBlock); // reinterpret_cast(body + bodyPadding); - lastSlot = reinterpret_cast(reinterpret_cast(nextBlock) + BlockSize - sizeof(slot_type) + 1); - } - - static_assert(BlockSize>=2 * sizeof(slot_type), "BlockSize too small."); -}; \ No newline at end of file diff --git a/src/checker/check2.h b/src/checker/check2.h index 13ae2e7..9e67f51 100644 --- a/src/checker/check2.h +++ b/src/checker/check2.h @@ -14,15 +14,6 @@ namespace ts::vm2 { inline unsigned int depth; } - inline Type *findMember(TypeRef *start, uint64_t hash) { - auto current = start; - while (current) { - if (current->type->hash == hash) return current->type; - current = current->next; - } - return nullptr; - } - /** * `left extends right ? true : false` */ @@ -95,7 +86,7 @@ namespace ts::vm2 { case TypeKind::PropertySignature: { switch (left->kind) { case TypeKind::PropertySignature: { - return true; + return extends(((TypeRef *) left->type)->next->type, ((TypeRef *) right->type)->next->type); } } return false; @@ -103,20 +94,38 @@ namespace ts::vm2 { case TypeKind::ObjectLiteral: { switch (left->kind) { case TypeKind::ObjectLiteral: { - auto rightCurrent = (TypeRef *) right->type; - auto leftStart = (TypeRef *) left->type; - - while (rightCurrent) { - switch (rightCurrent->type->kind) { -// case TypeKind::PropertySignature: - case TypeKind::PropertySignature: { - auto found = findMember(leftStart, rightCurrent->type->hash); - if (!found) return false; - if (!extends((Type *) found->type, (Type *) rightCurrent->type->type)) return false; - } + auto valid = true; + forEachChild(right, [&left, &valid](auto child, auto &stop) { + auto leftMember = findChild(left, child->hash); + if (!leftMember || !extends(leftMember, child)) { + stop = true; + valid = false; } - rightCurrent = rightCurrent->next; - } + }); + return valid; + + //auto rightCurrent = (TypeRef *) right->type; + //auto leftStart = (TypeRef *) left->type; + // + //auto i = 0; + //while (rightCurrent) { + // auto rightName = getPropertyOrMethodName(rightCurrent->type); + // //if (rightName) { + // //switch (rightCurrent->type->kind) { + // // case TypeKind::Method: + // // case TypeKind::MethodSignature: + // // case TypeKind::Property: + // // case TypeKind::PropertySignature: { + // // i++; + // // auto found = findMember(leftStart, rightName->hash); + // // if (!found) return false; + // // if (!extends(getPropertyOrMethodType(found), getPropertyOrMethodType(rightCurrent->type))) return false; + // // } + // //} + // //} + // rightCurrent = rightCurrent->next; + //} + //if (i == 0) return false; return true; } @@ -146,21 +155,35 @@ namespace ts::vm2 { } return true; } else { - auto current = (TypeRef *) right->type; - while (current) { - if (extends(left, current->type)) return true; - current = current->next; + //fast path first, if hash exists + if (!right->children.empty()) { + TypeRef *entry = &right->children[left->hash % right->children.size()]; + if (entry->type) { + while (entry && entry->type->hash != left->hash) { + //follow collision link + entry = entry->next; + } + if (entry) return true; + } } - return false; + + //slow path, full scan + auto valid = false; + forEachChild(right, [&left, &valid](Type *child, bool &stop) { + if (extends(left, child)) { + stop = true; + valid = true; + } + }); + return valid; } } case TypeKind::Literal: { switch (left->kind) { case TypeKind::Literal: - //todo: literal type 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; + return (left->flag & TypeFlag::True && right->flag & TypeFlag::True) || (left->flag & TypeFlag::False && right->flag & TypeFlag::False); } return false; } diff --git a/src/checker/compiler.h b/src/checker/compiler.h index 034eed9..e421376 100644 --- a/src/checker/compiler.h +++ b/src/checker/compiler.h @@ -825,14 +825,13 @@ namespace ts::checker { if (bodyAddress) { //type and body given, so we check if all `return` are valid. //it is moved in its own subroutine, so that the return type is cached and only run once. - auto checkBodyAddress = program.pushSubroutineNameLess(); + //auto checkBodyAddress = program.pushSubroutineNameLess(); + //program.pushOp(OP::CheckBody); + //program.pushAddress(bodyAddress); + //program.popSubroutine(); + program.pushOp(OP::CheckBody); program.pushAddress(bodyAddress); - program.popSubroutine(); - - program.pushOp(OP::Call); - program.pushAddress(checkBodyAddress); - program.pushUint16(0); } } else { if (bodyAddress) { @@ -1171,7 +1170,7 @@ namespace ts::checker { } else { program.pushOp(OP::Any, n); } - pushName(n, program); + pushName(n->name, program); program.pushOp(OP::PropertySignature, n->name); if (n->questionToken) program.pushOp(OP::Optional); if (hasModifier(n, SyntaxKind::ReadonlyKeyword)) program.pushOp(OP::Readonly); @@ -1185,7 +1184,7 @@ namespace ts::checker { } else { program.pushOp(OP::Any); } - pushName(n, program); + pushName(n->name, program); program.pushOp(OP::PropertySignature, node); if (n->questionToken) program.pushOp(OP::Optional); if (hasModifier(n, SyntaxKind::ReadonlyKeyword)) program.pushOp(OP::Readonly); @@ -1270,6 +1269,17 @@ namespace ts::checker { program.pushUint16(size); break; } + case SyntaxKind::NewExpression: { + const auto n = to(node); + handle(n->expression, program); + + auto argumentsCount = n->arguments ? n->arguments->length() : 0; + if (n->arguments) for (auto &&sub: n->arguments->list) handle(sub, program); + + program.pushOp(OP::New, node); + program.pushUint16(argumentsCount); + break; + } case SyntaxKind::CallExpression: { const auto n = to(node); auto typeArgumentsCount = n->typeArguments ? n->typeArguments->length() : 0; @@ -1308,7 +1318,15 @@ namespace ts::checker { case SyntaxKind::ExpressionStatement: { const auto n = to(node); handle(n->expression, program); - if (!program.expressionResult) { + + auto expressionPutsTypeOnStack = true; + switch (n->expression->kind) { + case SyntaxKind::VariableDeclarationList: { + expressionPutsTypeOnStack = false; + break; + } + } + if (expressionPutsTypeOnStack && !program.expressionResult) { //expression statements that are not used need to be removed from the stack. //.e.g `true;` or `doIt();` program.pushOp(OP::Pop); @@ -1529,7 +1547,6 @@ namespace ts::checker { auto n = to(node); switch (n->operatorToken->kind) { case SyntaxKind::EqualsToken: { - if (n->left->kind == SyntaxKind::Identifier) { const auto name = to(n->left)->escapedText; auto symbol = program.findSymbol(name); diff --git a/src/checker/instructions.h b/src/checker/instructions.h index 7bdcf81..cedb969 100644 --- a/src/checker/instructions.h +++ b/src/checker/instructions.h @@ -68,6 +68,7 @@ namespace ts::instructions { CallExpression, //JS call expression, with 1 parameter (amount of parameters) Instantiate, //instantiates a type on the stack (FunctionRef for example), ExpressionWithTypeArguments + New, /** * Reserved new stack entries to be used as type variables. @@ -126,6 +127,7 @@ namespace ts::instructions { Assign, Dup, //Duplicates the current stack end Set, //narrows/Sets a new value for a subroutine (variables) + SetAndPush, //narrows/Sets a new value for a subroutine (variables) Error, Pop, Inline, //Execute a subroutine on the same active frame diff --git a/src/checker/pool_array.h b/src/checker/pool_array.h new file mode 100644 index 0000000..c51156e --- /dev/null +++ b/src/checker/pool_array.h @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "math.h" +#include +#include + +/** + * Block is memory (Items + 1) * sizeof. Plus 1 because first entry is block header. + * Block header points to next and previous block. + */ +template +class PoolArray { +public: + unsigned int active = 0; + + union Slot { + T element; + struct Pointer { + Slot *prev; + Slot *next; + } header; + }; + + typedef char *data_pointer; + typedef Slot slot_type; + typedef Slot *slot_pointer; + + static_assert(BlockSize>=2 * sizeof(slot_type), "BlockSize too small."); + static_assert(Items>=GCQueueSize, "Items need to be bigger than GCQueueSize"); + + struct Pool { + Slot *currentBlock = nullptr; + Slot *firstBlock = nullptr; + + Slot *currentSlot = nullptr; + Slot *lastSlot = nullptr; + Slot *freeSlot = nullptr; + unsigned int blocks = 0; + unsigned int slotSize; + + std::vector gcQueue; + unsigned int gcQueued = 0; + unsigned int gcQueueSize = 0; + + explicit Pool(unsigned int slotSize): slotSize(slotSize), gcQueueSize(ceil(GCQueueSize/slotSize)) { + gcQueue.resize(gcQueueSize); + } + + void deallocate(const std::span &span) { + T *p = &span[0]; + auto slot = reinterpret_cast(p); + slot->header = {.prev = nullptr, .next = freeSlot}; + freeSlot = slot; + } + + void destruct(const std::span &span) { + for (auto &&item: span) item.~T(); + deallocate(span); + } + + void gc(const std::span &span) { + //flush queued items + if (gcQueued>=gcQueueSize) gcFlush(); + gcQueue[gcQueued++] = &span[0]; + } + + void gcFlush() { + for (unsigned int i = 0; i(gcQueue[i]); + slot->header = {.prev = nullptr, .next = freeSlot}; + freeSlot = slot; + } + gcQueued = 0; + } + }; + + constexpr static unsigned int poolAmount = 11; + std::array pools = {Pool(1), Pool(2), Pool(4), Pool(8), Pool(16), Pool(32), Pool(64), Pool(128), Pool(256), Pool(512), Pool(1024)}; + + PoolArray() noexcept {} + + ~PoolArray() noexcept { + for (auto &&pool: pools) { + slot_pointer curr = pool.currentBlock; + while (curr != nullptr) { + slot_pointer prev = curr->header.prev; + operator delete(reinterpret_cast(curr)); + curr = prev; + } + } + } + + unsigned int poolIndex(unsigned int size) { + if (size>1024) size = 1024; + return ceil(log2(size)); + } + + Pool &getPool(unsigned int size) { + return pools[poolIndex(size)]; + } + + std::span allocate(unsigned int size) { + auto &pool = getPool(size); + active += size; + if (pool.freeSlot != nullptr) { + T *result = reinterpret_cast(pool.freeSlot); + pool.freeSlot = pool.freeSlot->header.next; + return {result, size}; + } else { + if (pool.currentSlot + pool.slotSize - 1>=pool.lastSlot) { + allocateBlock(pool); + } + auto result = reinterpret_cast(pool.currentSlot + 1); + pool.currentSlot += pool.slotSize; + return {result, size}; + } + } + + void deallocate(const std::span &span) { + active -= span.size(); + auto &pool = getPool(span.size()); + pool.deallocate(span); + } + + std::span construct(unsigned int size) { + auto span = allocate(size); + for (auto &&item: span) new(&item) T(); + return span; + } + + void destruct(const std::span &span) { + for (auto &&item: span) item.~T(); + deallocate(span); + } + + void clear() { + active = 0; + for (auto &&pool: pools) { + pool.freeSlot = nullptr; + pool.gcQueued = 0; + if (pool.firstBlock) initializeBlock(pool, pool.firstBlock); + } + } + + void gc(const std::span &span) { + auto &pool = getPool(span.size()); + pool.gc(span); + } + +private: + + void allocateBlock(Pool &pool) { + if (pool.currentBlock && reinterpret_cast(pool.currentBlock)->header.next) { + initializeBlock(pool, reinterpret_cast(pool.currentBlock)->header.next); + } else { + pool.blocks++; + // Allocate space for the new block and store a pointer to the previous one + data_pointer newBlock = reinterpret_cast(operator new(BlockSize)); + reinterpret_cast(newBlock)->header = {.prev = pool.currentBlock, .next = nullptr}; + setNextBlock(pool, reinterpret_cast(newBlock)); + } + } + + void setNextBlock(Pool &pool, slot_pointer nextBlock) { + if (pool.currentBlock) pool.currentBlock->header.next = nextBlock; + if (!pool.firstBlock) pool.firstBlock = nextBlock; + initializeBlock(pool, nextBlock); + } + + slot_pointer blockStartSlot(slot_pointer block) { + auto blockPoint = reinterpret_cast(block); + return reinterpret_cast(blockPoint + sizeof(slot_type)); + } + + void initializeBlock(Pool &pool, slot_pointer nextBlock) { + pool.currentBlock = nextBlock; + pool.currentSlot = blockStartSlot(nextBlock); + pool.lastSlot = reinterpret_cast(reinterpret_cast(nextBlock) + BlockSize - sizeof(slot_type) + 1); + } +}; \ No newline at end of file diff --git a/src/checker/pool_single.h b/src/checker/pool_single.h new file mode 100644 index 0000000..41243be --- /dev/null +++ b/src/checker/pool_single.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "../core.h" + +/** + * Block is memory (Items + 1) * sizeof. Plus 1 because first entry is block header. + * Block header points to next and previous block. + */ +template +class PoolSingle { +public: + typedef T value_type; + typedef T *pointer; + typedef T &reference; + + std::array gcQueue; + unsigned int gcQueued = 0; + + union Slot { + value_type element; + struct Pointer { + Slot *prev; + Slot *next; + } pointer; + }; + + typedef char *data_pointer; + typedef Slot slot_type; + typedef Slot *slot_pointer; + static_assert(BlockSize>=2 * sizeof(slot_type), "BlockSize too small."); + + ~PoolSingle() noexcept { + slot_pointer curr = currentBlock; + while (curr != nullptr) { + slot_pointer prev = curr->pointer.prev; + operator delete(reinterpret_cast(curr)); + curr = prev; + } + } + + unsigned int active = 0; + unsigned int blocks = 0; + + pointer allocate() { + active++; + if (freeSlot != nullptr) { + pointer result = reinterpret_cast(freeSlot); + freeSlot = freeSlot->pointer.next; + return result; + } else { + if (currentSlot>=lastSlot) { + allocateBlock(); + } + return reinterpret_cast(currentSlot++); + } + } + + void deallocate(pointer p) { + if (p != nullptr) { + active--; + auto slot = reinterpret_cast(p); + slot->pointer = {.prev = nullptr, .next = freeSlot}; + freeSlot = slot; + } + } + + template + pointer construct(Args &&... args) { + pointer result = allocate(); + new(result) T(std::forward(args)...); + return result; + } + + void destruct(T *p) { + p->~T(); + deallocate(p); + } + + void clear() { + active = 0; + freeSlot = nullptr; + gcQueued = 0; + if (firstBlock) initializeBlock(firstBlock); + } + + void gc(pointer p) { + //flush queued items + if (gcQueued>=GCQueueSize) gcFlush(); + gcQueue[gcQueued++] = p; + } + + void gcFlush() { + for (unsigned int i = 0; i(currentBlock)->pointer.next) { + initializeBlock(reinterpret_cast(currentBlock)->pointer.next); + } else { + blocks++; + // Allocate space for the new block and store a pointer to the previous one + data_pointer newBlock = reinterpret_cast(operator new(BlockSize)); + reinterpret_cast(newBlock)->pointer = {.prev = currentBlock, .next = nullptr}; + setNextBlock(reinterpret_cast(newBlock)); + } + } + + void setNextBlock(slot_pointer nextBlock) { + if (currentBlock) currentBlock->pointer.next = nextBlock; + if (!firstBlock) firstBlock = nextBlock; + initializeBlock(nextBlock); + } + + slot_pointer blockStartSlot(slot_pointer block) { + auto blockPoint = reinterpret_cast(block); + return reinterpret_cast(blockPoint + sizeof(slot_type)); + } + + void initializeBlock(slot_pointer nextBlock) { + currentBlock = nextBlock; + currentSlot = blockStartSlot(nextBlock); + lastSlot = reinterpret_cast(reinterpret_cast(nextBlock) + BlockSize - sizeof(slot_type) + 1); + } +}; \ No newline at end of file diff --git a/src/checker/types2.h b/src/checker/types2.h index 80ae24f..fcfe1e6 100644 --- a/src/checker/types2.h +++ b/src/checker/types2.h @@ -11,7 +11,7 @@ namespace ts::vm2 { using std::string; using std::string_view; - enum class TypeKind: unsigned char { + enum class TypeKind: unsigned int { Unknown, Never, Any, @@ -126,18 +126,34 @@ namespace ts::vm2 { struct TypeRef { Type *type; TypeRef *next = nullptr; + explicit TypeRef(): type(nullptr), next(nullptr) {} + explicit TypeRef(Type *type, TypeRef *next = nullptr): type(type), next(next) {} }; struct Type { TypeKind kind; + + //used to map type to sourcecode unsigned int ip; - string_view text; + /** see TypeFlag */ unsigned int flag = 0; + + //ref counter for garbage collection unsigned int refCount = 0; + + string_view text; uint64_t hash = 0; + + //used as address for FunctionRef unsigned int size = 0; - void *type = nullptr; //either Type* or TypeRef* or string* depending on kind + + //either Type* or TypeRef* or string* depending on kind + void *type = nullptr; + + std::span children; + + Type(TypeKind kind, uint64_t hash): kind(kind), hash(hash) {} ~Type() { if (kind == TypeKind::Literal && type) delete (string *) type; @@ -151,7 +167,7 @@ namespace ts::vm2 { flag = literal->flag; if (literal->type) { //dynamic value, so copy it - setDynamicText(literal->text); + setDynamicText(literal->text, literal->hash); } else { //static value, safe reuse of string_view's reference text = literal->text; @@ -186,7 +202,7 @@ namespace ts::vm2 { } } - void setDynamicText(string_view value) { + void setDynamicText(string_view value, uint64_t hash = 0) { if (!type) { type = new string(value); } else { @@ -194,12 +210,12 @@ namespace ts::vm2 { ((string *) type)->append(value); } text = *(string *) type; - hash = hash::runtime_hash(text); + this->hash = hash ? hash : hash::runtime_hash(text); } void setDynamicLiteral(TypeFlag flag, string_view value) { + this->flag |= flag; setDynamicText(value); - setLiteral(flag, *(string *) type); } Type *setFlag(TypeFlag flag) { @@ -231,15 +247,82 @@ namespace ts::vm2 { } }; - inline void forEach(Type *type, std::function callback) { - bool stop = false; + inline Type *findChild(Type *type, uint64_t hash) { + if (type->children.empty()) { + auto current = (TypeRef *)type->type; + while (current) { + if (current->type->hash == hash) return current->type; + current = current->next; + } + return nullptr; + } else { + TypeRef *entry = &type->children[hash % type->children.size()]; + if (!entry->type) return nullptr; + while (entry && entry->type->hash != hash) { + //step through linked collisions + entry = entry->next; + } + return entry ? entry->type : nullptr; + } + } - auto current = (TypeRef *) type->type; - while (!stop && current) { - callback(current, stop); + inline void forEachChild(Type *type, const std::function &callback) { + if (type->type) { + auto stop = false; + auto current = (TypeRef *)type->type; + while (!stop && current) { + callback(current->type, stop); + current = current->next; + } + } else { + auto stop = false; + unsigned int i = 0; + unsigned int end = type->children.size(); + while (!stop && ichildren[i]; + //bucket could be empty + if (child.type) callback(child.type, stop); + if (child.next) { + //has hash collision, execute them as well + auto current = child.next; + while (!stop && current) { + callback(current->type, stop); + current = current->next; + } + } + i++; + } } } + inline void forEachHashTableChild(Type *type, const std::function &callback) { + auto stop = false; + unsigned int i = 0; + unsigned int end = type->children.size(); + while (!stop && ichildren[i]; + //bucket could be empty + if (child.type) callback(child.type, stop); + if (child.next) { + //has hash collision, execute them as well + auto current = child.next; + while (!stop && current) { + callback(current->type, stop); + current = current->next; + } + } + i++; + } + } + + inline Type *getPropertyOrMethodName(Type *type) { + return type->children[0].type; + } + + inline Type *getPropertyOrMethodType(Type *type) { + return type->children[1].type; + } + inline void stringifyType(Type *type, std::string &r) { switch (type->kind) { case TypeKind::Boolean: { @@ -267,23 +350,23 @@ namespace ts::vm2 { break; } case TypeKind::PropertySignature: { - r += string(type->text) + ": "; - stringifyType((Type *) type->type, r); + stringifyType(((TypeRef*)type->type)->type, r); + r += ": "; + stringifyType(((TypeRef*)type->type)->next->type, r); break; } case TypeKind::ObjectLiteral: { r += "{"; unsigned int i = 0; - auto current = (TypeRef *) type->type; - while (current) { + forEachChild(type, [&i, &r](Type *child, auto stop) { if (i++>20) { r += "..."; - break; + stop = true; + return; } - stringifyType(current->type, r); - current = current->next; + stringifyType(child, r); r + "; "; - } + }); r += "}"; break; } @@ -302,7 +385,7 @@ namespace ts::vm2 { break; } case TypeKind::Array: { - r += "<"; + r += "Array<"; stringifyType((Type *) type->type, r); r += ">"; break; diff --git a/src/checker/vm2.cpp b/src/checker/vm2.cpp index 9c15fd5..8486e30 100644 --- a/src/checker/vm2.cpp +++ b/src/checker/vm2.cpp @@ -2,6 +2,7 @@ #include "../hash.h" #include "./check2.h" #include "./vm2_utils.h" +#include "Tracy.hpp" namespace ts::vm2 { void prepare(shared &module) { @@ -11,40 +12,182 @@ namespace ts::vm2 { subroutine->depth = 0; } + inline Type *use(Type *type) { +// debug("use refCount={} {} ref={}", type->refCount, stringify(type), (void *) type); + type->refCount++; + return type; + } + // TypeRef is an owning reference - TypeRef *useAsRef(Type *type) { + TypeRef *useAsRef(Type *type, TypeRef *next = nullptr) { type->refCount++; - auto t = poolRef.newElement(); - t->type = type; - return t; + return poolRef.construct(type, next); } - Type *allocate(TypeKind kind) { - auto type = pool.newElement(); - type->kind = kind; - type->refCount = 0; -// debug("allocate {}", type->kind); - return type; + void addHashChild(Type *type, Type *child, unsigned int size) { + auto bucket = child->hash % size; + auto &entry = type->children[bucket]; + if (entry.type) { + //hash collision, prepend the list + entry.next = useAsRef(child, entry.next); + } else { + entry.type = use(child); + } } - void gc(TypeRef *typeRef) { - if (gcQueueRefIdx>=maxGcSize) { - //garbage collect now - gcRefFlush(); + void addHashChildWithoutRefCounter(Type *type, Type *child, unsigned int size) { + auto bucket = child->hash % size; + auto &entry = type->children[bucket]; + if (entry.type) { + //hash collision, append the list + entry.next = poolRef.construct(child, entry.next); + } else { + entry.type = child; } - gcQueueRef[gcQueueRefIdx++] = typeRef; + } + + Type *allocate(TypeKind kind, uint64_t hash) { + return pool.construct(kind, hash); + } + + std::span allocateRefs(unsigned int size) { + return poolRefs.construct(size); } inline void gcWithoutChildren(Type *type) { - if (gcQueueIdx>=maxGcSize) { - //garbage collect now - gcFlush(); - } + //just for debugging if (type->flag & TypeFlag::Deleted) { throw std::runtime_error("Type already deleted"); } type->flag |= TypeFlag::Deleted; - gcQueue[gcQueueIdx++] = type; + + pool.gc(type); + } + + //void gc(TypeRef *type) { + // type->type->refCount--; + // gc(type->type); + // poolRef.gc(type); + //} + + void gcFlush() { + pool.gcFlush(); + poolRef.gcFlush(); + } + + void gc(Type *type) { + //debug("gc refCount={} {} ref={}", type->refCount, stringify(type), (void *) type); + if (type->refCount>0) return; + gcWithoutChildren(type); + + switch (type->kind) { + case TypeKind::Function: + case TypeKind::Tuple: + case TypeKind::TemplateLiteral: { + auto current = (TypeRef *) type->type; + while (current) { + auto next = current->next; + current->type->refCount--; + gc(current->type); + current = next; + } + + current = (TypeRef *) type->type; + while (current) { + poolRef.gc(current); + current = current->next; + } + break; + } + case TypeKind::MethodSignature: + case TypeKind::PropertySignature: { + auto nameRef = (TypeRef *) type->type; + auto propTypeRef = nameRef->next; + + poolRef.gc(nameRef); + poolRef.gc(propTypeRef); + + nameRef->type->refCount--; + propTypeRef->type->refCount--; + + gc(nameRef->type); + gc(propTypeRef->type); + + break; + } + case TypeKind::Union: + case TypeKind::ObjectLiteral: { + auto current = (TypeRef *) type->type; + while (current) { + auto next = current->next; + current->type->refCount--; + gc(current->type); + current = next; + } + + current = (TypeRef *) type->type; + while (current) { + poolRef.gc(current); + current = current->next; + } + + if (!type->children.empty()) { + for (auto &&child: type->children) { + //collision list needs to be GC, too + auto current = child.next; + while (current) { + auto next = current->next; + poolRef.gc(current); + current = next; + } + } + poolRefs.gc(type->children); + } + break; + } + case TypeKind::Array: + case TypeKind::Rest: + case TypeKind::TupleMember: { + ((Type *) type->type)->refCount--; + gc((Type *) type->type); + break; + } + } + } + + void drop(std::span types) { + for (auto &&type: types) { + drop(type.type); + } + poolRefs.gc(types); + } + + void drop(Type *type) { + if (type == nullptr) return; + + if (type->refCount == 0) { + debug("type {} not used already!", stringify(type)); + return; + } + + type->refCount--; +// debug("drop refCount={} {} ref={}", type->refCount, stringify(type), (void *) type); + if (type->refCount == 0) { + gc(type); + } + } + + void gcStackAndFlush() { + gcStack(); + pool.gcFlush(); + poolRef.gcFlush(); + } + + void gcStack() { + for (unsigned int i = 0; ikind) { case TypeKind::Literal: { - if (type->flag & TypeFlag::StringLiteral) return allocate(TypeKind::String); - if (type->flag & TypeFlag::NumberLiteral) return allocate(TypeKind::Number); - if (type->flag & TypeFlag::BooleanLiteral) return allocate(TypeKind::Boolean); - if (type->flag & TypeFlag::BigIntLiteral) return allocate(TypeKind::BigInt); + if (type->flag & TypeFlag::StringLiteral) return allocate(TypeKind::String, hash::const_hash("string")); + if (type->flag & TypeFlag::NumberLiteral) return allocate(TypeKind::Number, hash::const_hash("number")); + if (type->flag & TypeFlag::BooleanLiteral) return allocate(TypeKind::Boolean, hash::const_hash("boolean")); + if (type->flag & TypeFlag::BigIntLiteral) return allocate(TypeKind::BigInt, hash::const_hash("bigint")); throw std::runtime_error("Invalid literal to widen"); } case TypeKind::Union: { @@ -87,8 +230,7 @@ namespace ts::vm2 { //widen each literal in the union. //remove duplicates (like "12" | "23" => string | string => string) unsigned int flag = 0; - - auto newUnion = allocate(TypeKind::Union); + auto newUnion = allocate(TypeKind::Union, hash::const_hash("union")); while (current) { switch (current->type->kind) { @@ -145,95 +287,6 @@ namespace ts::vm2 { } } - void gc(Type *type) { - //debug("gc refCount={} {} ref={}", type->refCount, stringify(type), (void *) type); - if (type->refCount>0) return; - gcWithoutChildren(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->refCount--; - auto next = current->next; - gc(current); - gc(current->type); - current = next; - } - break; - } - case TypeKind::Array: - case TypeKind::Rest: - case TypeKind::PropertySignature: - case TypeKind::TupleMember: { - ((Type *) type->type)->refCount--; - gc((Type *) type->type); - break; - } - } - } - - inline Type *use(Type *type) { - type->refCount++; -// debug("use refCount={} {} ref={}", type->refCount, stringify(type), (void *) type); - return type; - } - - void gcRefFlush() { -// debug("gcRefFlush"); - for (unsigned int i = 0; irefCount) 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; - - if (type->refCount == 0) { - debug("type {} not used already!", stringify(type)); - return; - } - type->refCount--; -// debug("drop refCount={} {} ref={}", type->refCount, stringify(type), (void *) type); - if (type->refCount == 0) { - gc(type); - } - } - - void gcStackAndFlush() { - gcStack(); - gcFlush(); - } - - void gcStack() { - for (unsigned int i = 0; idepth>0) { - // for (unsigned int i = 0; ivariables; i++) { - // drop(stack[subroutine->initialSp + i]); - // } - // frame = frames.pop(); - //} - //stack could look like that: // | [T] [T] [V] [P1] [P2] [TailCall] | // T=TypeArgument, V=TypeVariable, but we do not need anything of that, so we GC that. P indicates argument for the call. @@ -409,24 +454,23 @@ namespace ts::vm2 { } Type *resolveObjectIndexType(Type *object, Type *index) { - auto current = (TypeRef *) object->type; switch (index->kind) { case TypeKind::Literal: { - while (current) { - auto member = current->type; - switch (member->kind) { - case TypeKind::Method: - case TypeKind::MethodSignature: - case TypeKind::Property: - case TypeKind::PropertySignature: { - if (object->kind == TypeKind::Class && !(member->flag & TypeFlag::Static)) break; - auto first = (TypeRef *) member->type; - //first->type == index compares unique symbol equality - if (first->type == index || first->type->hash == index->hash) return member; - break; - } + auto member = findChild(object, index->hash); + if (!member) { + return allocate(TypeKind::Never); + } + switch (member->kind) { + case TypeKind::Method: + case TypeKind::MethodSignature: + case TypeKind::Property: + case TypeKind::PropertySignature: { + if (object->kind == TypeKind::Class && !(member->flag & TypeFlag::Static)) break; + auto first = (TypeRef *) member->type; + //first->type == index compares unique symbol equality + if (first->type == index || first->type->hash == index->hash) return member; + break; } - current = current->next; } return allocate(TypeKind::Never); } @@ -434,6 +478,7 @@ namespace ts::vm2 { case TypeKind::BigInt: case TypeKind::Symbol: case TypeKind::String: { + auto current = (TypeRef *) object->type; while (current) { auto member = current->type; switch (member->kind) { @@ -611,19 +656,27 @@ namespace ts::vm2 { auto types = subroutine->pop(size); //short path for `{'asd'}` - if (types.size() == 1 && types[0]->kind == TypeKind::Literal) { - if (types[0]->refCount == 0 && types[0]->flag & TypeFlag::StringLiteral) { + auto first = types[0]; + if (types.size() == 1 && first->kind == TypeKind::Literal) { + if (first->refCount == 0) { //reuse it - push(types[0]); - } else { + if (first->flag & TypeFlag::StringLiteral) { + push(first); + } else if (first->flag & TypeFlag::NumberLiteral) { + first->flag |= ~TypeFlag::NumberLiteral; + first->flag = TypeFlag::StringLiteral; + push(first); + } + return; + } else if (first->flag & TypeFlag::NumberLiteral || first->flag & TypeFlag::StringLiteral) { //create new one auto res = allocate(TypeKind::Literal); - res->fromLiteral(types[0]); + res->fromLiteral(first); res->flag = TypeFlag::StringLiteral; //convert number to string literal if necessary - gc(types[0]); + gc(first); push(res); + return; } - return; } CartesianProduct cartesian; @@ -681,28 +734,6 @@ namespace ts::vm2 { push(result); } -// inline void mapFrameToChildren(Type *container) { -// auto i = subroutine->initialSp + subroutine->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 = subroutine->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 printStack() { debug(""); debug("~~~~~~~~~~~~~~~"); @@ -756,9 +787,10 @@ namespace ts::vm2 { Type *handleFunction(TypeKind kind) { const auto size = subroutine->parseUint16(); - auto type = allocate(kind); + auto name = pop(); + auto type = allocate(kind, name->hash); //first is the name - type->type = useAsRef(pop()); + type->type = useAsRef(name); auto types = subroutine->pop(size); auto current = (TypeRef *) type->type; @@ -778,11 +810,21 @@ namespace ts::vm2 { return type; } + inline auto start = std::chrono::high_resolution_clock::now(); + //string_view frameName; void process() { + ZoneScoped; start: auto &bin = subroutine->module->bin; while (true) { - //debug("[{}:{}] OP {} {}", subroutine->depth, subroutine->depth, subroutine->ip, (OP) bin[subroutine->ip]); + ZoneScoped; + //std::chrono::duration took = std::chrono::high_resolution_clock::now() - start; + //fmt::print(" - took {:.9f}ms\n", took.count()); + //start = std::chrono::high_resolution_clock::now(); + //fmt::print("[{}:{}] OP {} {}\n", subroutine->depth, subroutine->depth, subroutine->ip, (OP) bin[subroutine->ip]); + //auto frameName = new string_view(fmt::format("[{}] {}", subroutine->ip, (OP) bin[subroutine->ip])); + //ZoneName(frameName->begin(), frameName->size()); + switch ((OP) bin[subroutine->ip]) { case OP::Halt: { // subroutine = activeSubroutines.reset(); @@ -807,27 +849,28 @@ namespace ts::vm2 { break; } case OP::Pop: { - gc(pop()); + auto type = pop(); + gc(type); break; } case OP::Never: { - stack[sp++] = allocate(TypeKind::Never); + stack[sp++] = allocate(TypeKind::Never, hash::const_hash("never")); break; } case OP::Any: { - stack[sp++] = allocate(TypeKind::Any); + stack[sp++] = allocate(TypeKind::Any, hash::const_hash("any")); break; } case OP::Undefined: { - stack[sp++] = allocate(TypeKind::Undefined); + stack[sp++] = allocate(TypeKind::Undefined, hash::const_hash("undefined")); break; } case OP::Null: { - stack[sp++] = allocate(TypeKind::Null); + stack[sp++] = allocate(TypeKind::Null, hash::const_hash("null")); break; } case OP::Unknown: { - stack[sp++] = allocate(TypeKind::Unknown); + stack[sp++] = allocate(TypeKind::Unknown, hash::const_hash("unknown")); break; } case OP::Parameter: { @@ -844,7 +887,7 @@ namespace ts::vm2 { } case OP::FunctionRef: { const auto address = subroutine->parseUint32(); - auto type = allocate(TypeKind::FunctionRef); + auto type = allocate(TypeKind::FunctionRef, hash::const_hash("function")); type->size = address; stack[sp++] = type; break; @@ -864,6 +907,21 @@ namespace ts::vm2 { } break; } + case OP::New: { + const auto arguments = subroutine->parseUint16(); + auto ref = pop(); //Class/Object with constructor signature + + switch (ref->kind) { + case TypeKind::ClassInstance: { + report("Can not call new on a class instance."); + break; + } + case TypeKind::Class: { + //push() + } + } + break; + } case OP::Static: { stack[sp - 1]->flag |= TypeFlag::Static; break; @@ -914,8 +972,11 @@ namespace ts::vm2 { //we could convert parameters to a tuple and then run isExtendable() on two tuples, //necessary for REST parameters. + if (typeToCall->refCount == 0) { + //detach returnType from typeToCall so it won't be GC + typeToCall->type = ((TypeRef *) typeToCall->type)->next; + } push(returnType); - gc(typeToCall); break; } @@ -935,6 +996,7 @@ namespace ts::vm2 { auto subroutineToSet = subroutine->module->getSubroutine(address); if (subroutineToSet->narrowed) drop(subroutineToSet->narrowed); subroutineToSet->narrowed = use(type); + push(type); break; } case OP::Assign: { @@ -1034,6 +1096,11 @@ namespace ts::vm2 { } break; } + case OP::CheckBody: { + const auto address = subroutine->parseUint32(); + auto expectedType = stack[sp - 1]; + break; + } case OP::InferBody: { const auto address = subroutine->parseUint32(); auto routine = subroutine->module->getSubroutine(address); @@ -1199,7 +1266,7 @@ namespace ts::vm2 { case OP::TypeArgument: { if (subroutine->size()<=subroutine->typeArguments) { //all variables will be dropped at the end of the subroutine - push(use(allocate(TypeKind::Unknown))); + push(use(allocate(TypeKind::Unknown, hash::const_hash("unknown")))); } else { //for provided argument we do not increase refCount, because it's the caller's job //check constraints @@ -1254,6 +1321,7 @@ namespace ts::vm2 { break; } } + gc(container); push(t); break; } @@ -1280,15 +1348,15 @@ namespace ts::vm2 { break; } case OP::String: { - stack[sp++] = allocate(TypeKind::String); + stack[sp++] = allocate(TypeKind::String, hash::const_hash("string")); break; } case OP::Number: { - stack[sp++] = allocate(TypeKind::Number); + stack[sp++] = allocate(TypeKind::Number, hash::const_hash("number")); break; } case OP::Boolean: { - stack[sp++] = allocate(TypeKind::Boolean); + stack[sp++] = allocate(TypeKind::Boolean, hash::const_hash("boolean")); break; } case OP::NumberLiteral: { @@ -1350,104 +1418,109 @@ namespace ts::vm2 { auto type = allocate(TypeKind::PropertySignature); type->type = useAsRef(name); ((TypeRef *) type->type)->next = useAsRef(propertyType); + type->hash = name->hash; push(type); break; } case OP::Class: { const auto size = subroutine->parseUint16(); - auto item = allocate(TypeKind::Class); + auto type = allocate(TypeKind::Class); if (!size) { - item->type = nullptr; - push(item); + push(type); break; } + //for class type->children acts as static hash map + type->children = allocateRefs(size); auto types = subroutine->pop(size); - item->type = useAsRef(types[0]); - if (types.size()>1) { - auto current = (TypeRef *) item->type; - for_each(++types.begin(), types.end(), [¤t](auto v) { - current->next = useAsRef(v); - current = current->next; - }); - current->next = nullptr; + for (unsigned int i = 0; iparseUint16(); - auto item = allocate(TypeKind::ObjectLiteral); + auto type = allocate(TypeKind::ObjectLiteral); if (!size) { - item->type = nullptr; - push(item); + push(type); break; } + type->size = size; auto types = subroutine->pop(size); - item->type = useAsRef(types[0]); - if (types.size()>1) { - auto current = (TypeRef *) item->type; - for_each(++types.begin(), types.end(), [¤t](auto v) { - current->next = useAsRef(v); + if (size<5) { + type->type = useAsRef(types[0]); + auto current = (TypeRef *) type->type; + for (unsigned int i = 1; inext = useAsRef(types[i]); current = current->next; - }); - current->next = nullptr; + } + } else { + type->children = allocateRefs(size); + for (unsigned int i = 0; iparseUint16(); - auto item = allocate(TypeKind::Union); - //printStack(); + auto type = allocate(TypeKind::Union); if (!size) { - item->type = nullptr; - push(item); + push(type); break; } + auto types = subroutine->pop(size); + auto allocationSize = size; + for (auto &&child: types) { + if (child->kind == TypeKind::Union) allocationSize += child->size - 1; + } + + type->size = allocationSize; auto first = types[0]; if (first->kind == TypeKind::Union) { - //if type has no owner, we can steal its children - if (first->refCount == 0) { - item->type = first->type; - first->type = nullptr; - //since we stole its children, we want it to GC but without its children. their refCount count belongs now to us. - gc(first); + TypeRef *current = nullptr; + forEachChild(first, [&type, ¤t](Type *child, auto) { + if (current) { + current = current->next = useAsRef(child); + } else { + type->type = current = useAsRef(child); + } + }); + gc(first); + } else { + type->type = useAsRef(first); + } + + auto current = (TypeRef *) type->type; + for (unsigned int i = 1; ikind == TypeKind::Union) { + forEachChild(types[i], [¤t](Type *child, auto) { + current = current->next = useAsRef(child); + }); + gc(types[i]); } else { - throw std::runtime_error("Can not merge used union"); + current = (current->next = useAsRef(types[i])); } - } else { - item->type = useAsRef(first); } - if (size>1) { - 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->refCount == 0) { - current->next = (TypeRef *) v->type; - v->type = nullptr; - //since we stole its children, we want it to GC but without its children. their refCount count belongs now to us. - gcWithoutChildren(v); - //set current to the end of the list - while (current->next) current = current->next; - } else { - throw std::runtime_error("Can not merge used union"); - } + + if (allocationSize>5) { + type->children = allocateRefs(allocationSize); + for (unsigned int i = 0; ikind == TypeKind::Union) { + forEachChild(types[i], [&allocationSize, &type](Type *child, auto) { + addHashChildWithoutRefCounter(type, child, allocationSize); + }); } else { - current->next = useAsRef(v); - current = current->next; + addHashChildWithoutRefCounter(type, types[i], allocationSize); } - }); - current->next = nullptr; + } } - push(item); + push(type); break; } case OP::Array: { diff --git a/src/checker/vm2.h b/src/checker/vm2.h index b53f031..dcdee49 100644 --- a/src/checker/vm2.h +++ b/src/checker/vm2.h @@ -2,7 +2,8 @@ #include #include -#include "./MemoryPool.h" +#include "./pool_single.h" +#include "./pool_array.h" #include #include #include @@ -32,18 +33,9 @@ namespace ts::vm2 { // }; constexpr auto poolSize = 10000; - inline MemoryPool pool; - inline MemoryPool poolRef; - void gcFlush(); - void gcRefFlush(); - void printStack(); - - constexpr auto maxGcSize = 4069; - inline std::array gcQueue; - inline unsigned int gcQueueIdx; - - inline std::array gcQueueRef; - inline unsigned int gcQueueRefIdx; + inline PoolSingle pool; + inline PoolSingle poolRef; + inline PoolArray poolRefs; // The stack does not own Type inline std::array stack; @@ -157,7 +149,7 @@ namespace ts::vm2 { } }; - constexpr auto stackSize = 4069; + constexpr auto stackSize = 1024; inline StackPool activeSubroutines; inline StackPool loops; @@ -168,14 +160,18 @@ namespace ts::vm2 { void clear(shared &module); void prepare(shared &module); void drop(Type *type); - void drop(TypeRef *type); - void gc(TypeRef *typeRef); + void drop(std::span *types); + void gc(std::span *types); void gc(Type *type); + void gcFlush(); // Garbage collect whatever is left on the stack void gcStack(); void gcStackAndFlush(); - TypeRef *useAsRef(Type *type); - Type *allocate(TypeKind kind); + + Type *allocate(TypeKind kind, uint64_t hash = 0); + std::span allocateRefs(unsigned int size); + + void addHashChild(Type *type, Type *child, unsigned int size); std::span popFrame(); @@ -185,9 +181,8 @@ namespace ts::vm2 { // poolRef = MemoryPool(); pool.clear(); poolRef.clear(); + poolRefs.clear(); - gcQueueIdx = 0; - gcQueueRefIdx = 0; sp = 0; subroutine = activeSubroutines.reset(); loops.reset(); diff --git a/src/core.h b/src/core.h index 820cf28..a211afc 100644 --- a/src/core.h +++ b/src/core.h @@ -236,8 +236,8 @@ namespace ts { template inline void debug(T fmt, Args &&...args) { // fmt::print(fmt, std::forward(args)...); -// std::cout << fmt::format(fmt, std::forward(args)...) << "\n"; - std::cout << fmt::format(fmt, args...) << "\n"; + std::cout << fmt::format(fmt, std::forward(args)...) << "\n"; +// std::cout << fmt::format(fmt, args...) << "\n"; } inline std::chrono::duration benchRun(int iterations, const function &callback) { diff --git a/src/tests/test_pool_array.cpp b/src/tests/test_pool_array.cpp new file mode 100644 index 0000000..7e8a13a --- /dev/null +++ b/src/tests/test_pool_array.cpp @@ -0,0 +1,213 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#include + +#include +#include "../checker/pool_array.h" +#include "../core.h" +#include + +struct Item { + std::string_view title; + unsigned int i; + std::vector jopp; +}; + +TEST_CASE("basic") { + ts::debug("std::span = {}", sizeof(std::span)); +} + +TEST_CASE("block creation") { + PoolArray pool; + REQUIRE(pool.pool(1).blocks == 0); + REQUIRE(pool.active == 0); + + auto p1 = pool.allocate(1); + REQUIRE(pool.active == 1); + REQUIRE(pool.pool(1).blocks == 1); + + auto p2 = pool.allocate(1); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 2); + + pool.deallocate(p1); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 1); + + pool.deallocate(p2); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 0); + + auto p3 = pool.allocate(1); + REQUIRE(pool.active == 1); + REQUIRE(pool.pool(1).blocks == 1); + + auto p4 = pool.allocate(1); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 2); +} + +TEST_CASE("multi pool") { + PoolArray pool; + auto p1 = pool.construct(2); + REQUIRE(pool.active == 2); + REQUIRE(pool.pool(2).blocks == 1); + + auto p2 = pool.construct(3); + REQUIRE(pool.active == 5); + REQUIRE(pool.pool(2).blocks == 1); + REQUIRE(pool.pool(4).blocks == 1); + + pool.destruct(p2); + REQUIRE(pool.active == 2); + REQUIRE(pool.pool(2).blocks == 1); + REQUIRE(pool.pool(4).blocks == 1); +} + +TEST_CASE("mixed") { + PoolArray pool; + REQUIRE(pool.pool(1).blocks == 0); + REQUIRE(pool.active == 0); + + auto p1 = pool.allocate(1); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 1); + + auto p2 = pool.allocate(2); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 3); + + pool.deallocate(p1); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 2); + + pool.deallocate(p2); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 0); + + auto p3 = pool.allocate(1); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 1); + + pool.deallocate(p3); + REQUIRE(pool.pool(1).blocks == 1); + REQUIRE(pool.active == 0); +} + +TEST_CASE("allocator2") { + PoolArray pool; + auto p1 = pool.allocate(1); + auto p2 = pool.allocate(1); + REQUIRE(pool.active == 2); + REQUIRE(pool.pool(1).blocks == 1); + + auto p3 = pool.allocate(1); + REQUIRE(pool.active == 3); + REQUIRE(pool.pool(1).blocks == 2); + + auto p4 = pool.allocate(1); + REQUIRE(pool.active == 4); + REQUIRE(pool.pool(1).blocks == 2); + + auto p5 = pool.allocate(1); + REQUIRE(pool.active == 5); + REQUIRE(pool.pool(1).blocks == 3); +} + +TEST_CASE("allocator3") { + PoolArray pool; + { + auto p1 = pool.allocate(1); + auto p2 = pool.allocate(1); + REQUIRE(pool.active == 2); + REQUIRE(pool.pool(1).blocks == 1); + + auto p3 = pool.allocate(1); + REQUIRE(pool.active == 3); + REQUIRE(pool.pool(1).blocks == 2); + + auto p4 = pool.allocate(1); + REQUIRE(pool.active == 4); + REQUIRE(pool.pool(1).blocks == 2); + } + + { + pool.clear(); + auto p1 = pool.allocate(1); + REQUIRE(pool.active == 1); + REQUIRE(pool.pool(1).blocks == 2); + + auto p2 = pool.allocate(1); + REQUIRE(pool.active == 2); + REQUIRE(pool.pool(1).blocks == 2); + + //now block 2 should be reused + auto p3 = pool.allocate(1); + REQUIRE(pool.active == 3); + REQUIRE(pool.pool(1).blocks == 2); + + //now block 2 should be reused + auto p4 = pool.allocate(1); + REQUIRE(pool.active == 4); + REQUIRE(pool.pool(1).blocks == 2); + + //now block 3 should be created + auto p5 = pool.allocate(1); + REQUIRE(pool.active == 5); + REQUIRE(pool.pool(1).blocks == 3); + } +} + +TEST_CASE("allocator4") { + PoolArray pool; + pool.clear(); + + auto p1 = pool.construct(1); + REQUIRE(p1[0].i == 0); + REQUIRE(p1[0].title == ""); + + auto p2 = pool.construct(1); + REQUIRE(p2[0].i == 0); + REQUIRE(p2[0].title == ""); + p2[0].i = 2; + REQUIRE(p2[0].i == 2); + + pool.destruct(p2); + + auto p3 = pool.construct(1); + REQUIRE(&p3[0] == &p2[0]); + REQUIRE(p3[0].i == 0); + REQUIRE(p3[0].title == ""); +} + +TEST_CASE("allocator5") { + PoolArray pool; + pool.clear(); + + auto p1 = pool.construct(1); + p1[0].i = 1; + auto p2 = pool.construct(1); + p2[0].i = 2; + + auto p3 = pool.construct(1); + p3[0].i = 3; + auto p4 = pool.construct(1); + p4[0].i = 4; + + auto p5 = pool.construct(1); + p5[0].i = 5; + auto p6_ = pool.construct(1); + pool.destruct(p6_); + + auto p6 = pool.construct(1); + p6[0].i = 6; + REQUIRE(pool.active == 6); + REQUIRE(pool.pool(1).blocks == 3); + + REQUIRE(p1[0].i == 1); + REQUIRE(p2[0].i == 2); + REQUIRE(p3[0].i == 3); + REQUIRE(p4[0].i == 4); + REQUIRE(p5[0].i == 5); + REQUIRE(p6[0].i == 6); +} \ No newline at end of file diff --git a/src/tests/test_allocator.cpp b/src/tests/test_pool_single.cpp similarity index 94% rename from src/tests/test_allocator.cpp rename to src/tests/test_pool_single.cpp index 5f0a874..9bbc5c2 100644 --- a/src/tests/test_allocator.cpp +++ b/src/tests/test_pool_single.cpp @@ -2,7 +2,7 @@ #include #include -#include "../checker/MemoryPool.h" +#include "../checker/pool_single.h" #include struct Item { @@ -12,7 +12,7 @@ struct Item { }; TEST_CASE("allocator1") { - MemoryPool pool; + PoolSingle pool; REQUIRE(pool.blocks == 0); REQUIRE(pool.active == 0); @@ -42,7 +42,7 @@ TEST_CASE("allocator1") { } TEST_CASE("allocator2") { - MemoryPool pool; + PoolSingle pool; auto p1 = pool.allocate(); auto p2 = pool.allocate(); REQUIRE(pool.active == 2); @@ -62,7 +62,7 @@ TEST_CASE("allocator2") { } TEST_CASE("allocator3") { - MemoryPool pool; + PoolSingle pool; { auto p1 = pool.allocate(); auto p2 = pool.allocate(); @@ -106,7 +106,7 @@ TEST_CASE("allocator3") { } TEST_CASE("allocator4") { - MemoryPool pool; + PoolSingle pool; pool.clear(); auto p1 = pool.newElement(); @@ -128,7 +128,7 @@ TEST_CASE("allocator4") { } TEST_CASE("allocator5") { - MemoryPool pool; + PoolSingle pool; pool.clear(); auto p1 = pool.newElement(); diff --git a/src/tests/test_vm2.cpp b/src/tests/test_vm2.cpp index ea44375..4339eb2 100644 --- a/src/tests/test_vm2.cpp +++ b/src/tests/test_vm2.cpp @@ -28,6 +28,13 @@ using std::string_view; // debug("std::array = {}", sizeof(std::array)); //} +TEST_CASE("vm2Base0") { + string code = R"( +const a: string = ''; + )"; + testBench(code, 0); +} + TEST_CASE("vm2Base1") { string code = R"( const v1: string = "abc"; @@ -62,7 +69,15 @@ const v2: number = 44; )", 0); } -TEST_CASE("vm2Union") { +TEST_CASE("vm2Union0") { + string code = R"( +const v1: string | number = 123; +const v2: string | number = true; + )"; + testBench(code, 1); +} + +TEST_CASE("vm2Union1") { string code = R"( type a = T | (string | number); const v1: a = 'yes'; @@ -75,7 +90,7 @@ const v3: a = false; REQUIRE(module->errors.size() == 1); ts::vm2::gcStackAndFlush(); - //only v1, v2, v3 plus for each 4 union (true | string | number) + //only v1, v2, v3 plus for each 4 because union + (true | string | number) REQUIRE(ts::vm2::pool.active == 3 * 4); testBench(code, 1); @@ -290,6 +305,7 @@ const var1: A = [1, 2]; auto module = test(code, 0); ts::vm2::gcFlush(); REQUIRE(ts::vm2::pool.active == (1 + 2) + (1 + 2)); //[1], [1, 2], where "1" in second tuple is shared with first "1" in [1] + testBench(code); } TEST_CASE("vm2Tuple30") { @@ -522,6 +538,7 @@ TEST_CASE("vm2BenchOverhead") { TEST_CASE("vm2Complex1") { string code = R"( type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; //yes + //type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; //no, A refCount too big. //type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; //????, this makes it to 'use' A before the call and [...A, 0] happen //type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; //no, A used after ...A @@ -530,13 +547,16 @@ type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum< const var1: StringToNum<'999', []> = 1002; //const var2: StringToNum<'999'> = 1002; )"; - test(code, 1); - for (auto i = 0; i <1000; i++) { - auto bin = compile(code, false); - assert(bin[439] == OP::TailCall); - } - testBench(code, 1); + testWarmBench(code, 100); + //test(code, 1); + //for (auto i = 0; i <1000; i++) { + // auto bin = compile(code, false); + // assert(bin[439] == OP::TailCall); + //} + //testBench(code, 1, 1); + + usleep(100000); } TEST_CASE("function1") { @@ -562,6 +582,7 @@ TEST_CASE("function2") { } TEST_CASE("function3") { + ZoneScoped; string code = R"( function doIt() { return 1; @@ -570,13 +591,22 @@ TEST_CASE("function3") { const var2: string = doIt(); )"; test(code, 1); - //testBench(code, 1); + testBench(code, 1); } TEST_CASE("function4") { string code = R"( function doIt(v: number) { + if (v == 1) return 'yes'; if (v == 2) return 'yes'; + if (v == 3) return 'yes'; + if (v == 4) return 'yes'; + if (v == 5) return 'yes'; + if (v == 6) return 'yes'; + if (v == 7) return 'yes'; + if (v == 8) return 'yes'; + if (v == 9) return 'yes'; + if (v == 10) return 'yes'; return 1; } const var1: number | string = doIt(0); @@ -604,6 +634,7 @@ TEST_CASE("controlFlow1") { boolFunc(bool); bool = false; + (bool = false); boolFunc(bool); bool = Date.now() > 1000 ? true : false; @@ -615,30 +646,60 @@ TEST_CASE("controlFlow1") { TEST_CASE("class1") { string code = R"( - class Date { + class MyDate { static now(): number { return 0; } } - const now: number = Date.now(); - const now2: string = Date.now(); + const now: number = MyDate.now(); + const now2: string = MyDate.now(); )"; test(code, 1); - //testBench(code, 0); + testBench(code, 1); } TEST_CASE("class2") { string code = R"( - class Date { + class MyDate { now(): number { return 0; } } - const now: number = new Date.now(); - const now2: string = new Date.now(); + const now: number = new MyDate.now(); + const now2: string = new MyDate.now(); )"; test(code, 1); - //testBench(code, 0); + testBench(code, 1); +} + +TEST_CASE("class3") { + string code = R"( + class MyDate { + now(): T { + return 0; + } + } + const now: number = new MyDate.now(); + const now2: string = new MyDate.now(); +)"; + test(code, 1); + testBench(code, 1); +} + +//todo: instance class from value arguments. does this work for functions already? +TEST_CASE("class4") { + string code = R"( + class MyDate { + constructor(public item: T) {} + now(): T { + return this.item; + } + } + const now: number = new MyDate(123).now(); + const now2: string = new MyDate('123').now(); +)"; + test(code, 1); + testBench(code, 1); } TEST_CASE("vm2Cartesian") { @@ -658,9 +719,11 @@ TEST_CASE("vm2Cartesian") { vm2::CartesianProduct cartesian; //`${'a'}${'b'|'c'}` => ('a'|'b')|('a'|'c') cartesian.add(vm2::allocate(TypeKind::Literal)->setLiteral(TypeFlag::StringLiteral, "a")); + auto unionType = allocate(TypeKind::Union); - unionType->appendChild(useAsRef(vm2::allocate(TypeKind::Literal)->setLiteral(TypeFlag::StringLiteral, "b"))); - unionType->appendChild(useAsRef(vm2::allocate(TypeKind::Literal)->setLiteral(TypeFlag::StringLiteral, "c"))); + unionType->children = allocateRefs(2); + addHashChild(unionType, vm2::allocate(TypeKind::Literal)->setLiteral(TypeFlag::StringLiteral, "b"), 2); + addHashChild(unionType, vm2::allocate(TypeKind::Literal)->setLiteral(TypeFlag::StringLiteral, "c"), 2); cartesian.add(unionType); auto product = cartesian.calculate(); REQUIRE(product.size() == 2); @@ -675,51 +738,3 @@ TEST_CASE("vm2Cartesian") { REQUIRE(stringify(second[1]) == "\"c\""); } } - -TEST_CASE("bigUnion") { - ts::checker::Program program; - - for (auto i = 0; i<300; i++) { - program.pushOp(OP::StringLiteral); - program.pushStorage((new string("foo"))->append(to_string(i))); - program.pushOp(OP::StringLiteral); - program.pushStorage("a"); - program.pushOp(OP::PropertySignature); - program.pushOp(OP::ObjectLiteral); - program.pushUint16(1); - program.pushOp(OP::TupleMember); - } - program.pushOp(OP::Tuple); - program.pushUint16(300); - - for (auto i = 0; i<300; i++) { - program.pushOp(OP::StringLiteral); - program.pushStorage((new string("foo"))->append(to_string(i))); - } - program.pushOp(OP::Union); - program.pushUint16(300); - - program.pushOp(OP::StringLiteral); - program.pushStorage("a"); - program.pushOp(OP::PropertySignature); - program.pushOp(OP::ObjectLiteral); - program.pushUint16(1); - program.pushOp(OP::Array); - - program.pushOp(OP::Assign); - program.pushOp(OP::Halt); - - auto module = std::make_shared(program.build(), "app.ts", ""); - run(module); - module->printErrors(); - REQUIRE(module->errors.size() == 0); - - ts::vm2::clear(module); - ts::vm2::gcStackAndFlush(); - REQUIRE(ts::vm2::pool.active == 0); - - ts::bench("first", 1000, [&] { - module->clear(); - run(module); - }); -} diff --git a/src/tests/test_vm2_union.cpp b/src/tests/test_vm2_union.cpp new file mode 100644 index 0000000..814822c --- /dev/null +++ b/src/tests/test_vm2_union.cpp @@ -0,0 +1,69 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include +#include + +#include "../core.h" +#include "../hash.h" +#include "../checker/compiler.h" +#include "../checker/vm2.h" +#include "./utils.h" + +using namespace ts; +using namespace ts::vm2; + +using std::string; +using std::string_view; + +TEST_CASE("bigUnion") { + ts::checker::Program program; + + auto foos = 300; + + for (auto i = 0; iappend(to_string(i))); + program.pushOp(OP::StringLiteral); + program.pushStorage("a"); + program.pushOp(OP::PropertySignature); + program.pushOp(OP::ObjectLiteral); + program.pushUint16(1); + program.pushOp(OP::TupleMember); + } + program.pushOp(OP::Tuple); + program.pushUint16(foos); + + for (auto i = 0; iappend(to_string(i))); + } + program.pushOp(OP::Union); + program.pushUint16(foos); + + program.pushOp(OP::StringLiteral); + program.pushStorage("a"); + program.pushOp(OP::PropertySignature); + program.pushOp(OP::ObjectLiteral); + program.pushUint16(1); + program.pushOp(OP::Array); + + program.pushOp(OP::Assign); + program.pushOp(OP::Halt); + + auto module = std::make_shared(program.build(), "app.ts", ""); + run(module); + run(module); + module->printErrors(); + REQUIRE(module->errors.size() == 0); + + debug("pool.active = {}", ts::vm2::pool.active); + debug("poolRef.active = {}", ts::vm2::poolRef.active); + ts::vm2::clear(module); + ts::vm2::gcStackAndFlush(); + REQUIRE(ts::vm2::pool.active == 0); + REQUIRE(ts::vm2::poolRef.active == 0); + + ts::bench("first", 1000, [&] { + module->clear(); + run(module); + }); +} diff --git a/src/tests/utils.h b/src/tests/utils.h index 68b5754..b6fdb4d 100644 --- a/src/tests/utils.h +++ b/src/tests/utils.h @@ -2,6 +2,7 @@ #include "../checker/compiler.h" #include "../checker/debug.h" #include "../checker/vm2.h" +#include "Tracy.hpp" namespace ts { std::string compile(std::string code, bool print = true) { @@ -23,6 +24,17 @@ namespace ts { return module; } + void testWarmBench(string code, int iterations = 1000) { + auto bin = compile(code); + auto module = make_shared(bin, "app.ts", code); + auto warmTime = benchRun(iterations, [&module] { + ZoneScoped; + module->clear(); + vm2::run(module); + }); + std::cout << fmt::format("{} iterations (it): warm {:.9f}ms/it", iterations, warmTime.count() / iterations); + } + void testBench(string code, unsigned int expectedErrors = 0, int iterations = 1000) { auto bin = compile(code); auto module = make_shared(bin, "app.ts", code); @@ -32,6 +44,7 @@ namespace ts { if (expectedErrors != module->errors.size()) return; auto warmTime = benchRun(iterations, [&module] { + ZoneScoped; module->clear(); vm2::run(module); }); diff --git a/tests/bench.ts b/tests/bench.ts new file mode 100644 index 0000000..557538f --- /dev/null +++ b/tests/bench.ts @@ -0,0 +1,37 @@ +import {CompilerOptions, createCompilerHost, createProgram, getPreEmitDiagnostics} from "typescript"; + +const code = ` + class MyDate { + static now(): number { + return 0; + } + } + const now: number = MyDate.now(); + const now2: string = MyDate.now(); +`; + +const options: CompilerOptions = { + strict: true, + skipLibCheck: true, + types: [], + typeRoots: [], + lib: [], +}; +const host = createCompilerHost(options); +host.readFile = (fileName) => { + return code; +} +host.writeFile = () => { +} + +const program = createProgram(['app.ts'], options, host); +console.log(getPreEmitDiagnostics(program)); + +const iterations = 10; +const start = Date.now(); +for (let i = 0; i < iterations; i++) { + const program = createProgram(['app.ts'], options, host); + const diagnostics = getPreEmitDiagnostics(program); +} +const took = Date.now() - start; +console.log(iterations, 'iterations took', took, 'ms.', took/iterations, 'ms/op'); diff --git a/tests/big1.ts b/tests/big1.ts index d1d19d7..d190f1f 100644 --- a/tests/big1.ts +++ b/tests/big1.ts @@ -352,5 +352,4 @@ const commands: CommandConfig[] = [ {id: 'foo282'}, {id: 'foo283'}, {id: 'foo284'}, {id: 'foo285'}, {id: 'foo286'}, {id: 'foo287'}, {id: 'foo288'}, {id: 'foo289'}, {id: 'foo290'}, {id: 'foo291'}, {id: 'foo292'}, {id: 'foo293'}, {id: 'foo294'}, {id: 'foo295'}, {id: 'foo296'}, {id: 'foo297'}, {id: 'foo298'}, {id: 'foo299'}, - {id: 3} ]; \ No newline at end of file